You are on page 1of 10

Android tip #1 ContentProvider , Accessing local file system from WebView / showing image in webview using content://

ok, this was a tough one. First be informed that this is something Google (Android people) are trying to prevent i.e. letting browser (WebView) have access to the local file system. In earlier releases of SDK you could access local files using file:// but it is stopped now. Then there was an option where you can provide a WebViewClient and implement shouldOverrideUrlLoading to make it work. This was also removed. The way to make it work now is by implementing your own ContentProvider, there is lot of discussion and documentation on implementing ContentProvider but all that is completely redundant (not needed). The solution is very simple, create your own ContentProvider and only override 1java.lang.String mode) throws java.io.FileNotFoundException Rest of the code in ContentProvder is not needed for this problem. Step 1: Declare your Content Provider in AndroidManifest.xml 1<provider android:name="MyDataContentProvider"
android:authorities="com.techjini" /> public android.os.ParcelFileDescriptor openFile(android.net.Uri uri,

Step 2: Create your ContentProvider and implement openFile All you have to do is get real path from uri, open it and return the descriptor 1URI uri = URI.create("file:///data/data/com.techjini/files/myImage.jpeg"); File file = new File(uri); 2ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, 3ParcelFileDescriptor.MODE_READ_ONLY); 4return parcel; Step 3: (You need this step only if file is not already present on the device/sdcard) Save your content to the file. Following is an example to store a Bitmap 1FileOutputStream fos = openFileOutput("myImage.jpeg", 2Activity.MODE_WORLD_WRITEABLE); imageView.getBitmap().compress(Bitmap.CompressFormat.JPEG, 100, fos); 3 4fos.flush(); 5fos.close();

You can find out where your image is stored using 1System.out.println(getFilesDir().getAbsolutePath()); Step 4: Access the file in WebView 1myWebView.loadUrl("content://com.techjini/myImage.jpeg"); //com.techjini is what you mentioned in 'android:authorities' in your 2AndroidManifest.xml Looks simple

How to display a local file in the Android WebView


For some reason WebView is not displaying local files found on sdcard or in other directories of an Android phone. It was doing so prior to the official 1.0 SDK release, but not anymore. There was another way to display local files - by overriding WebViewClient.shouldOverrideUrlLoading, but since 1.0 SDK it's gone. Right now community agrees that a relatively easy way to make WebView display local files is through a custom ContentProvider. It's not quite obvious what needs to be overridden for this specific task. I couldn't find a complete example online. So I decided to share mine. There are 2 steps 1. Create new class that extends ContentProvider and override essentially 1 method openFile. Method onCreate can be empty returning true and the rest can just throw UnsupportedOperationException. 2. Add a provider entry to AndroidManifest.xml. Provider implementation Note, that method constructUri is just a utility method that can be used to construct provider specific URI out of a file path.

package com.tourizo.android.content; import java.io.*; import import import import android.content.*; android.database.*; android.net.*; android.os.*;

public class LocalFileContentProvider extends ContentProvider { private static final String URI_PREFIX = "content://com.tourizo.android.localfile"; public static String constructUri(String url) { Uri uri = Uri.parse(url); return uri.isAbsolute() ? url : URI_PREFIX + url; } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { File file = new File(uri.getPath()); ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); return parcel; } @Override public boolean onCreate() { return true; } @Override public int delete(Uri uri, String s, String[] as) { throw new UnsupportedOperationException("Not supported by this provider"); } @Override public String getType(Uri uri) { throw new UnsupportedOperationException("Not supported by this provider"); } @Override public Uri insert(Uri uri, ContentValues contentvalues) { throw new UnsupportedOperationException("Not supported by this provider"); } @Override public Cursor query(Uri uri, String[] as, String s, String[] as1, String s1) { throw new UnsupportedOperationException("Not supported by this provider"); } @Override public int update(Uri uri, ContentValues contentvalues, String s, String[] as) { throw new UnsupportedOperationException("Not supported by this provider"); } }

AndroidManifest.xml entry under application tag

