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 thefiles/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
         pathattribute.
 
- 
         path="path"
         The subdirectory you're sharing. While the nameattribute is a URI path
         segment, thepathvalue 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 ClipDataobject from the content
     URI, and set the access permissions on theClipDataobject:
 
 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();
        }
    }
}