FileProviderpublic class FileProvider extends android.content.ContentProvider FileProvider is a special subclass of {@link ContentProvider} that facilitates secure sharing
of files associated with an app by creating a content:// {@link Uri} for a file
instead of a file:/// {@link Uri}.
A content URI allows you to grant read and write access using
temporary access permissions. When you create an {@link Intent} containing
a content URI, in order to send the content URI
to a client app, you can also call {@link Intent#setFlags(int) Intent.setFlags()} to add
permissions. These permissions are available to the client app for as long as the stack for
a receiving {@link android.app.Activity} is active. For an {@link Intent} going to a
{@link android.app.Service}, the permissions are available as long as the
{@link android.app.Service} is running.
In comparison, to control access to a file:/// {@link Uri} you have to modify the
file system permissions of the underlying file. The permissions you provide become available to
any app, and remain in effect until you change them. This level of access is
fundamentally insecure.
The increased level of file access security offered by a content URI
makes FileProvider a key part of Android's security infrastructure.
This overview of FileProvider includes the following topics:
- Defining a FileProvider
- Specifying Available Files
- Retrieving the Content URI for a File
- Granting Temporary Permissions to a URI
- Serving a Content URI to Another App
Defining a FileProvider
Since the default functionality of FileProvider includes content URI generation for files, you
don't need to define a subclass in code. Instead, you can include a FileProvider in your app
by specifying it entirely in XML. To specify the FileProvider component itself, add a
<provider>
element to your app manifest. Set the android:name attribute to
android.support.v4.content.FileProvider . Set the android:authorities
attribute to a URI authority based on a domain you control; for example, if you control the
domain mydomain.com you should use the authority
com.mydomain.fileprovider . Set the android:exported attribute to
false ; the FileProvider does not need to be public. Set the
android:grantUriPermissions attribute to true , to allow you
to grant temporary access to files. For example:
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
...
</provider>
...
</application>
</manifest>
If you want to override any of the default behavior of FileProvider methods, extend
the FileProvider class and use the fully-qualified class name in the android:name
attribute of the <provider> element.
Specifying Available Files
A FileProvider can only generate a content URI for files in directories that you specify
beforehand. To specify a directory, specify the its storage area and path in XML, using child
elements of the <paths> element.
For example, the following paths element tells FileProvider that you intend to
request content URIs for the images/ subdirectory of your private file area.
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
...
</paths>
The <paths> element must contain one or more of the following child elements:
-
<files-path name="name" path="path" />
-
Represents files in the
files/ subdirectory of your app's internal storage
area. This subdirectory is the same as the value returned by {@link Context#getFilesDir()
Context.getFilesDir()}.
-
<external-path name="name" path="path" />
-
Represents files in the root of your app's external storage area. The path
{@link Context#getExternalFilesDir(String) Context.getExternalFilesDir()} returns the
files/ subdirectory of this this root.
-
<cache-path name="name" path="path" />
-
-
Represents files in the cache subdirectory of your app's internal storage area. The root path
of this subdirectory is the same as the value returned by {@link Context#getCacheDir()
getCacheDir()}.
These child elements all use the same attributes:
-
name="name"
-
A URI path segment. To enforce security, this value hides the name of the subdirectory
you're sharing. The subdirectory name for this value is contained in the
path attribute.
-
path="path"
-
The subdirectory you're sharing. While the
name attribute is a URI path
segment, the path value is an actual subdirectory name. Notice that the
value refers to a subdirectory, not an individual file or files. You can't
share a single file by its file name, nor can you specify a subset of files using
wildcards.
You must specify a child element of <paths> for each directory that contains
files for which you want content URIs. For example, these XML elements specify two directories:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
<files-path name="my_docs" path="docs/"/>
</paths>
Put the <paths> element and its children in an XML file in your project.
For example, you can add them to a new file called res/xml/file_paths.xml .
To link this file to the FileProvider, add a
<meta-data> element
as a child of the <provider> element that defines the FileProvider. Set the
<meta-data> element's "android:name" attribute to
android.support.FILE_PROVIDER_PATHS . Set the element's "android:resource" attribute
to @xml/file_paths (notice that you don't specify the .xml
extension). For example:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
Generating the Content URI for a File
To share a file with another app using a content URI, your app has to generate the content URI.
To generate the content URI, create a new {@link File} for the file, then pass the {@link File}
to {@link #getUriForFile(Context, String, File) getUriForFile()}. You can send the content URI
returned by {@link #getUriForFile(Context, String, File) getUriForFile()} to another app in an
{@link android.content.Intent}. The client app that receives the content URI can open the file
and access its contents by calling
{@link android.content.ContentResolver#openFileDescriptor(Uri, String)
ContentResolver.openFileDescriptor} to get a {@link ParcelFileDescriptor}.
For example, suppose your app is offering files to other apps with a FileProvider that has the
authority com.mydomain.fileprovider . To get a content URI for the file
default_image.jpg in the images/ subdirectory of your internal storage
add the following code:
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
As a result of the previous snippet,
{@link #getUriForFile(Context, String, File) getUriForFile()} returns the content URI
content://com.mydomain.fileprovider/my_images/default_image.jpg .
Granting Temporary Permissions to a URI
To grant an access permission to a content URI returned from
{@link #getUriForFile(Context, String, File) getUriForFile()}, do one of the following:
-
Call the method
{@link Context#grantUriPermission(String, Uri, int)
Context.grantUriPermission(package, Uri, mode_flags)} for the
content://
{@link Uri}, using the desired mode flags. This grants temporary access permission for the
content URI to the specified package, according to the value of the
the mode_flags parameter, which you can set to
{@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}
or both. The permission remains in effect until you revoke it by calling
{@link Context#revokeUriPermission(Uri, int) revokeUriPermission()} or until the device
reboots.
-
Put the content URI in an {@link Intent} by calling {@link Intent#setData(Uri) setData()}.
-
Next, call the method {@link Intent#setFlags(int) Intent.setFlags()} with either
{@link Intent#FLAG_GRANT_READ_URI_PERMISSION} or
{@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} or both.
-
Finally, send the {@link Intent} to
another app. Most often, you do this by calling
{@link android.app.Activity#setResult(int, android.content.Intent) setResult()}.
Permissions granted in an {@link Intent} remain in effect while the stack of the receiving
{@link android.app.Activity} is active. When the stack finishes, the permissions are
automatically removed. Permissions granted to one {@link android.app.Activity} in a client
app are automatically extended to other components of that app.
Serving a Content URI to Another App
There are a variety of ways to serve the content URI for a file to a client app. One common way
is for the client app to start your app by calling
{@link android.app.Activity#startActivityForResult(Intent, int, Bundle) startActivityResult()},
which sends an {@link Intent} to your app to start an {@link android.app.Activity} in your app.
In response, your app can immediately return a content URI to the client app or present a user
interface that allows the user to pick a file. In the latter case, once the user picks the file
your app can return its content URI. In both cases, your app returns the content URI in an
{@link Intent} sent via {@link android.app.Activity#setResult(int, Intent) setResult()}.
You can also put the content URI in a {@link android.content.ClipData} object and then add the
object to an {@link Intent} you send to a client app. To do this, call
{@link Intent#setClipData(ClipData) Intent.setClipData()}. When you use this approach, you can
add multiple {@link android.content.ClipData} objects to the {@link Intent}, each with its own
content URI. When you call {@link Intent#setFlags(int) Intent.setFlags()} on the {@link Intent}
to set temporary access permissions, the same permissions are applied to all of the content
URIs.
Note: The {@link Intent#setClipData(ClipData) Intent.setClipData()} method is
only available in platform version 16 (Android 4.1) and later. If you want to maintain
compatibility with previous versions, you should send one content URI at a time in the
{@link Intent}. Set the action to {@link Intent#ACTION_SEND} and put the URI in data by calling
{@link Intent#setData setData()}.
More Information
To learn more about FileProvider, see the Android training class
Sharing Files Securely with URIs.
|
Fields Summary |
---|
private static final String[] | COLUMNS | private static final String | META_DATA_FILE_PROVIDER_PATHS | private static final String | TAG_ROOT_PATH | private static final String | TAG_FILES_PATH | private static final String | TAG_CACHE_PATH | private static final String | TAG_EXTERNAL | private static final String | ATTR_NAME | private static final String | ATTR_PATH | private static final File | DEVICE_ROOT | private static HashMap | sCache | private PathStrategy | mStrategy |
Methods Summary |
---|
public void | attachInfo(android.content.Context context, android.content.pm.ProviderInfo info)After the FileProvider is instantiated, this method is called to provide the system with
information about the provider.
super.attachInfo(context, info);
// Sanity check our security
if (info.exported) {
throw new SecurityException("Provider must not be exported");
}
if (!info.grantUriPermissions) {
throw new SecurityException("Provider must grant uri permissions");
}
mStrategy = getPathStrategy(context, info.authority);
| private static java.io.File | buildPath(java.io.File base, java.lang.String segments)
File cur = base;
for (String segment : segments) {
if (segment != null) {
cur = new File(cur, segment);
}
}
return cur;
| private static java.lang.String[] | copyOf(java.lang.String[] original, int newLength)
final String[] result = new String[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
| private static java.lang.Object[] | copyOf(java.lang.Object[] original, int newLength)
final Object[] result = new Object[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
| public int | delete(android.net.Uri uri, java.lang.String selection, java.lang.String[] selectionArgs)Deletes the file associated with the specified content URI, as
returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this
method does not throw an {@link java.io.IOException}; you must check its return value.
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
return file.delete() ? 1 : 0;
| private static android.support.v4.content.FileProvider$PathStrategy | getPathStrategy(android.content.Context context, java.lang.String authority)Return {@link PathStrategy} for given authority, either by parsing or
returning from cache.
PathStrategy strat;
synchronized (sCache) {
strat = sCache.get(authority);
if (strat == null) {
try {
strat = parsePathStrategy(context, authority);
} catch (IOException e) {
throw new IllegalArgumentException(
"Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
} catch (XmlPullParserException e) {
throw new IllegalArgumentException(
"Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
}
sCache.put(authority, strat);
}
}
return strat;
| public java.lang.String | getType(android.net.Uri uri)Returns the MIME type of a content URI returned by
{@link #getUriForFile(Context, String, File) getUriForFile()}.
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
final int lastDot = file.getName().lastIndexOf('.");
if (lastDot >= 0) {
final String extension = file.getName().substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
}
}
return "application/octet-stream";
| public static android.net.Uri | getUriForFile(android.content.Context context, java.lang.String authority, java.io.File file)Return a content URI for a given {@link File}. Specific temporary
permissions for the content URI can be set with
{@link Context#grantUriPermission(String, Uri, int)}, or added
to an {@link Intent} by calling {@link Intent#setData(Uri) setData()} and then
{@link Intent#setFlags(int) setFlags()}; in both cases, the applicable flags are
{@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
{@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. A FileProvider can only return a
content {@link Uri} for file paths defined in their <paths>
meta-data element. See the Class Overview for more information.
final PathStrategy strategy = getPathStrategy(context, authority);
return strategy.getUriForFile(file);
| public android.net.Uri | insert(android.net.Uri uri, android.content.ContentValues values)By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must
subclass FileProvider if you want to provide different functionality.
throw new UnsupportedOperationException("No external inserts");
| private static int | modeToMode(java.lang.String mode)Copied from ContentResolver.java
int modeBits;
if ("r".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
} else if ("w".equals(mode) || "wt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else if ("wa".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_APPEND;
} else if ("rw".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE;
} else if ("rwt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else {
throw new IllegalArgumentException("Invalid mode: " + mode);
}
return modeBits;
| public boolean | onCreate()The default FileProvider implementation does not need to be initialized. If you want to
override this method, you must provide your own subclass of FileProvider.
return true;
| public android.os.ParcelFileDescriptor | openFile(android.net.Uri uri, java.lang.String mode)By default, FileProvider automatically returns the
{@link ParcelFileDescriptor} for a file associated with a content://
{@link Uri}. To get the {@link ParcelFileDescriptor}, call
{@link android.content.ContentResolver#openFileDescriptor(Uri, String)
ContentResolver.openFileDescriptor}.
To override this method, you must provide your own subclass of FileProvider.
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
final int fileMode = modeToMode(mode);
return ParcelFileDescriptor.open(file, fileMode);
| private static android.support.v4.content.FileProvider$PathStrategy | parsePathStrategy(android.content.Context context, java.lang.String authority)Parse and return {@link PathStrategy} for given authority as defined in
{@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
final SimplePathStrategy strat = new SimplePathStrategy(authority);
final ProviderInfo info = context.getPackageManager()
.resolveContentProvider(authority, PackageManager.GET_META_DATA);
final XmlResourceParser in = info.loadXmlMetaData(
context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS);
if (in == null) {
throw new IllegalArgumentException(
"Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data");
}
int type;
while ((type = in.next()) != END_DOCUMENT) {
if (type == START_TAG) {
final String tag = in.getName();
final String name = in.getAttributeValue(null, ATTR_NAME);
String path = in.getAttributeValue(null, ATTR_PATH);
File target = null;
if (TAG_ROOT_PATH.equals(tag)) {
target = buildPath(DEVICE_ROOT, path);
} else if (TAG_FILES_PATH.equals(tag)) {
target = buildPath(context.getFilesDir(), path);
} else if (TAG_CACHE_PATH.equals(tag)) {
target = buildPath(context.getCacheDir(), path);
} else if (TAG_EXTERNAL.equals(tag)) {
target = buildPath(Environment.getExternalStorageDirectory(), path);
}
if (target != null) {
strat.addRoot(name, target);
}
}
}
return strat;
| public android.database.Cursor | query(android.net.Uri uri, java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)Use a content URI returned by
{@link #getUriForFile(Context, String, File) getUriForFile()} to get information about a file
managed by the FileProvider.
FileProvider reports the column names defined in {@link android.provider.OpenableColumns}:
- {@link android.provider.OpenableColumns#DISPLAY_NAME}
- {@link android.provider.OpenableColumns#SIZE}
For more information, see
{@link ContentProvider#query(Uri, String[], String, String[], String)
ContentProvider.query()}.
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
if (projection == null) {
projection = COLUMNS;
}
String[] cols = new String[projection.length];
Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
cols[i] = OpenableColumns.DISPLAY_NAME;
values[i++] = file.getName();
} else if (OpenableColumns.SIZE.equals(col)) {
cols[i] = OpenableColumns.SIZE;
values[i++] = file.length();
}
}
cols = copyOf(cols, i);
values = copyOf(values, i);
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
| public int | update(android.net.Uri uri, android.content.ContentValues values, java.lang.String selection, java.lang.String[] selectionArgs)By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must
subclass FileProvider if you want to provide different functionality.
throw new UnsupportedOperationException("No external updates");
|
|