<provider android:name=".content.LocalFileContentProvider" android:authorities="com.tourizo.android.localfile" />

I hope this was helpful.

Data Storage
A typical desktop operating system provides a common file system that any application can use to store files that can be read by other applications (perhaps with some access control settings). Android uses a different system: On Android, all application data (including files) are private to that application. However, Android also provides a standard way for an application to expose its private data to other applications through content providers. A content provider is an optional component of an application that exposes read/write access to the application's data, subject to whatever restrictions it might impose. Content providers implement a standard syntax for requesting and modifying data, and a standard mechanism for reading the returned data. Android supplies a number of content providers for standard data types, such as image, audio, and video files and personal contact information. For more information on using content providers, see a separate document, Content Providers. Whether or not you want to export your application's data to others, you need a way to store it. Android provides the following four mechanisms for storing and retrieving data: Preferences, Files, Databases, and Network.

Preferences
Preferences is a lightweight mechanism to store and retrieve key-value pairs of primitive data types. It is typically used to store application preferences, such as a default greeting or a text font to be loaded whenever the application is started. Call Context.getSharedPreferences() to read and write values. Assign a name to your set of preferences if you want to share them with other components in the same application, or use Activity.getPreferences() with no name to keep them private to the calling activity. You cannot share preferences across applications (except by using a content provider). Here is an example of setting user preferences for silent keypress mode for a calculator:

