1cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey/*
2cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * Copyright (C) 2013 The Android Open Source Project
3cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey *
4cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * you may not use this file except in compliance with the License.
6cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * You may obtain a copy of the License at
7cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey *
8cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey *
10cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * Unless required by applicable law or agreed to in writing, software
11cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * See the License for the specific language governing permissions and
14cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * limitations under the License.
15cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey */
16cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
17cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeypackage android.support.v4.content;
18cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
19cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
20cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport static org.xmlpull.v1.XmlPullParser.START_TAG;
21cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
22cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.content.ContentProvider;
23cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.content.ContentValues;
24cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.content.Context;
25cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.content.Intent;
26cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.content.pm.PackageManager;
27cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.content.pm.ProviderInfo;
28cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.content.res.XmlResourceParser;
29cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.database.Cursor;
30cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.database.MatrixCursor;
31cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.net.Uri;
32cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.os.Environment;
33cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.os.ParcelFileDescriptor;
34cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.provider.OpenableColumns;
35cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.text.TextUtils;
36cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport android.webkit.MimeTypeMap;
37cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
38cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport org.xmlpull.v1.XmlPullParserException;
39cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
40cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport java.io.File;
41cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport java.io.FileNotFoundException;
42cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport java.io.IOException;
43cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport java.util.HashMap;
44cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeyimport java.util.Map;
45cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
46cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey/**
472304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * FileProvider is a special subclass of {@link ContentProvider} that facilitates secure sharing
482304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * of files associated with an app by creating a <code>content://</code> {@link Uri} for a file
492304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * instead of a <code>file:///</code> {@link Uri}.
50cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * <p>
512304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * A content URI allows you to grant read and write access using
522304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * temporary access permissions. When you create an {@link Intent} containing
532304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * a content URI, in order to send the content URI
542304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * to a client app, you can also call {@link Intent#setFlags(int) Intent.setFlags()} to add
552304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * permissions. These permissions are available to the client app for as long as the stack for
562304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * a receiving {@link android.app.Activity} is active. For an {@link Intent} going to a
572304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * {@link android.app.Service}, the permissions are available as long as the
582304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * {@link android.app.Service} is running.
59cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * <p>
602304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * In comparison, to control access to a <code>file:///</code> {@link Uri} you have to modify the
612304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * file system permissions of the underlying file. The permissions you provide become available to
622304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <em>any</em> app, and remain in effect until you change them. This level of access is
632304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * fundamentally insecure.
64cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * <p>
652304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * The increased level of file access security offered by a content URI
662304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * makes FileProvider a key part of Android's security infrastructure.
67cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * <p>
682304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * This overview of FileProvider includes the following topics:
692304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </p>
702304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <ol>
712304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <li><a href="#ProviderDefinition">Defining a FileProvider</a></li>
722304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <li><a href="#SpecifyFiles">Specifying Available Files</a></li>
732304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <li><a href="#GetUri">Retrieving the Content URI for a File</li>
742304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <li><a href="#Permissions">Granting Temporary Permissions to a URI</a></li>
752304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <li><a href="#ServeUri">Serving a Content URI to Another App</a></li>
762304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </ol>
772304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <h3 id="ProviderDefinition">Defining a FileProvider</h3>
78cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * <p>
792304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * Since the default functionality of FileProvider includes content URI generation for files, you
802304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * don't need to define a subclass in code. Instead, you can include a FileProvider in your app
812304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * by specifying it entirely in XML. To specify the FileProvider component itself, add a
822304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">&lt;provider&gt;</a></code>
832304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * element to your app manifest. Set the <code>android:name</code> attribute to
842304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <code>android.support.v4.content.FileProvider</code>. Set the <code>android:authorities</code>
852304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * attribute to a URI authority based on a domain you control; for example, if you control the
862304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * domain <code>mydomain.com</code> you should use the authority
872304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <code>com.mydomain.fileprovider</code>. Set the <code>android:exported</code> attribute to
882304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <code>false</code>; the FileProvider does not need to be public. Set the
892304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"
902304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * >android:grantUriPermissions</a> attribute to <code>true</code>, to allow you
912304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * to grant temporary access to files. For example:
922304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <pre class="prettyprint">
932304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;manifest&gt;
942304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    ...
952304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    &lt;application&gt;
962304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *        ...
972304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *        &lt;provider
982304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *            android:name="android.support.v4.content.FileProvider"
992304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *            android:authorities="com.mydomain.fileprovider"
1002304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *            android:exported="false"
1012304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *            android:grantUriPermissions="true"&gt;
1022304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *            ...
1032304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *        &lt;/provider&gt;
1042304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *        ...
1052304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    &lt;/application&gt;
1062304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;/manifest&gt;</pre>
107cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey * <p>
1082304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * If you want to override any of the default behavior of FileProvider methods, extend
1092304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * the FileProvider class and use the fully-qualified class name in the <code>android:name</code>
1102304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * attribute of the <code>&lt;provider&gt;</code> element.
1112304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <h3 id="SpecifyFiles">Specifying Available Files</h3>
1122304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * A FileProvider can only generate a content URI for files in directories that you specify
1132304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * beforehand. To specify a directory, specify the its storage area and path in XML, using child
1142304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * elements of the <code>&lt;paths&gt;</code> element.
1152304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * For example, the following <code>paths</code> element tells FileProvider that you intend to
1162304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * request content URIs for the <code>images/</code> subdirectory of your private file area.
1172304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <pre class="prettyprint">
1182304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;paths xmlns:android="http://schemas.android.com/apk/res/android"&gt;
1192304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    &lt;files-path name="my_images" path="images/"/&gt;
1202304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    ...
1212304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;/paths&gt;
1222304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *</pre>
1232304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p>
1242304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * The <code>&lt;paths&gt;</code> element must contain one or more of the following child elements:
1252304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </p>
1262304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <dl>
1272304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dt>
1282304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <pre class="prettyprint">
1292304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;files-path name="<i>name</i>" path="<i>path</i>" /&gt;
1302304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *</pre>
1312304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     </dt>
1322304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dd>
1332304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     Represents files in the <code>files/</code> subdirectory of your app's internal storage
1342304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     area. This subdirectory is the same as the value returned by {@link Context#getFilesDir()
1352304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     Context.getFilesDir()}.
1362002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     </dd>
1372002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     <dt>
1382002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette * <pre>
1392002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *&lt;cache-path name="<i>name</i>" path="<i>path</i>" /&gt;
1402002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *</pre>
1412002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     <dt>
1422002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     <dd>
1432002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     Represents files in the cache subdirectory of your app's internal storage area. The root path
1442002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     of this subdirectory is the same as the value returned by {@link Context#getCacheDir()
1452002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     getCacheDir()}.
1462002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     </dd>
1472304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dt>
1482304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <pre class="prettyprint">
1492304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;external-path name="<i>name</i>" path="<i>path</i>" /&gt;
1502304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *</pre>
1512304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     </dt>
1522304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dd>
1532002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     Represents files in the root of the external storage area. The root path of this subdirectory
1542002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     is the same as the value returned by
1552002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     {@link Environment#getExternalStorageDirectory() Environment.getExternalStorageDirectory()}.
1562304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     </dd>
1572304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dt>
1582002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette * <pre class="prettyprint">
1592002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *&lt;external-files-path name="<i>name</i>" path="<i>path</i>" /&gt;
1602304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *</pre>
1612002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     </dt>
1622002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     <dd>
1632002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     Represents files in the root of your app's external storage area. The root path of this
1642002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     subdirectory is the same as the value returned by
1652002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     {@code Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)}.
1662002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     </dd>
1672304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dt>
1682002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette * <pre class="prettyprint">
1692002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *&lt;external-cache-path name="<i>name</i>" path="<i>path</i>" /&gt;
1702002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *</pre>
1712002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     </dt>
1722304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dd>
1732002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     Represents files in the root of your app's external cache area. The root path of this
1742002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     subdirectory is the same as the value returned by
1752002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette *     {@link Context#getExternalCacheDir() Context.getExternalCacheDir()}.
1762304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     </dd>
1772304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </dl>
1782304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p>
1792304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     These child elements all use the same attributes:
1802304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </p>
1812304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <dl>
1822304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dt>
1832304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         <code>name="<i>name</i>"</code>
1842304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     </dt>
1852304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dd>
1862304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         A URI path segment. To enforce security, this value hides the name of the subdirectory
1872304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         you're sharing. The subdirectory name for this value is contained in the
1882304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         <code>path</code> attribute.
1892304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     </dd>
1902304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dt>
1912304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         <code>path="<i>path</i>"</code>
1922304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     </dt>
1932304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <dd>
1942304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         The subdirectory you're sharing. While the <code>name</code> attribute is a URI path
1952304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         segment, the <code>path</code> value is an actual subdirectory name. Notice that the
1962304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         value refers to a <b>subdirectory</b>, not an individual file or files. You can't
1972304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         share a single file by its file name, nor can you specify a subset of files using
1982304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *         wildcards.
1992304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     </dd>
2002304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </dl>
2012304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p>
2022304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * You must specify a child element of <code>&lt;paths&gt;</code> for each directory that contains
2032304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * files for which you want content URIs. For example, these XML elements specify two directories:
2042304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <pre class="prettyprint">
2052304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;paths xmlns:android="http://schemas.android.com/apk/res/android"&gt;
2062304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    &lt;files-path name="my_images" path="images/"/&gt;
2072304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    &lt;files-path name="my_docs" path="docs/"/&gt;
2082304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;/paths&gt;
2092304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *</pre>
2102304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p>
2112304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * Put the <code>&lt;paths&gt;</code> element and its children in an XML file in your project.
2122304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * For example, you can add them to a new file called <code>res/xml/file_paths.xml</code>.
2132304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * To link this file to the FileProvider, add a
2142304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">&lt;meta-data&gt;</a> element
2152304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * as a child of the <code>&lt;provider&gt;</code> element that defines the FileProvider. Set the
2162304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <code>&lt;meta-data&gt;</code> element's "android:name" attribute to
2172304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <code>android.support.FILE_PROVIDER_PATHS</code>. Set the element's "android:resource" attribute
2182304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * to <code>&#64;xml/file_paths</code> (notice that you don't specify the <code>.xml</code>
2192304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * extension). For example:
2202304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <pre class="prettyprint">
2212304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;provider
2222304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    android:name="android.support.v4.content.FileProvider"
2232304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    android:authorities="com.mydomain.fileprovider"
2242304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    android:exported="false"
2252304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    android:grantUriPermissions="true"&gt;
2262304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    &lt;meta-data
2272304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *        android:name="android.support.FILE_PROVIDER_PATHS"
2282304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *        android:resource="&#64;xml/file_paths" /&gt;
2292304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *&lt;/provider&gt;
2302304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *</pre>
2312304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <h3 id="GetUri">Generating the Content URI for a File</h3>
2322304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p>
2332304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * To share a file with another app using a content URI, your app has to generate the content URI.
2342304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * To generate the content URI, create a new {@link File} for the file, then pass the {@link File}
2352304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * to {@link #getUriForFile(Context, String, File) getUriForFile()}. You can send the content URI
2362304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * returned by {@link #getUriForFile(Context, String, File) getUriForFile()} to another app in an
2372304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * {@link android.content.Intent}. The client app that receives the content URI can open the file
2382304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * and access its contents by calling
2392304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
2402304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * ContentResolver.openFileDescriptor} to get a {@link ParcelFileDescriptor}.
2412304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p>
2422304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * For example, suppose your app is offering files to other apps with a FileProvider that has the
2432304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * authority <code>com.mydomain.fileprovider</code>. To get a content URI for the file
2442304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <code>default_image.jpg</code> in the <code>images/</code> subdirectory of your internal storage
2452304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * add the following code:
2462304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <pre class="prettyprint">
2472304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *File imagePath = new File(Context.getFilesDir(), "images");
2482304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *File newFile = new File(imagePath, "default_image.jpg");
2492304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
2502304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *</pre>
2512304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * As a result of the previous snippet,
2522304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * {@link #getUriForFile(Context, String, File) getUriForFile()} returns the content URI
2532304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <code>content://com.mydomain.fileprovider/my_images/default_image.jpg</code>.
2542304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <h3 id="Permissions">Granting Temporary Permissions to a URI</h3>
2552304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * To grant an access permission to a content URI returned from
2562304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * {@link #getUriForFile(Context, String, File) getUriForFile()}, do one of the following:
2572304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <ul>
2582304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <li>
2592304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     Call the method
2602304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     {@link Context#grantUriPermission(String, Uri, int)
2612304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     Context.grantUriPermission(package, Uri, mode_flags)} for the <code>content://</code>
2622304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     {@link Uri}, using the desired mode flags. This grants temporary access permission for the
2632304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     content URI to the specified package, according to the value of the
2642304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     the <code>mode_flags</code> parameter, which you can set to
2652304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}
2662304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     or both. The permission remains in effect until you revoke it by calling
2672304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     {@link Context#revokeUriPermission(Uri, int) revokeUriPermission()} or until the device
2682304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     reboots.
2692304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </li>
2702304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <li>
2712304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     Put the content URI in an {@link Intent} by calling {@link Intent#setData(Uri) setData()}.
2722304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </li>
2732304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <li>
2742304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     Next, call the method {@link Intent#setFlags(int) Intent.setFlags()} with either
2752304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} or
2762304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} or both.
2772304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </li>
2782304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <li>
2792304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     Finally, send the {@link Intent} to
2802304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     another app. Most often, you do this by calling
2812304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     {@link android.app.Activity#setResult(int, android.content.Intent) setResult()}.
2822304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     <p>
2832304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     Permissions granted in an {@link Intent} remain in effect while the stack of the receiving
2842304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     {@link android.app.Activity} is active. When the stack finishes, the permissions are
2852304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     automatically removed. Permissions granted to one {@link android.app.Activity} in a client
2862304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     app are automatically extended to other components of that app.
2872304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *     </p>
2882304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </li>
2892304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </ul>
2902304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <h3 id="ServeUri">Serving a Content URI to Another App</h3>
2912304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p>
2922304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * There are a variety of ways to serve the content URI for a file to a client app. One common way
2932304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * is for the client app to start your app by calling
2942304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * {@link android.app.Activity#startActivityForResult(Intent, int, Bundle) startActivityResult()},
2952304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * which sends an {@link Intent} to your app to start an {@link android.app.Activity} in your app.
2962304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * In response, your app can immediately return a content URI to the client app or present a user
2972304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * interface that allows the user to pick a file. In the latter case, once the user picks the file
2982304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * your app can return its content URI. In both cases, your app returns the content URI in an
2992304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * {@link Intent} sent via {@link android.app.Activity#setResult(int, Intent) setResult()}.
3002304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </p>
3012304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p>
3022304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  You can also put the content URI in a {@link android.content.ClipData} object and then add the
3032304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  object to an {@link Intent} you send to a client app. To do this, call
3042304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  {@link Intent#setClipData(ClipData) Intent.setClipData()}. When you use this approach, you can
3052304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  add multiple {@link android.content.ClipData} objects to the {@link Intent}, each with its own
3062304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  content URI. When you call {@link Intent#setFlags(int) Intent.setFlags()} on the {@link Intent}
3072304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  to set temporary access permissions, the same permissions are applied to all of the content
3082304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  URIs.
3092304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </p>
3102304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p class="note">
3112304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  <strong>Note:</strong> The {@link Intent#setClipData(ClipData) Intent.setClipData()} method is
3122304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  only available in platform version 16 (Android 4.1) and later. If you want to maintain
3132304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  compatibility with previous versions, you should send one content URI at a time in the
3142304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  {@link Intent}. Set the action to {@link Intent#ACTION_SEND} and put the URI in data by calling
3152304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *  {@link Intent#setData setData()}.
3162304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </p>
3172304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <h3 id="">More Information</h3>
3182304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * <p>
3192304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin *    To learn more about FileProvider, see the Android training class
3206ac481ae682ce6c374ab7ba646f55f679fdf9afcNick Kralevich *    <a href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files Securely with URIs</a>.
3212304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin * </p>
322cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey */
323cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkeypublic class FileProvider extends ContentProvider {
324cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static final String[] COLUMNS = {
325cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
326cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
327cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static final String
328cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS";
329cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
330cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static final String TAG_ROOT_PATH = "root-path";
331cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static final String TAG_FILES_PATH = "files-path";
332cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static final String TAG_CACHE_PATH = "cache-path";
333cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static final String TAG_EXTERNAL = "external-path";
3342002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette    private static final String TAG_EXTERNAL_FILES = "external-files-path";
3352002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette    private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
336cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
337cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static final String ATTR_NAME = "name";
338cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static final String ATTR_PATH = "path";
339cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
340cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static final File DEVICE_ROOT = new File("/");
341cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
342cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    // @GuardedBy("sCache")
343cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static HashMap<String, PathStrategy> sCache = new HashMap<String, PathStrategy>();
344cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
345cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private PathStrategy mStrategy;
346cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
3472304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin    /**
3482304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * The default FileProvider implementation does not need to be initialized. If you want to
3492304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * override this method, you must provide your own subclass of FileProvider.
3502304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     */
351cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    @Override
352cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    public boolean onCreate() {
353cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return true;
354cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
355cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
3562304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin    /**
3572304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * After the FileProvider is instantiated, this method is called to provide the system with
3582304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * information about the provider.
3592304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     *
3602304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param context A {@link Context} for the current component.
3612304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param info A {@link ProviderInfo} for the new provider.
3622304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     */
363cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    @Override
364cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    public void attachInfo(Context context, ProviderInfo info) {
365cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        super.attachInfo(context, info);
366cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
367cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        // Sanity check our security
368cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        if (info.exported) {
369cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            throw new SecurityException("Provider must not be exported");
370cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
371cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        if (!info.grantUriPermissions) {
372cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            throw new SecurityException("Provider must grant uri permissions");
373cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
374cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
375cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        mStrategy = getPathStrategy(context, info.authority);
376cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
377cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
378cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    /**
3792304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * Return a content URI for a given {@link File}. Specific temporary
3802304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * permissions for the content URI can be set with
3812304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link Context#grantUriPermission(String, Uri, int)}, or added
3822304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * to an {@link Intent} by calling {@link Intent#setData(Uri) setData()} and then
3832304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link Intent#setFlags(int) setFlags()}; in both cases, the applicable flags are
3842304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
3852304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. A FileProvider can only return a
3862304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * <code>content</code> {@link Uri} for file paths defined in their <code>&lt;paths&gt;</code>
3872304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * meta-data element. See the Class Overview for more information.
388cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     *
3892304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param context A {@link Context} for the current component.
3902304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param authority The authority of a {@link FileProvider} defined in a
391385cccb8927b54284505b0bfdadb95e157cbdfd2Neil Fuller     *            {@code <provider>} element in your app's manifest.
3922304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param file A {@link File} pointing to the filename for which you want a
3932304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * <code>content</code> {@link Uri}.
3942304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @return A content URI for the file.
395cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * @throws IllegalArgumentException When the given {@link File} is outside
3962304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * the paths supported by the provider.
397cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     */
398cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    public static Uri getUriForFile(Context context, String authority, File file) {
399cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final PathStrategy strategy = getPathStrategy(context, authority);
400cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return strategy.getUriForFile(file);
401cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
402cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
4032304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin    /**
4042304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * Use a content URI returned by
4052304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link #getUriForFile(Context, String, File) getUriForFile()} to get information about a file
4062304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * managed by the FileProvider.
4072304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * FileProvider reports the column names defined in {@link android.provider.OpenableColumns}:
4082304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * <ul>
4092304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * <li>{@link android.provider.OpenableColumns#DISPLAY_NAME}</li>
4102304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * <li>{@link android.provider.OpenableColumns#SIZE}</li>
4112304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * </ul>
4122304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * For more information, see
4132304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link ContentProvider#query(Uri, String[], String, String[], String)
4142304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * ContentProvider.query()}.
4152304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     *
4162304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param uri A content URI returned by {@link #getUriForFile}.
4172304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param projection The list of columns to put into the {@link Cursor}. If null all columns are
4182304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * included.
4192304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param selection Selection criteria to apply. If null then all data that matches the content
4202304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * URI is returned.
4212304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param selectionArgs An array of {@link java.lang.String}, containing arguments to bind to
4222304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * the <i>selection</i> parameter. The <i>query</i> method scans <i>selection</i> from left to
4232304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * right and iterates through <i>selectionArgs</i>, replacing the current "?" character in
4242304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * <i>selection</i> with the value at the current position in <i>selectionArgs</i>. The
4252304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * values are bound to <i>selection</i> as {@link java.lang.String} values.
4262304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param sortOrder A {@link java.lang.String} containing the column name(s) on which to sort
4272304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * the resulting {@link Cursor}.
4282304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @return A {@link Cursor} containing the results of the query.
4292304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     *
4302304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     */
431cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    @Override
432cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
433cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            String sortOrder) {
434cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        // ContentProvider has already checked granted permissions
435cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final File file = mStrategy.getFileForUri(uri);
436cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
437cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        if (projection == null) {
438cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            projection = COLUMNS;
439cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
440cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
441cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        String[] cols = new String[projection.length];
442cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        Object[] values = new Object[projection.length];
443cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        int i = 0;
444cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        for (String col : projection) {
445cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (OpenableColumns.DISPLAY_NAME.equals(col)) {
446cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                cols[i] = OpenableColumns.DISPLAY_NAME;
447cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                values[i++] = file.getName();
448cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            } else if (OpenableColumns.SIZE.equals(col)) {
449cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                cols[i] = OpenableColumns.SIZE;
450cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                values[i++] = file.length();
451cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
452cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
453cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
454cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        cols = copyOf(cols, i);
455cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        values = copyOf(values, i);
456cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
457cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final MatrixCursor cursor = new MatrixCursor(cols, 1);
458cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        cursor.addRow(values);
459cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return cursor;
460cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
461cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
4622304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin    /**
4632304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * Returns the MIME type of a content URI returned by
4642304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link #getUriForFile(Context, String, File) getUriForFile()}.
4652304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     *
4662304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param uri A content URI returned by
4672304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link #getUriForFile(Context, String, File) getUriForFile()}.
4682304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @return If the associated file has an extension, the MIME type associated with that
4692304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * extension; otherwise <code>application/octet-stream</code>.
4702304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     */
471cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    @Override
472cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    public String getType(Uri uri) {
473cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        // ContentProvider has already checked granted permissions
474cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final File file = mStrategy.getFileForUri(uri);
475cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
476cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final int lastDot = file.getName().lastIndexOf('.');
477cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        if (lastDot >= 0) {
478cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            final String extension = file.getName().substring(lastDot + 1);
479cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
480cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (mime != null) {
481cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                return mime;
482cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
483cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
484cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
485cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return "application/octet-stream";
486cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
487cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
4882304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin    /**
4892304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must
4902304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * subclass FileProvider if you want to provide different functionality.
4912304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     */
492cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    @Override
493cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    public Uri insert(Uri uri, ContentValues values) {
494cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        throw new UnsupportedOperationException("No external inserts");
495cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
496cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
4972304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin    /**
4982304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must
4992304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * subclass FileProvider if you want to provide different functionality.
5002304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     */
501cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    @Override
502cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
503cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        throw new UnsupportedOperationException("No external updates");
504cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
505cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
5062304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin    /**
5072304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * Deletes the file associated with the specified content URI, as
5082304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this
5092304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * method does <b>not</b> throw an {@link java.io.IOException}; you must check its return value.
5102304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     *
5112304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param uri A content URI for a file, as returned by
5122304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link #getUriForFile(Context, String, File) getUriForFile()}.
5132304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param selection Ignored. Set to {@code null}.
5142304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param selectionArgs Ignored. Set to {@code null}.
5152304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @return 1 if the delete succeeds; otherwise, 0.
5162304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     */
517cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    @Override
518cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    public int delete(Uri uri, String selection, String[] selectionArgs) {
519cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        // ContentProvider has already checked granted permissions
520cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final File file = mStrategy.getFileForUri(uri);
521cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return file.delete() ? 1 : 0;
522cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
523cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
5242304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin    /**
5252304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * By default, FileProvider automatically returns the
5262304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link ParcelFileDescriptor} for a file associated with a <code>content://</code>
5272304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link Uri}. To get the {@link ParcelFileDescriptor}, call
5282304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
5292304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * ContentResolver.openFileDescriptor}.
5302304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     *
5312304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * To override this method, you must provide your own subclass of FileProvider.
5322304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     *
5332304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param uri A content URI associated with a file, as returned by
5342304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * {@link #getUriForFile(Context, String, File) getUriForFile()}.
5352304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @param mode Access mode for the file. May be "r" for read-only access, "rw" for read and
5362304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * write access, or "rwt" for read and write access that truncates any existing file.
5372304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     * @return A new {@link ParcelFileDescriptor} with which you can access the file.
5382304a87017eb4cf2cbe6f1f9e656422dff911962Joe Malin     */
539cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    @Override
540cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
541cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        // ContentProvider has already checked granted permissions
542cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final File file = mStrategy.getFileForUri(uri);
543cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final int fileMode = modeToMode(mode);
544cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return ParcelFileDescriptor.open(file, fileMode);
545cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
546cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
547cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    /**
548cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * Return {@link PathStrategy} for given authority, either by parsing or
549cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * returning from cache.
550cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     */
551cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static PathStrategy getPathStrategy(Context context, String authority) {
552cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        PathStrategy strat;
553cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        synchronized (sCache) {
554cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            strat = sCache.get(authority);
555cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (strat == null) {
556cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                try {
557cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    strat = parsePathStrategy(context, authority);
558cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                } catch (IOException e) {
559cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    throw new IllegalArgumentException(
560cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                            "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
561cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                } catch (XmlPullParserException e) {
562cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    throw new IllegalArgumentException(
563cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                            "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e);
564cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                }
565cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                sCache.put(authority, strat);
566cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
567cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
568cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return strat;
569cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
570cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
571cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    /**
572cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * Parse and return {@link PathStrategy} for given authority as defined in
573385cccb8927b54284505b0bfdadb95e157cbdfd2Neil Fuller     * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
574cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     *
575cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * @see #getPathStrategy(Context, String)
576cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     */
577cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static PathStrategy parsePathStrategy(Context context, String authority)
578cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            throws IOException, XmlPullParserException {
579cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final SimplePathStrategy strat = new SimplePathStrategy(authority);
580cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
581cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final ProviderInfo info = context.getPackageManager()
582cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                .resolveContentProvider(authority, PackageManager.GET_META_DATA);
583cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final XmlResourceParser in = info.loadXmlMetaData(
584cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS);
585cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        if (in == null) {
586cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            throw new IllegalArgumentException(
587cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    "Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data");
588cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
589cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
590cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        int type;
591cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        while ((type = in.next()) != END_DOCUMENT) {
592cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (type == START_TAG) {
593cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                final String tag = in.getName();
594cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
595cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                final String name = in.getAttributeValue(null, ATTR_NAME);
596cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                String path = in.getAttributeValue(null, ATTR_PATH);
597cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
598cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                File target = null;
599cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                if (TAG_ROOT_PATH.equals(tag)) {
6002002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    target = DEVICE_ROOT;
601cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                } else if (TAG_FILES_PATH.equals(tag)) {
6022002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    target = context.getFilesDir();
603cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                } else if (TAG_CACHE_PATH.equals(tag)) {
6042002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    target = context.getCacheDir();
605cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                } else if (TAG_EXTERNAL.equals(tag)) {
6062002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    target = Environment.getExternalStorageDirectory();
6072002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                } else if (TAG_EXTERNAL_FILES.equals(tag)) {
6082002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
6092002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    if (externalFilesDirs.length > 0) {
6102002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                        target = externalFilesDirs[0];
6112002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    }
6122002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                } else if (TAG_EXTERNAL_CACHE.equals(tag)) {
6132002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(context);
6142002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    if (externalCacheDirs.length > 0) {
6152002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                        target = externalCacheDirs[0];
6162002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    }
617cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                }
618cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
619cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                if (target != null) {
6202002bed7d4769fc985b7a8e8b5ba875ffc7e829aAlan Viverette                    strat.addRoot(name, buildPath(target, path));
621cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                }
622cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
623cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
624cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
625cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return strat;
626cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
627cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
628cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    /**
629cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * Strategy for mapping between {@link File} and {@link Uri}.
630cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * <p>
631cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * Strategies must be symmetric so that mapping a {@link File} to a
632cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * {@link Uri} and then back to a {@link File} points at the original
633cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * target.
634cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * <p>
635cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * Strategies must remain consistent across app launches, and not rely on
636cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * dynamic state. This ensures that any generated {@link Uri} can still be
637cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * resolved if your process is killed and later restarted.
638cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     *
639cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * @see SimplePathStrategy
640cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     */
641cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    interface PathStrategy {
642cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        /**
643cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey         * Return a {@link Uri} that represents the given {@link File}.
644cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey         */
645cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        public Uri getUriForFile(File file);
646cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
647cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        /**
648cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey         * Return a {@link File} that represents the given {@link Uri}.
649cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey         */
650cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        public File getFileForUri(Uri uri);
651cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
652cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
653cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    /**
654cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * Strategy that provides access to files living under a narrow whitelist of
655cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * filesystem roots. It will throw {@link SecurityException} if callers try
656cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * accessing files outside the configured roots.
657cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * <p>
658cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * For example, if configured with
659cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * {@code addRoot("myfiles", context.getFilesDir())}, then
660cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * {@code context.getFileStreamPath("foo.txt")} would map to
661cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * {@code content://myauthority/myfiles/foo.txt}.
662cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     */
663cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    static class SimplePathStrategy implements PathStrategy {
664cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        private final String mAuthority;
665cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        private final HashMap<String, File> mRoots = new HashMap<String, File>();
666cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
667cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        public SimplePathStrategy(String authority) {
668cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            mAuthority = authority;
669cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
670cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
671cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        /**
672cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey         * Add a mapping from a name to a filesystem root. The provider only offers
673cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey         * access to files that live under configured roots.
674cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey         */
675cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        public void addRoot(String name, File root) {
676cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (TextUtils.isEmpty(name)) {
677cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                throw new IllegalArgumentException("Name must not be empty");
678cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
679cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
680cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            try {
681cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                // Resolve to canonical path to keep path checking fast
682cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                root = root.getCanonicalFile();
683cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            } catch (IOException e) {
684cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                throw new IllegalArgumentException(
685cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                        "Failed to resolve canonical path for " + root, e);
686cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
687cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
688cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            mRoots.put(name, root);
689cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
690cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
691cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        @Override
692cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        public Uri getUriForFile(File file) {
693cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            String path;
694cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            try {
695cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                path = file.getCanonicalPath();
696cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            } catch (IOException e) {
697cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
698cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
699cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
700cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            // Find the most-specific root path
701cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            Map.Entry<String, File> mostSpecific = null;
702cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            for (Map.Entry<String, File> root : mRoots.entrySet()) {
703cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                final String rootPath = root.getValue().getPath();
704cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                if (path.startsWith(rootPath) && (mostSpecific == null
705cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                        || rootPath.length() > mostSpecific.getValue().getPath().length())) {
706cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    mostSpecific = root;
707cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                }
708cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
709cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
710cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (mostSpecific == null) {
711cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                throw new IllegalArgumentException(
712cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                        "Failed to find configured root that contains " + path);
713cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
714cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
715cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            // Start at first char of path under root
716cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            final String rootPath = mostSpecific.getValue().getPath();
717cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (rootPath.endsWith("/")) {
718cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                path = path.substring(rootPath.length());
719cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            } else {
720cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                path = path.substring(rootPath.length() + 1);
721cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
722cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
723cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            // Encode the tag and path separately
724cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
725cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            return new Uri.Builder().scheme("content")
726cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    .authority(mAuthority).encodedPath(path).build();
727cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
728cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
729cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        @Override
730cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        public File getFileForUri(Uri uri) {
731cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            String path = uri.getEncodedPath();
732cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
733cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            final int splitIndex = path.indexOf('/', 1);
734cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            final String tag = Uri.decode(path.substring(1, splitIndex));
735cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            path = Uri.decode(path.substring(splitIndex + 1));
736cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
737cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            final File root = mRoots.get(tag);
738cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (root == null) {
739cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                throw new IllegalArgumentException("Unable to find configured root for " + uri);
740cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
741cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
742cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            File file = new File(root, path);
743cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            try {
744cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                file = file.getCanonicalFile();
745cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            } catch (IOException e) {
746cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
747cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
748cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
749cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (!file.getPath().startsWith(root.getPath())) {
750cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                throw new SecurityException("Resolved path jumped beyond configured root");
751cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
752cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
753cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            return file;
754cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
755cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
756cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
757cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    /**
758cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     * Copied from ContentResolver.java
759cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey     */
760cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static int modeToMode(String mode) {
761cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        int modeBits;
762cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        if ("r".equals(mode)) {
763cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
764cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        } else if ("w".equals(mode) || "wt".equals(mode)) {
765cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
766cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    | ParcelFileDescriptor.MODE_CREATE
767cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    | ParcelFileDescriptor.MODE_TRUNCATE;
768cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        } else if ("wa".equals(mode)) {
769cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
770cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    | ParcelFileDescriptor.MODE_CREATE
771cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    | ParcelFileDescriptor.MODE_APPEND;
772cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        } else if ("rw".equals(mode)) {
773cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            modeBits = ParcelFileDescriptor.MODE_READ_WRITE
774cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    | ParcelFileDescriptor.MODE_CREATE;
775cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        } else if ("rwt".equals(mode)) {
776cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            modeBits = ParcelFileDescriptor.MODE_READ_WRITE
777cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    | ParcelFileDescriptor.MODE_CREATE
778cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                    | ParcelFileDescriptor.MODE_TRUNCATE;
779cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        } else {
780cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            throw new IllegalArgumentException("Invalid mode: " + mode);
781cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
782cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return modeBits;
783cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
784cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
785cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static File buildPath(File base, String... segments) {
786cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        File cur = base;
787cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        for (String segment : segments) {
788cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            if (segment != null) {
789cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey                cur = new File(cur, segment);
790cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey            }
791cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        }
792cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return cur;
793cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
794cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
795cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static String[] copyOf(String[] original, int newLength) {
796cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final String[] result = new String[newLength];
797cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        System.arraycopy(original, 0, result, 0, newLength);
798cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return result;
799cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
800cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey
801cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    private static Object[] copyOf(Object[] original, int newLength) {
802cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        final Object[] result = new Object[newLength];
803cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        System.arraycopy(original, 0, result, 0, newLength);
804cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey        return result;
805cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey    }
806cdafda9f0228e8cb160a7c873d130dea4bbfea7cJeff Sharkey}
807