File Storage

Your photo needs more than a place on the screen. Full-size pictures are too large to stick inside a SQLite database, much less an Intent. They will need a place to live on your device’s filesystem.

Luckily, you have a place to stash these files: your private storage. Recall that you used your private storage to save your SQLite database. With methods like Context.getFileStreamPath(String) and Context.getFilesDir(), you can do the same thing with regular files, too (which will live in a subfolder adjacent to the databases subfolder your SQLite database lives in).

These are the basic file and directory methods in the Context class:

File getFilesDir()

returns a handle to the directory for private application files

FileInputStream openFileInput(String name)

opens an existing file for input (relative to the files directory)

FileOutputStream openFileOutput(String name, int mode)

opens a file for output, possibly creating it (relative to the files directory)

File getDir(String name, int mode)

gets (and possibly creates) a subdirectory within the files directory

String[] fileList()

gets a list of file names in the main files directory, such as for use with openFileInput(String)

File getCacheDir()

returns a handle to a directory you can use specifically for storing cache files; you should take care to keep this directory tidy and use as little space as possible

There is a catch, though. Because these files are private, only your own application can read or write to them. As long as no other app needs to access those files, these methods are sufficient.

However, they are not sufficient if another application needs to write to your files. This is the case for CriminalIntent: The external camera app will need to save the picture it takes as a file in your app. In those cases, these methods do not go far enough: While there is a Context.MODE_WORLD_READABLE flag you can pass into openFileOutput(String, int), it is deprecated and not completely reliable in its effects on newer devices. Once upon a time you could also transfer files using publicly accessible external storage, but this has been locked down in recent versions of Android for security reasons.

If you need to share or receive files with other apps (files like stored pictures), you need to expose those files through a ContentProvider. A ContentProvider allows you to expose content URIs to other apps. They can then download from or write to those content URIs. Either way, you are in control and always have the option to deny those reads or writes if you so choose.

Using FileProvider

When all you need to do is receive a file from another application, implementing an entire ContentProvider is overkill. Fortunately, Google has provided a convenience class called FileProvider that takes care of everything except the configuration work.

The first step is to declare FileProvider as a ContentProvider hooked up to a specific authority. This is done by adding a content provider declaration to your AndroidManifest.xml.

Listing 16.2  Adding a FileProvider declaration (AndroidManifest.xml)

<activity
    android:name=".CrimePagerActivity"
    android:parentActivityName=".CrimeListActivity">
</activity>
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.bignerdranch.android.criminalintent.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
</provider>

The authority is a location – a place that files will be saved to. By hooking up FileProvider to your authority, you give other apps a target for their requests. By adding the exported="false" attribute, you keep anyone from using your provider except you or anyone you grant permission to. And by adding the grantUriPermissions attribute, you add the ability to grant other apps permission to write to URIs on this authority when you send them out in an intent. (Keep an eye out for this later.)

Now that you have told Android where your FileProvider is, you also need to tell your FileProvider which files it is exposing. This bit of configuration is done with an extra XML resource file. Right-click your app/res folder in the project tool window and select NewAndroid resource file. For Resource type, select XML, and then enter files for the name.

Crack open xml/files.xml, switch to the Text tab, and replace its contents with the following:

Listing 16.3  Filling out the paths description (res/xml/files.xml)

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

</PreferenceScreen>
<paths>
    <files-path name="crime_photos" path="."/>
</paths>

This XML file is a description that says, Map the root path of my private storage as crime_photos. You will not use the crime_photos name – FileProvider uses that internally.

Now hook up files.xml to your FileProvider by adding a meta-data tag in your AndroidManifest.xml.

Listing 16.4  Hooking up the paths description (AndroidManifest.xml)

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.bignerdranch.android.criminalintent.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/files"/>
</provider>

Designating a picture location

Time to give your pictures a place to live locally. First, add a method to Crime to get a well-known filename.

Listing 16.5  Adding the filename-derived property (Crime.java)

    public void setSuspect(String suspect) {
        mSuspect = suspect;
    }

    public String getPhotoFilename() {
        return "IMG_" + getId().toString() + ".jpg";
    }
}

Crime.getPhotoFilename() will not know what folder the photo will be stored in. However, the filename will be unique, since it is based on the Crime’s ID.

Next, find where the photos should live. CrimeLab is responsible for everything related to persisting data in CriminalIntent, so it is a natural owner for this idea. Add a getPhotoFile(Crime) method to CrimeLab that provides a complete local filepath for Crime’s image.

Listing 16.6  Finding photo file location (CrimeLab.java)

public class CrimeLab {
    ...
    public Crime getCrime(UUID id) {
        ...
    }

    public File getPhotoFile(Crime crime) {
        File filesDir = mContext.getFilesDir();
        return new File(filesDir, crime.getPhotoFilename());
    }

    public void updateCrime(Crime crime) {
        ...
    }

This code does not create any files on the filesystem. It only returns File objects that point to the right locations. Later on, you will use FileProvider to expose these paths as URIs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
34.200.219.10