Merge "Expose external app files/cache dir from FileProvider."
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 6117cba..35314ae 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -17,21 +17,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.support.tests">
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
     <application>
         <uses-library android:name="android.test.runner" />
 
-        <provider
-            android:name="android.support.v4.content.FileProvider"
-            android:authorities="moocow"
-            android:grantUriPermissions="true">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/paths" />
-        </provider>
-
         <activity android:name="android.support.tests.GrantActivity" android:label="_GrantActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/v4/java/android/support/v4/content/FileProvider.java b/v4/java/android/support/v4/content/FileProvider.java
index 9d6d803..9e82d63 100644
--- a/v4/java/android/support/v4/content/FileProvider.java
+++ b/v4/java/android/support/v4/content/FileProvider.java
@@ -133,15 +133,6 @@
  *     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()}.
- *     <dt>
- * <pre class="prettyprint">
- *&lt;external-path name="<i>name</i>" path="<i>path</i>" /&gt;
- *</pre>
- *     </dt>
- *     <dd>
- *     Represents files in the root of your app's external storage area. The path
- *     {@link Context#getExternalFilesDir(String) Context.getExternalFilesDir()} returns the
- *     <code>files/</code> subdirectory of this this root.
  *     </dd>
  *     <dt>
  * <pre>
@@ -153,6 +144,36 @@
  *     of this subdirectory is the same as the value returned by {@link Context#getCacheDir()
  *     getCacheDir()}.
  *     </dd>
+ *     <dt>
+ * <pre class="prettyprint">
+ *&lt;external-path name="<i>name</i>" path="<i>path</i>" /&gt;
+ *</pre>
+ *     </dt>
+ *     <dd>
+ *     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()}.
+ *     </dd>
+ *     <dt>
+ * <pre class="prettyprint">
+ *&lt;external-files-path name="<i>name</i>" path="<i>path</i>" /&gt;
+ *</pre>
+ *     </dt>
+ *     <dd>
+ *     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
+ *     {@code Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)}.
+ *     </dd>
+ *     <dt>
+ * <pre class="prettyprint">
+ *&lt;external-cache-path name="<i>name</i>" path="<i>path</i>" /&gt;
+ *</pre>
+ *     </dt>
+ *     <dd>
+ *     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()}.
+ *     </dd>
  * </dl>
  * <p>
  *     These child elements all use the same attributes:
@@ -310,6 +331,8 @@
     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 ATTR_NAME = "name";
     private static final String ATTR_PATH = "path";
@@ -574,17 +597,27 @@
 
                 File target = null;
                 if (TAG_ROOT_PATH.equals(tag)) {
-                    target = buildPath(DEVICE_ROOT, path);
+                    target = DEVICE_ROOT;
                 } else if (TAG_FILES_PATH.equals(tag)) {
-                    target = buildPath(context.getFilesDir(), path);
+                    target = context.getFilesDir();
                 } else if (TAG_CACHE_PATH.equals(tag)) {
-                    target = buildPath(context.getCacheDir(), path);
+                    target = context.getCacheDir();
                 } else if (TAG_EXTERNAL.equals(tag)) {
-                    target = buildPath(Environment.getExternalStorageDirectory(), path);
+                    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];
+                    }
                 }
 
                 if (target != null) {
-                    strat.addRoot(name, target);
+                    strat.addRoot(name, buildPath(target, path));
                 }
             }
         }
diff --git a/v4/tests/AndroidManifest.xml b/v4/tests/AndroidManifest.xml
index 556c885..466bfb8 100644
--- a/v4/tests/AndroidManifest.xml
+++ b/v4/tests/AndroidManifest.xml
@@ -20,10 +20,23 @@
     <uses-sdk android:minSdkVersion="4" tools:overrideLibrary="android.support.test,
             android.support.test.espresso, android.support.test.espresso.idling"/>
 
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.support.v4.widget.test.TextViewTestActivity"/>
         <activity android:name="android.support.v4.widget.TestActivity"/>
+
+        <provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="moocow"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/paths" />
+        </provider>
     </application>
 
     <instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/tests/java/android/support/v4/content/FileProviderTest.java b/v4/tests/java/android/support/v4/content/FileProviderTest.java
similarity index 86%
rename from tests/java/android/support/v4/content/FileProviderTest.java
rename to v4/tests/java/android/support/v4/content/FileProviderTest.java
index f8122fa..c446d85 100644
--- a/tests/java/android/support/v4/content/FileProviderTest.java
+++ b/v4/tests/java/android/support/v4/content/FileProviderTest.java
@@ -26,21 +26,19 @@
 import android.support.v4.content.FileProvider.SimplePathStrategy;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.Suppress;
 
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
 /**
  * Tests for {@link FileProvider}
  */