import android.app.Activity; import android.content.SharedPreferences; public class Calc extends Activity { public static final String PREFS_NAME = "MyPrefsFile"; . . . @Override protected void onCreate(Bundle state){ super.onCreate(state); . . . // Restore preferences SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); boolean silent = settings.getBoolean("silentMode", false); setSilent(silent); } @Override protected void onStop(){ super.onStop(); // Save user preferences. We need an Editor object to // make changes. All objects are from android.context.Context SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); SharedPreferences.Editor editor = settings.edit(); editor.putBoolean("silentMode", mSilentMode); // Don't forget to commit your edits!!! editor.commit(); } }

Files
You can store files directly on the mobile device or on a removable storage medium. By default, other applications cannot access these files. To read data from a file, call Context.openFileInput() and pass it the local name and path of the file. It returns a standard Java FileInputStream object. To write to a file, call Context.openFileOutput() with the name and path. It returns a FileOutputStream object. Calling these methods with name and path strings from another application will not work; you can only access local files. If you have a static file to package with your application at compile time, you can save the file in your project in res/raw/myDataFile, and then open it with Resources.openRawResource (R.raw.myDataFile). It returns an InputStream object that you can use to read from the file.

Android Security : Files and Preferences

Unix-style file permissions are presenst in ssAndroid for file systems that are formatted to support them, such as the root file system. Each application has its own area on the file system that it owns, almost like programs have a home directory to go along with their user IDs. An Activity or Services Context object gives access to this directory with the getFilesDir(), getDir(), openFileOutput(), openFileInput(), and getFileStreamPath() methods, but the files and paths returned by the context are not special and can be used with other file-management objects such as FileInputStream. The mode parameter is used to create a file with a given set of file permissions (corresponding to the Unix file permissions). You can bitwise-OR these permissions together. For example, a mode of MODE_WORLD_WRITABLE | MODE_WORLD_READABLE makes a file world-readable and writable. (World is also known as other, so MODE_WORLD_WRITEABLE creates other writeable files, like the command chmod o+w somefile does.) The value MODE_PRIVATE cannot be combined this way because it is just a zero. Somewhat oddly, the mode parameter also indicates if the resultant file is truncated or opened for appending with MODE_APPEND.
The following code is a simple example of creating a sample file that can be read by anyone: fos = openFileOutput("PublicKey", Context.MODE_WORLD_READABLE);

The resultant FileOutputStream (called fos in this example) can be written to only by this process, but it can be read by any program on the system. This interface of passing in flags that indicate whether files are world-readable or world-writable is simpler than the file permissions Linux supports, but should be sufficient for most applications. To experiment with full Linux file permissions, you could try executing chmod, or the less documented android.os.FileUtils classs static method setPermissions(), which takes a filename and a mode uid and gid. Generally, any code that creates data that is world-accessible must be carefully reviewed to consider the following: y y y Is anything written to this file sensitive? For example, something only you know because of a permission you have. Could a change to this data cause something unpleasant or unexpected to happen? Is the data in a complex format whose native parser might have exploitable vulnerabilities? Historically a lot of complex file format parsers written in C or C++ have had exploitable parser bugs. If the file is world-writeable, a bad program could fill up the phones memory and your application would get the blame! This kind of antisocial behavior might happenand because the file is stored under your applications home directory, the user might choose to fix the problem by uninstalling your program or wiping its data.

Obviously, executable code such as scripts, libraries, and configuration files that specify which components, sites, or folders to use would be bad candidates for allowing writes. Log files, databases, and pending work would be bad candidates for world-readability. SharedPreferences is a system feature that is backed by a file with permissions like any others. The mode parameter for getSharedPreferences(String name, int mode) uses the same file modes defined by Context. It is very unlikely you have preferences so unimportant you dont mind if other programs change

them. I recommend avoiding using MODE_WORLD_WRITEABLE and suggest searching for it when reviewing an application as an obvious place to start looking for weaknesses.

Write to and read from a file


If you want to save youre settings or other data, here is a example to write to and read from a file. The file is private for the application, so isn't accessible for other apps or the user. To write some string data to the device:

1. 2. // Save settings 3. 4. public void WriteSettings(Context context, String data){ 5. 6. FileOutputStream fOut = null; 7. 8. OutputStreamWriter osw = null; 9. 10. 11. 12. try{ 13. 14. fOut = openFileOutput("settings.dat",MODE_PRIVATE); 15. 16. osw = new OutputStreamWriter(fOut); 17. 18. osw.write(data); 19. 20. osw.flush(); 21. 22. Toast.makeText(context, "Settings saved",Toast.LENGTH_SHORT).show(); 23. 24. } 25. 26. catch (Exception e) { 27. 28. e.printStackTrace(); 29. 30. Toast.makeText(context, "Settings not saved",Toast.LENGTH_SHORT).show(); 31. } 32.

33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50.

finally { try { osw.close(); fOut.close(); } catch (IOException e) { e.printStackTrace(); } } }

to read the same settings:

1. 2. // Read settings 3. 4. public String ReadSettings(Context context){ 5. 6. FileInputStream fIn = null; 7. 8. InputStreamReader isr = null; 9. 10. 11. 12. char[] inputBuffer = new char[255]; 13. 14. String data = null; 15. 16. 17. 18. try{ 19. 20. fIn = openFileInput("settings.dat"); 21. 22. isr = new InputStreamReader(fIn);

23. 24. isr.read(inputBuffer); 25. 26. data = new String(inputBuffer); 27. 28. Toast.makeText(context, "Settings read",Toast.LENGTH_SHORT).show(); 29. 30. } 31. 32. catch (Exception e) { 33. 34. e.printStackTrace(); 35. 36. Toast.makeText(context, "Settings not read",Toast.LENGTH_SHORT).show(); 37. } 38. 39. 40. finally { 41. 42. try { 43. 44. isr.close(); 45. 46. fIn.close(); 47. } catch (IOException e) { 48. 49. 50. e.printStackTrace(); 51. 52. } 53. 54. } 55. 56. return data; 57. 58. } 59. 60.

How to use the write method:

WriteSettings(this,"setting0, setting1, setting2");

How to read the settings (put in a array for easier use):

1. String data[] = ReadSettings(this).split(",");

where does the settings.dat file get saved? how can I view it? /data/data/PACKAGE_NAME/files/settings.dat For images file, we can use:
Bitmap bitmap = BitmapFactory.decodeFile("data/data/com.apps.myapp/images/img.png"); ImageView imgView = (ImageView) this.findViewById(R.id.imgViewId); imgView.setImageBitmap(bitmap);

You might also like