java.lang.Object
↳ContentProvider
↳androidx.core.content.FileProvider
Subclasses:
BrowserServiceFileProvider
Gradle dependencies
compile group: 'androidx.core', name: 'core', version: '1.15.0-alpha02'
- groupId: androidx.core
- artifactId: core
- version: 1.15.0-alpha02
Artifact androidx.core:core:1.15.0-alpha02 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.core:core com.android.support:support-compat
Androidx class mapping:
androidx.core.content.FileProvider android.support.v4.content.FileProvider
Overview
FileProvider is a special subclass of ContentProvider
that facilitates secure sharing
of files associated with an app by creating a content://
for a file
instead of a file:///
.
A content URI allows you to grant read and write access using
temporary access permissions. When you create an containing
a content URI, in order to send the content URI
to a client app, you can also call to add
permissions. These permissions are available to the client app for as long as the stack for
a receiving is active. For an going to a
, the permissions are available as long as the
is running.
In comparison, to control access to a file:///
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
- Generating the Content URI for a File
- Granting Temporary Permissions to a URI
- Serving a Content URI to Another App
Defining a FileProvider
Extend FileProvider with a default constructor, and call super with an XML resource file that
specifies the available files (see below for the structure of the XML file):
public class MyFileProvider extends FileProvider {
public MyFileProvider() {
super(R.xml.file_paths)
}
}
Add a
<provider>
element to your app manifest. Set the
android:name
attribute to the FileProvider you
created. 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="com.sample.MyFileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
...
</provider>
...
</application>
</manifest>
It is possible to use FileProvider directly instead of extending it. However, this is not
reliable and will causes crashes on some devices.
Specifying Available Files
A FileProvider can only generate a content URI for files in directories that you specify
beforehand. To specify a directory, specify 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 .
<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 .
-
<external-path name="name" path="path" />
Represents files in the root of the external storage area. The root path of this subdirectory
is the same as the value returned by
.
-
<external-files-path name="name" path="path"
/>
Represents files in the root of your app's external storage area. The root path of this
subdirectory is the same as the value returned by
.
-
<external-cache-path name="name" path="path"
/>
Represents files in the root of your app's external cache area. The root path of this
subdirectory is the same as the value returned by
.
-
<external-media-path name="name" path="path"
/>
Represents files in the root of your app's external media area. The root path of this
subdirectory is the same as the value returned by the first result of
.
Note: this directory is only available on API 21+ devices.
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, pass it to super() in the constructor for the
FileProvider you defined above, 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="com.sample.MyFileProvider"
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 java.io.File
for the file, then pass the java.io.File
to getUriForFile(). You can send the content URI
returned by getUriForFile() to another app in an
. The client app that receives the content URI can open the file
and access its contents by calling
to get a .
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(), "my_images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
As a result of the previous snippet,
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
getUriForFile(), you can either grant the
permission to a specific package or include the permission in an intent, as shown in the
following sections.
Grant Permission to a Specific Package
Call the method
for the content://
, 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
,
or both. The permission remains in effect until you revoke it by calling
or until the device
reboots.
Include the Permission in an Intent
To allow the user to choose which app receives the intent, and the permission to access the
content, do the following:
-
Put the content URI in an by calling .
-
Call the method with either
or
or both.
To support devices that run a version between Android 4.1 (API level 16) and Android 5.1
(API level 22) inclusive, create a ClipData
object from the content
URI, and set the access permissions on the ClipData
object:
shareContentIntent.setClipData(ClipData.newRawUri("", contentUri));
shareContentIntent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
Send the to
another app. Most often, you do this by calling
.
Permissions granted in an remain in effect while the stack of the receiving
is active. When the stack finishes, the permissions are
automatically removed. Permissions granted to one 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
,
which sends an to your app to start an 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
sent via .
You can also put the content URI in a ClipData
object and then add the
object to an you send to a client app. To do this, call
. When you use this approach, you can
add multiple ClipData
objects to the , each with its own
content URI. When you call on the
to set temporary access permissions, the same permissions are applied to all of the content
URIs.
Note: The 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
. Set the action to and put the URI in data by calling
.
More Information
To learn more about FileProvider, see the Android training class
Sharing Files Securely with
URIs.
Summary
Methods |
---|
public void | attachInfo(Context context, ProviderInfo info)
After the FileProvider is instantiated, this method is called to provide the system with
information about the provider. |
public int | delete(Uri uri, java.lang.String selection, java.lang.String selectionArgs[])
Deletes the file associated with the specified content URI, as
returned by getUriForFile(). |
public java.lang.String | getType(Uri uri)
Returns the MIME type of a content URI returned by
getUriForFile(). |
public java.lang.String | getTypeAnonymous(Uri uri)
Unrestricted version of getType
called, when caller does not have corresponding permissions |
public static Uri | getUriForFile(Context context, java.lang.String authority, java.io.File file)
Return a content URI for a given java.io.File . |
public static Uri | getUriForFile(Context context, java.lang.String authority, java.io.File file, java.lang.String displayName)
Return a content URI for a given java.io.File . |
public Uri | insert(Uri uri, ContentValues values)
By default, this method throws an java.lang.UnsupportedOperationException . |
public boolean | onCreate()
The default FileProvider implementation does not need to be initialized. |
public ParcelFileDescriptor | openFile(Uri uri, java.lang.String mode)
By default, FileProvider automatically returns the
for a file associated with a content://
. |
public Cursor | query(Uri uri, java.lang.String projection[], java.lang.String selection, java.lang.String selectionArgs[], java.lang.String sortOrder)
Use a content URI returned by
getUriForFile() to get information about a file
managed by the FileProvider. |
public int | update(Uri uri, ContentValues values, java.lang.String selection, java.lang.String selectionArgs[])
By default, this method throws an java.lang.UnsupportedOperationException . |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructors
protected
FileProvider(int resourceId)
Methods
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.
public void
attachInfo(Context context, ProviderInfo info)
After the FileProvider is instantiated, this method is called to provide the system with
information about the provider.
Parameters:
context: A for the current component.
info: A for the new provider.
public static Uri
getUriForFile(Context context, java.lang.String authority, java.io.File file)
Return a content URI for a given java.io.File
. Specific temporary
permissions for the content URI can be set with
, or added
to an by calling and then
; in both cases, the applicable flags are
and
. A FileProvider can only return a
content
for file paths defined in their <paths>
meta-data element. See the Class Overview for more information.
Parameters:
context: A for the current component.
authority: The authority of a FileProvider defined in a
element in your app's manifest.
file: A java.io.File
pointing to the filename for which you want a
content
.
Returns:
A content URI for the file.
public static Uri
getUriForFile(Context context, java.lang.String authority, java.io.File file, java.lang.String displayName)
Return a content URI for a given java.io.File
. Specific temporary
permissions for the content URI can be set with
, or added
to an by calling and then
; in both cases, the applicable flags are
and
. A FileProvider can only return a
content
for file paths defined in their <paths>
meta-data element. See the Class Overview for more information.
Parameters:
context: A for the current component.
authority: The authority of a FileProvider defined in a
element in your app's manifest.
file: A java.io.File
pointing to the filename for which you want a
content
.
displayName: The filename to be displayed. This can be used if the original filename
is undesirable.
Returns:
A content URI for the file.
public Cursor
query(Uri uri, java.lang.String projection[], java.lang.String selection, java.lang.String selectionArgs[], java.lang.String sortOrder)
Use a content URI returned by
getUriForFile() to get information about a file
managed by the FileProvider.
FileProvider reports the column names defined in :
For more information, see
ContentProvider.query()
.
Parameters:
uri: A content URI returned by FileProvider.getUriForFile(Context, String, File).
projection: The list of columns to put into the . If null all columns are
included.
selection: Selection criteria to apply. If null then all data that matches the content
URI is returned.
selectionArgs: An array of java.lang.String
, containing arguments to bind to
the selection parameter. The query method scans selection from left to
right and iterates through selectionArgs, replacing the current "?" character in
selection with the value at the current position in selectionArgs. The
values are bound to selection as java.lang.String
values.
sortOrder: A java.lang.String
containing the column name(s) on which to sort
the resulting .
Returns:
A containing the results of the query.
public java.lang.String
getType(Uri uri)
Returns the MIME type of a content URI returned by
getUriForFile().
Parameters:
uri: A content URI returned by
getUriForFile().
Returns:
If the associated file has an extension, the MIME type associated with that
extension; otherwise application/octet-stream
.
public java.lang.String
getTypeAnonymous(Uri uri)
Unrestricted version of getType
called, when caller does not have corresponding permissions
public Uri
insert(Uri uri, ContentValues values)
By default, this method throws an java.lang.UnsupportedOperationException
. You must
subclass FileProvider if you want to provide different functionality.
public int
update(Uri uri, ContentValues values, java.lang.String selection, java.lang.String selectionArgs[])
By default, this method throws an java.lang.UnsupportedOperationException
. You must
subclass FileProvider if you want to provide different functionality.
public int
delete(Uri uri, java.lang.String selection, java.lang.String selectionArgs[])
Deletes the file associated with the specified content URI, as
returned by getUriForFile(). Notice that this
method does not throw an java.io.IOException
; you must check its return value.
Parameters:
uri: A content URI for a file, as returned by
getUriForFile().
selection: Ignored. Set to null.
selectionArgs: Ignored. Set to null.
Returns:
1 if the delete succeeds; otherwise, 0.
public ParcelFileDescriptor
openFile(Uri uri, java.lang.String mode)
By default, FileProvider automatically returns the
for a file associated with a content://
. To get the , call
.
To override this method, you must provide your own subclass of FileProvider.
Parameters:
uri: A content URI associated with a file, as returned by
getUriForFile().
mode: Access mode for the file. May be "r" for read-only access, "rw" for read and
write access, or "rwt" for read and write access that truncates any existing file.
Returns:
A new with which you can access the file.
Source
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.core.content;
import static androidx.core.util.ObjectsCompat.requireNonNull;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Service;
import android.content.ClipData;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import androidx.annotation.CallSuper;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.XmlRes;
import androidx.core.content.res.ResourcesCompat;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* FileProvider is a special subclass of {@link ContentProvider} that facilitates secure sharing
* of files associated with an app by creating a <code>content://</code> {@link Uri} for a file
* instead of a <code>file:///</code> {@link Uri}.
* <p>
* 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 Activity} is active. For an {@link Intent} going to a
* {@link Service}, the permissions are available as long as the
* {@link Service} is running.
* <p>
* In comparison, to control access to a <code>file:///</code> {@link Uri} you have to modify the
* file system permissions of the underlying file. The permissions you provide become available to
* <em>any</em> app, and remain in effect until you change them. This level of access is
* fundamentally insecure.
* <p>
* The increased level of file access security offered by a content URI
* makes FileProvider a key part of Android's security infrastructure.
* <p>
* This overview of FileProvider includes the following topics:
* </p>
* <ol>
* <li>Defining a FileProvider</li>
* <li>Specifying Available Files</li>
* <li>Generating the Content URI for a File</li>
* <li>Granting Temporary Permissions to a URI</li>
* <li>Serving a Content URI to Another App</li>
* </ol>
* <p>
* <b>Defining a FileProvider</b>
* <p>
* Extend FileProvider with a default constructor, and call super with an XML resource file that
* specifies the available files (see below for the structure of the XML file):
* <pre class="prettyprint">
* public class MyFileProvider extends FileProvider {
* public MyFileProvider() {
* super(R.xml.file_paths)
* }
* }
* </pre>
* Add a
* <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code>
* element to your app manifest. Set the <code>android:name</code> attribute to the FileProvider you
* created. Set the <code>android:authorities</code> attribute to a URI authority based on a
* domain you control; for example, if you control the domain <code>mydomain.com</code> you
* should use the authority <code>com.mydomain.fileprovider</code>. Set the
* <code>android:exported</code> attribute to <code>false</code>; the FileProvider does not need
* to be public. Set the <a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"
* >android:grantUriPermissions</a> attribute to <code>true</code>, to allow you to grant temporary
* access to files. For example:
* <pre class="prettyprint">
* <manifest>
* ...
* <application>
* ...
* <provider
* android:name="com.sample.MyFileProvider"
* android:authorities="com.mydomain.fileprovider"
* android:exported="false"
* android:grantUriPermissions="true">
* ...
* </provider>
* ...
* </application>
* </manifest></pre>
* <p>
* It is possible to use FileProvider directly instead of extending it. However, this is not
* reliable and will causes crashes on some devices.
* <p>
* <b>Specifying Available Files</b>
* <p>
* A FileProvider can only generate a content URI for files in directories that you specify
* beforehand. To specify a directory, specify its storage area and path in XML, using child
* elements of the <code><paths></code> element.
* For example, the following <code>paths</code> element tells FileProvider that you intend to
* request content URIs for the <code>images/</code> subdirectory of your private file area.
* <pre class="prettyprint">
* <paths xmlns:android="http://schemas.android.com/apk/res/android">
* <files-path name="my_images" path="images/"/>
* ...
* </paths>
* </pre>
* <p>
* The <code><paths></code> element must contain one or more of the following child elements:
* <ul>
* <li>
* <pre class="prettyprint"><files-path name="<i>name</i>" path="<i>path</i>" /></pre>
* Represents files in the <code>files/</code> subdirectory of your app's internal storage
* area. This subdirectory is the same as the value returned by {@link Context#getFilesDir()
* Context.getFilesDir()}.
* </li>
* <li><pre><cache-path name="<i>name</i>" path="<i>path</i>" /></pre>
* 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()}.
* <li>
* <pre class="prettyprint"><external-path name="<i>name</i>" path="<i>path</i>" /></pre>
* Represents files in the root of the external storage area. The root path of this subdirectory
* is the same as the value returned by
* {@link Environment#getExternalStorageDirectory() Environment.getExternalStorageDirectory()}.
* <li>
* <pre class="prettyprint"><external-files-path name="<i>name</i>" path="<i>path</i>"
* /></pre>
* Represents files in the root of your app's external storage area. The root path of this
* subdirectory is the same as the value returned by
* {@link Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)}.
* </li>
* <li>
* <pre class="prettyprint"><external-cache-path name="<i>name</i>" path="<i>path</i>"
* /></pre>
* Represents files in the root of your app's external cache area. The root path of this
* subdirectory is the same as the value returned by
* {@link Context#getExternalCacheDir() Context.getExternalCacheDir()}.
* <li>
* <pre class="prettyprint"><external-media-path name="<i>name</i>" path="<i>path</i>"
* /></pre>
* Represents files in the root of your app's external media area. The root path of this
* subdirectory is the same as the value returned by the first result of
* {@link Context#getExternalMediaDirs() Context.getExternalMediaDirs()}.
* <p><strong>Note:</strong> this directory is only available on API 21+ devices.</p>
* </li>
* </ul>
* <p>
* These child elements all use the same attributes:
* <ul>
* <li>
* <code>name="<i>name</i>"</code>
* <p>
* 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
* <code>path</code> attribute.
* </li>
* <li>
* <code>path="<i>path</i>"</code>
* <p>
* The subdirectory you're sharing. While the <code>name</code> attribute is a URI path
* segment, the <code>path</code> value is an actual subdirectory name. Notice that the
* value refers to a <b>subdirectory</b>, 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.
* </li>
* </ul>
* <p>
* You must specify a child element of <code><paths></code> for each directory that contains
* files for which you want content URIs. For example, these XML elements specify two directories:
* <pre class="prettyprint">
* <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>
* </pre>
* <p>
* Put the <code><paths></code> element and its children in an XML file in your project.
* For example, you can add them to a new file called <code>res/xml/file_paths.xml</code>.
* </pre>
* To link this file to the FileProvider, pass it to super() in the constructor for the
* FileProvider you defined above, add a <a href="{@docRoot}guide/topics/manifest/meta-data
* -element.html"><meta-data></a> element as a child of the <code><provider></code>
* element that defines the FileProvider. Set the <code><meta-data></code> element's
* "android:name" attribute to <code>android.support.FILE_PROVIDER_PATHS</code>. Set the
* element's "android:resource" attribute to <code>@xml/file_paths</code> (notice that you
* don't specify the <code>.xml</code> extension). For example:
* <pre class="prettyprint">
* <provider
* android:name="com.sample.MyFileProvider"
* 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>
* </pre>
* <p>
* <b>Generating the Content URI for a File</b>
* <p>
* 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 Intent}. The client app that receives the content URI can open the file
* and access its contents by calling
* {@link ContentResolver#openFileDescriptor(Uri, String)
* ContentResolver.openFileDescriptor} to get a {@link ParcelFileDescriptor}.
* <p>
* For example, suppose your app is offering files to other apps with a FileProvider that has the
* authority <code>com.mydomain.fileprovider</code>. To get a content URI for the file
* <code>default_image.jpg</code> in the <code>images/</code> subdirectory of your internal storage
* add the following code:
* <pre class="prettyprint">
* File imagePath = new File(Context.getFilesDir(), "my_images");
* File newFile = new File(imagePath, "default_image.jpg");
* Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
* </pre>
* As a result of the previous snippet,
* {@link #getUriForFile(Context, String, File) getUriForFile()} returns the content URI
* <code>content://com.mydomain.fileprovider/my_images/default_image.jpg</code>.
* <p>
* <b>Granting Temporary Permissions to a URI</b>
* <p>
* To grant an access permission to a content URI returned from
* {@link #getUriForFile(Context, String, File) getUriForFile()}, you can either grant the
* permission to a specific package or include the permission in an intent, as shown in the
* following sections.
* <h4>Grant Permission to a Specific Package</h4>
* <p>
* Call the method
* {@link Context#grantUriPermission(String, Uri, int)
* Context.grantUriPermission(package, Uri, mode_flags)} for the <code>content://</code>
* {@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 <code>mode_flags</code> 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.
* </p>
* <h4>Include the Permission in an Intent</h4>
* <p>
* To allow the user to choose which app receives the intent, and the permission to access the
* content, do the following:
* </p>
* <ol>
* <li>
* Put the content URI in an {@link Intent} by calling {@link Intent#setData(Uri) setData()}.
* </li>
* <li>
* <p>
* 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.
* </p>
* <p>
* To support devices that run a version between Android 4.1 (API level 16) and Android 5.1
* (API level 22) inclusive, create a {@link ClipData} object from the content
* URI, and set the access permissions on the <code>ClipData</code> object:
* </p>
* <pre class="prettyprint">
* shareContentIntent.setClipData(ClipData.newRawUri("", contentUri));
* shareContentIntent.addFlags(
* Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
* </pre>
* </li>
* <li>
* Send the {@link Intent} to
* another app. Most often, you do this by calling
* {@link Activity#setResult(int, Intent) setResult()}.
* </li>
* </ol>
* <p>
* Permissions granted in an {@link Intent} remain in effect while the stack of the receiving
* {@link Activity} is active. When the stack finishes, the permissions are
* automatically removed. Permissions granted to one {@link Activity} in a client
* app are automatically extended to other components of that app.
* <p>
* <b>Serving a Content URI to Another App</b>
* <p>
* 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 Activity#startActivityForResult(Intent, int, Bundle) startActivityResult()},
* which sends an {@link Intent} to your app to start an {@link 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 Activity#setResult(int, Intent) setResult()}.
* </p>
* <p>
* You can also put the content URI in a {@link 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 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.
* </p>
* <p class="note">
* <strong>Note:</strong> 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()}.
* </p>
* <b>More Information</b>
* <p>
* To learn more about FileProvider, see the Android training class
* <a href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files Securely with
* URIs</a>.
* </p>
*/
public class FileProvider extends ContentProvider {
private static final String[] COLUMNS = {
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
private static final String
META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS";
private static final String TAG_ROOT_PATH = "root-path";
private static final String TAG_FILES_PATH = "files-path";
private static final String TAG_CACHE_PATH = "cache-path";
private static final String TAG_EXTERNAL = "external-path";
private static final String TAG_EXTERNAL_FILES = "external-files-path";
private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
private static final String TAG_EXTERNAL_MEDIA = "external-media-path";
private static final String ATTR_NAME = "name";
private static final String ATTR_PATH = "path";
private static final String DISPLAYNAME_FIELD = "displayName";
private static final File DEVICE_ROOT = new File("/");
@GuardedBy("sCache")
private static final HashMap<String, PathStrategy> sCache = new HashMap<>();
@NonNull
private final Object mLock = new Object();
@GuardedBy("mLock")
private String mAuthority;
// Do NOT access directly! Use getLocalPathStrategy() instead.
@GuardedBy("mLock")
@Nullable
private PathStrategy mLocalPathStrategy;
private final int mResourceId;
public FileProvider() {
this(ResourcesCompat.ID_NULL);
}
protected FileProvider(@XmlRes int resourceId) {
mResourceId = resourceId;
}
/**
* 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.
*/
@Override
public boolean onCreate() {
return true;
}
/**
* After the FileProvider is instantiated, this method is called to provide the system with
* information about the provider.
*
* @param context A {@link Context} for the current component.
* @param info A {@link ProviderInfo} for the new provider.
*/
@SuppressWarnings("StringSplitter")
@CallSuper
@Override
public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
super.attachInfo(context, info);
// Check our security attributes.
if (info.exported) {
// Our intent here is to help application developers to avoid *accidentally* opening up
// this provider to the "world" which may lead to vulnerabilities in their applications.
throw new SecurityException("Provider must not be exported");
}
if (!info.grantUriPermissions) {
throw new SecurityException("Provider must grant uri permissions");
}
final String authority = info.authority.split(";")[0];
synchronized (mLock) {
mAuthority = authority;
}
synchronized (sCache) {
sCache.remove(authority);
}
}
/**
* 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
* <code>content</code> {@link Uri} for file paths defined in their <code><paths></code>
* meta-data element. See the Class Overview for more information.
*
* @param context A {@link Context} for the current component.
* @param authority The authority of a {@link FileProvider} defined in a
* {@code <provider>} element in your app's manifest.
* @param file A {@link File} pointing to the filename for which you want a
* <code>content</code> {@link Uri}.
* @return A content URI for the file.
* @throws IllegalArgumentException When the given {@link File} is outside
* the paths supported by the provider.
*/
public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
@NonNull File file) {
final PathStrategy strategy = getPathStrategy(context, authority, ResourcesCompat.ID_NULL);
return strategy.getUriForFile(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
* <code>content</code> {@link Uri} for file paths defined in their <code><paths></code>
* meta-data element. See the Class Overview for more information.
*
* @param context A {@link Context} for the current component.
* @param authority The authority of a {@link FileProvider} defined in a
* {@code <provider>} element in your app's manifest.
* @param file A {@link File} pointing to the filename for which you want a
* <code>content</code> {@link Uri}.
* @param displayName The filename to be displayed. This can be used if the original filename
* is undesirable.
* @return A content URI for the file.
* @throws IllegalArgumentException When the given {@link File} is outside
* the paths supported by the provider.
*/
@SuppressLint("StreamFiles")
@NonNull
public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
@NonNull File file, @NonNull String displayName) {
Uri uri = getUriForFile(context, authority, file);
return uri.buildUpon().appendQueryParameter(DISPLAYNAME_FIELD, displayName).build();
}
/**
* 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 OpenableColumns}:
* <ul>
* <li>{@link OpenableColumns#DISPLAY_NAME}</li>
* <li>{@link OpenableColumns#SIZE}</li>
* </ul>
* For more information, see
* {@link ContentProvider#query(Uri, String[], String, String[], String)
* ContentProvider.query()}.
*
* @param uri A content URI returned by {@link #getUriForFile}.
* @param projection The list of columns to put into the {@link Cursor}. If null all columns are
* included.
* @param selection Selection criteria to apply. If null then all data that matches the content
* URI is returned.
* @param selectionArgs An array of {@link String}, containing arguments to bind to
* the <i>selection</i> parameter. The <i>query</i> method scans <i>selection</i> from left to
* right and iterates through <i>selectionArgs</i>, replacing the current "?" character in
* <i>selection</i> with the value at the current position in <i>selectionArgs</i>. The
* values are bound to <i>selection</i> as {@link String} values.
* @param sortOrder A {@link String} containing the column name(s) on which to sort
* the resulting {@link Cursor}.
* @return A {@link Cursor} containing the results of the query.
*
*/
@NonNull
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder) {
// ContentProvider has already checked granted permissions
final File file = getLocalPathStrategy().getFileForUri(uri);
String displayName = uri.getQueryParameter(DISPLAYNAME_FIELD);
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++] = (displayName == null) ? file.getName() : displayName;
} 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;
}
/**
* Returns the MIME type of a content URI returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
*
* @param uri A content URI returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
* @return If the associated file has an extension, the MIME type associated with that
* extension; otherwise <code>application/octet-stream</code>.
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
// ContentProvider has already checked granted permissions
final File file = getLocalPathStrategy().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";
}
/**
* Unrestricted version of getType
* called, when caller does not have corresponding permissions
*/
//@Override
@SuppressWarnings("MissingOverride")
@Nullable
public String getTypeAnonymous(@NonNull Uri uri) {
return "application/octet-stream";
}
/**
* By default, this method throws an {@link UnsupportedOperationException}. You must
* subclass FileProvider if you want to provide different functionality.
*/
@Override
public Uri insert(@NonNull Uri uri, @NonNull ContentValues values) {
throw new UnsupportedOperationException("No external inserts");
}
/**
* By default, this method throws an {@link UnsupportedOperationException}. You must
* subclass FileProvider if you want to provide different functionality.
*/
@Override
public int update(@NonNull Uri uri, @NonNull ContentValues values, @Nullable String selection,
@Nullable String[] selectionArgs) {
throw new UnsupportedOperationException("No external updates");
}
/**
* Deletes the file associated with the specified content URI, as
* returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this
* method does <b>not</b> throw an {@link IOException}; you must check its return value.
*
* @param uri A content URI for a file, as returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
* @param selection Ignored. Set to {@code null}.
* @param selectionArgs Ignored. Set to {@code null}.
* @return 1 if the delete succeeds; otherwise, 0.
*/
@Override
public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) {
// ContentProvider has already checked granted permissions
final File file = getLocalPathStrategy().getFileForUri(uri);
return file.delete() ? 1 : 0;
}
/**
* By default, FileProvider automatically returns the
* {@link ParcelFileDescriptor} for a file associated with a <code>content://</code>
* {@link Uri}. To get the {@link ParcelFileDescriptor}, call
* {@link ContentResolver#openFileDescriptor(Uri, String)
* ContentResolver.openFileDescriptor}.
*
* To override this method, you must provide your own subclass of FileProvider.
*
* @param uri A content URI associated with a file, as returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
* @param mode Access mode for the file. May be "r" for read-only access, "rw" for read and
* write access, or "rwt" for read and write access that truncates any existing file.
* @return A new {@link ParcelFileDescriptor} with which you can access the file.
*/
@SuppressLint("UnknownNullness") // b/171012356
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
throws FileNotFoundException {
// ContentProvider has already checked granted permissions
final File file = getLocalPathStrategy().getFileForUri(uri);
final int fileMode = modeToMode(mode);
return ParcelFileDescriptor.open(file, fileMode);
}
/** Return the local {@link PathStrategy}, creating it if necessary. */
@NonNull
private PathStrategy getLocalPathStrategy() {
synchronized (mLock) {
requireNonNull(mAuthority, "mAuthority is null. Did you override attachInfo and "
+ "did not call super.attachInfo()?");
if (mLocalPathStrategy == null) {
mLocalPathStrategy = getPathStrategy(getContext(), mAuthority, mResourceId);
}
return mLocalPathStrategy;
}
}
/**
* Return {@link PathStrategy} for given authority, either by parsing or
* returning from cache.
*/
private static PathStrategy getPathStrategy(Context context, String authority, int resourceId) {
PathStrategy strat;
synchronized (sCache) {
strat = sCache.get(authority);
if (strat == null) {
try {
strat = parsePathStrategy(context, authority, resourceId);
} 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;
}
@VisibleForTesting
static XmlResourceParser getFileProviderPathsMetaData(Context context, String authority,
@Nullable ProviderInfo info,
int resourceId) {
if (info == null) {
throw new IllegalArgumentException(
"Couldn't find meta-data for provider with authority " + authority);
}
if (info.metaData == null && resourceId != ResourcesCompat.ID_NULL) {
info.metaData = new Bundle(1);
info.metaData.putInt(META_DATA_FILE_PROVIDER_PATHS, resourceId);
}
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");
}
return in;
}
/**
* Parse and return {@link PathStrategy} for given authority as defined in
* {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
*
* @see #getPathStrategy(Context, String, int)
*/
private static PathStrategy parsePathStrategy(Context context, String authority, int resourceId)
throws IOException, XmlPullParserException {
final SimplePathStrategy strat = new SimplePathStrategy(authority);
final ProviderInfo info = context.getPackageManager()
.resolveContentProvider(authority, PackageManager.GET_META_DATA);
final XmlResourceParser in = getFileProviderPathsMetaData(context, authority, info,
resourceId);
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 = DEVICE_ROOT;
} else if (TAG_FILES_PATH.equals(tag)) {
target = context.getFilesDir();
} else if (TAG_CACHE_PATH.equals(tag)) {
target = context.getCacheDir();
} else if (TAG_EXTERNAL.equals(tag)) {
target = Environment.getExternalStorageDirectory();
} else if (TAG_EXTERNAL_FILES.equals(tag)) {
File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
if (externalFilesDirs.length > 0) {
target = externalFilesDirs[0];
}
} else if (TAG_EXTERNAL_CACHE.equals(tag)) {
File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(context);
if (externalCacheDirs.length > 0) {
target = externalCacheDirs[0];
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& TAG_EXTERNAL_MEDIA.equals(tag)) {
File[] externalMediaDirs = Api21Impl.getExternalMediaDirs(context);
if (externalMediaDirs.length > 0) {
target = externalMediaDirs[0];
}
}
if (target != null) {
strat.addRoot(name, buildPath(target, path));
}
}
}
return strat;
}
/**
* Strategy for mapping between {@link File} and {@link Uri}.
* <p>
* Strategies must be symmetric so that mapping a {@link File} to a
* {@link Uri} and then back to a {@link File} points at the original
* target.
* <p>
* Strategies must remain consistent across app launches, and not rely on
* dynamic state. This ensures that any generated {@link Uri} can still be
* resolved if your process is killed and later restarted.
*
* @see SimplePathStrategy
*/
interface PathStrategy {
/**
* Return a {@link Uri} that represents the given {@link File}.
*/
Uri getUriForFile(File file);
/**
* Return a {@link File} that represents the given {@link Uri}.
*/
File getFileForUri(Uri uri);
}
/**
* Strategy that provides access to files living under a narrow allowed list
* of filesystem roots. It will throw {@link SecurityException} if callers try
* accessing files outside the configured roots.
* <p>
* For example, if configured with
* {@code addRoot("myfiles", context.getFilesDir())}, then
* {@code context.getFileStreamPath("foo.txt")} would map to
* {@code content://myauthority/myfiles/foo.txt}.
*/
static class SimplePathStrategy implements PathStrategy {
private final String mAuthority;
private final HashMap<String, File> mRoots = new HashMap<>();
SimplePathStrategy(String authority) {
mAuthority = authority;
}
/**
* Add a mapping from a name to a filesystem root. The provider only offers
* access to files that live under configured roots.
*/
void addRoot(String name, File root) {
if (TextUtils.isEmpty(name)) {
throw new IllegalArgumentException("Name must not be empty");
}
try {
// Resolve to canonical path to keep path checking fast
root = root.getCanonicalFile();
} catch (IOException e) {
throw new IllegalArgumentException(
"Failed to resolve canonical path for " + root, e);
}
mRoots.put(name, root);
}
@Override
public Uri getUriForFile(File file) {
String path;
try {
path = file.getCanonicalPath();
} catch (IOException e) {
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
}
// Find the most-specific root path
Map.Entry<String, File> mostSpecific = null;
for (Map.Entry<String, File> root : mRoots.entrySet()) {
final String rootPath = root.getValue().getPath();
if (belongsToRoot(path, rootPath) && (mostSpecific == null
|| rootPath.length() > mostSpecific.getValue().getPath().length())) {
mostSpecific = root;
}
}
if (mostSpecific == null) {
throw new IllegalArgumentException(
"Failed to find configured root that contains " + path);
}
// Start at first char of path under root
final String rootPath = mostSpecific.getValue().getPath();
if (rootPath.endsWith("/")) {
path = path.substring(rootPath.length());
} else {
path = path.substring(rootPath.length() + 1);
}
// Encode the tag and path separately
path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
return new Uri.Builder().scheme("content")
.authority(mAuthority).encodedPath(path).build();
}
@Override
public File getFileForUri(Uri uri) {
String path = uri.getEncodedPath();
final int splitIndex = path.indexOf('/', 1);
final String tag = Uri.decode(path.substring(1, splitIndex));
path = Uri.decode(path.substring(splitIndex + 1));
final File root = mRoots.get(tag);
if (root == null) {
throw new IllegalArgumentException("Unable to find configured root for " + uri);
}
File file = new File(root, path);
try {
file = file.getCanonicalFile();
} catch (IOException e) {
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
}
if (!belongsToRoot(file.getPath(), root.getPath())) {
throw new SecurityException("Resolved path jumped beyond configured root");
}
return file;
}
/**
* Check if the given file is located "under" the given root.
*/
private boolean belongsToRoot(@NonNull String filePath, @NonNull String rootPath) {
// If we naively did the
// filePath.startsWith(rootPath)
// check, we would miss cases such as the following:
// rootPath="files/data"
// filePath="files/data2"
// Thus we'll have to do more here.
// Remove trailing '/'s (if any) first.
filePath = removeTrailingSlash(filePath);
rootPath = removeTrailingSlash(rootPath);
return filePath.equals(rootPath) || filePath.startsWith(rootPath + '/');
}
}
/**
* Copied from ContentResolver.java
*/
private static int modeToMode(String mode) {
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;
}
private static File buildPath(File base, String... segments) {
File cur = base;
for (String segment : segments) {
if (segment != null) {
cur = new File(cur, segment);
}
}
return cur;
}
private static String[] copyOf(String[] original, int newLength) {
final String[] result = new String[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
private static Object[] copyOf(Object[] original, int newLength) {
final Object[] result = new Object[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
@NonNull
private static String removeTrailingSlash(@NonNull String path) {
if (path.length() > 0 && path.charAt(path.length() - 1) == '/') {
return path.substring(0, path.length() - 1);
} else {
return path;
}
}
@RequiresApi(21)
static class Api21Impl {
private Api21Impl() {
// This class is not instantiable.
}
static File[] getExternalMediaDirs(Context context) {
// Deprecated, otherwise this would belong on ContextCompat as a public method.
return context.getExternalMediaDirs();
}
}
}