-@Suppress
 public class FileProviderTest extends AndroidTestCase {
     private static final String TEST_AUTHORITY = "moocow";
 
@@ -111,11 +109,12 @@
         final SimplePathStrategy strat = new SimplePathStrategy("authority");
         strat.addRoot("tag", mContext.getFilesDir());
 
-        File file = buildPath(mContext.getFilesDir(), "file.test");
+        File expectedRoot = mContext.getFilesDir().getCanonicalFile();
+        File file = buildPath(expectedRoot, "file.test");
         assertEquals(file.getPath(),
                 strat.getFileForUri(Uri.parse("content://authority/tag/file.test")).getPath());
 
-        file = buildPath(mContext.getFilesDir(), "subdir", "file.test");
+        file = buildPath(expectedRoot, "subdir", "file.test");
         assertEquals(file.getPath(), strat.getFileForUri(
                 Uri.parse("content://authority/tag/subdir/file.test")).getPath());
     }
@@ -135,7 +134,8 @@
         final SimplePathStrategy strat = new SimplePathStrategy("authority");
         strat.addRoot("t/g", mContext.getFilesDir());
 
-        File file = buildPath(mContext.getFilesDir(), "lol\"wat?foo&bar", "wat.txt");
+        File expectedRoot = mContext.getFilesDir().getCanonicalFile();
+        File file = buildPath(expectedRoot, "lol\"wat?foo&bar", "wat.txt");
         final String expected = "content://authority/t%2Fg/lol%22wat%3Ffoo%26bar/wat.txt";
 
         assertEquals(expected,
@@ -148,7 +148,8 @@
         final SimplePathStrategy strat = new SimplePathStrategy("authority");
         strat.addRoot("tag", mContext.getFilesDir());
 
-        File file = buildPath(mContext.getFilesDir(), "file.txt");
+        File expectedRoot = mContext.getFilesDir().getCanonicalFile();
+        File file = buildPath(expectedRoot, "file.txt");
         assertEquals(file.getPath(), strat.getFileForUri(
                 Uri.parse("content://authority/tag/file.txt?extra=foo")).getPath());
     }
@@ -159,7 +160,8 @@
 
         // When canonicalized, the path separators are trimmed
         File inFile = new File(mContext.getFilesDir(), "//foo//bar//");
-        File outFile = new File(mContext.getFilesDir(), "/foo/bar");
+        File expectedRoot = mContext.getFilesDir().getCanonicalFile();
+        File outFile = new File(expectedRoot, "/foo/bar");
         final String expected = "content://authority/tag/foo/bar";
 
         assertEquals(expected,
@@ -246,7 +248,7 @@
         try {
             out.write(TEST_DATA_ALT);
         } finally {
-            IoUtils.closeQuietly(out);
+            closeQuietly(out);
         }
 
         assertContentsEquals(TEST_DATA_ALT, uri);
@@ -266,7 +268,7 @@
         try {
             out.write(TEST_DATA_ALT);
         } finally {
-            IoUtils.closeQuietly(out);
+            closeQuietly(out);
         }
 
         assertContentsEquals(TEST_DATA_ALT, uri);
@@ -314,14 +316,24 @@
         actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
                 buildPath(Environment.getExternalStorageDirectory(), "Android", "obb", "foobar"));
         assertEquals("content://moocow/test_external/Android/obb/foobar", actual.toString());
+
+        File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(mContext, null);
+        actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
+            buildPath(externalFilesDirs[0], "foo", "bar"));
+        assertEquals("content://moocow/test_external_files/foo/bar", actual.toString());
+
+        File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(mContext);
+        actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
+            buildPath(externalCacheDirs[0], "foo", "bar"));
+        assertEquals("content://moocow/test_external_cache/foo/bar", actual.toString());
     }
 
     private void assertContentsEquals(byte[] expected, Uri actual) throws Exception {
         final InputStream in = mResolver.openInputStream(actual);
         try {
-            MoreAsserts.assertEquals(expected, Streams.readFully(in));
+            MoreAsserts.assertEquals(expected, readFully(in));
         } finally {
-            IoUtils.closeQuietly(in);
+            closeQuietly(in);
         }
     }
 
@@ -350,4 +362,21 @@
         }
         return cur;
     }
+
+    private static void closeQuietly(Closeable c) {
+        try {
+            c.close();
+        } catch (IOException ignored) {
+        }
+    }
+
+    private static byte[] readFully(InputStream is) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buffer = new byte[4096];
+        int read;
+        while ((read = is.read(buffer)) != -1) {
+            out.write(buffer, 0, read);
+        }
+        return out.toByteArray();
+    }
 }
diff --git a/tests/res/xml/paths.xml b/v4/tests/res/xml/paths.xml
similarity index 73%
rename from tests/res/xml/paths.xml
rename to v4/tests/res/xml/paths.xml
index baa2908..7f04099 100644
--- a/tests/res/xml/paths.xml
+++ b/v4/tests/res/xml/paths.xml
@@ -9,6 +9,10 @@
     <files-path name="test_thumbs" path="thumbs/" />
     <!-- /data/data/com.example/cache -->
     <cache-path name="test_cache" />
-    <!-- /storage/emulated/0/Android/com.example/files -->
+    <!-- /storage/emulated/0 -->
     <external-path name="test_external" />
+    <!-- /storage/emulated/0/Android/com.example/files -->
+    <external-files-path name="test_external_files" />
+    <!-- /storage/emulated/0/Android/com.example/cache -->
+    <external-cache-path name="test_external_cache" />
 </paths>