Support versions of new external storage APIs.

Add support library versions of getObbDirs(), getExternalFilesDirs(),
getExternalCacheDirs(), and Environment.getStorageState().

Bug: 11287667
Change-Id: I78db3800a3fbcd65b6ac7c3ee8c297d535a3b59c
diff --git a/v4/froyo/android/support/v4/content/ContextCompatFroyo.java b/v4/froyo/android/support/v4/content/ContextCompatFroyo.java
new file mode 100644
index 0000000..1fc2f8d
--- /dev/null
+++ b/v4/froyo/android/support/v4/content/ContextCompatFroyo.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content;
+
+import android.content.Context;
+
+import java.io.File;
+
+class ContextCompatFroyo {
+    public static File getExternalCacheDir(Context context) {
+        return context.getExternalCacheDir();
+    }
+
+    public static File getExternalFilesDir(Context context, String type) {
+        return context.getExternalFilesDir(type);
+    }
+}
diff --git a/v4/honeycomb/android/support/v4/content/ContextCompatHoneycomb.java b/v4/honeycomb/android/support/v4/content/ContextCompatHoneycomb.java
index 736e6f2..9abc97d 100644
--- a/v4/honeycomb/android/support/v4/content/ContextCompatHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/content/ContextCompatHoneycomb.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.content.Intent;
 
+import java.io.File;
+
 /**
  * Implementation of context compatibility that can call Honeycomb APIs.
  */
@@ -27,4 +29,8 @@
     static void startActivities(Context context, Intent[] intents) {
         context.startActivities(intents);
     }
+
+    public static File getObbDir(Context context) {
+        return context.getObbDir();
+    }
 }
diff --git a/v4/java/android/support/v4/content/ContextCompat.java b/v4/java/android/support/v4/content/ContextCompat.java
index 6acf97e..8fbf4bb 100644
--- a/v4/java/android/support/v4/content/ContextCompat.java
+++ b/v4/java/android/support/v4/content/ContextCompat.java
@@ -16,11 +16,15 @@
 
 package android.support.v4.content;
 
-import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Environment;
+import android.os.StatFs;
+import android.support.v4.os.EnvironmentCompat;
+
+import java.io.File;
 
 /**
  * Helper for accessing features in {@link android.content.Context}
@@ -28,6 +32,12 @@
  */
 public class ContextCompat {
 
+    private static final String DIR_ANDROID = "Android";
+    private static final String DIR_DATA = "data";
+    private static final String DIR_OBB = "obb";
+    private static final String DIR_FILES = "files";
+    private static final String DIR_CACHE = "cache";
+
     /**
      * Start a set of activities as a synthesized task stack, if able.
      *
@@ -96,4 +106,195 @@
         }
         return false;
     }
+
+    /**
+     * Returns absolute paths to application-specific directories on all
+     * external storage devices where the application's OBB files (if there are
+     * any) can be found. Note if the application does not have any OBB files,
+     * these directories may not exist.
+     * <p>
+     * This is like {@link Context#getFilesDir()} in that these files will be
+     * deleted when the application is uninstalled, however there are some
+     * important differences:
+     * <ul>
+     * <li>External files are not always available: they will disappear if the
+     * user mounts the external storage on a computer or removes it.
+     * <li>There is no security enforced with these files.
+     * </ul>
+     * <p>
+     * External storage devices returned here are considered a permanent part of
+     * the device, including both emulated external storage and physical media
+     * slots, such as SD cards in a battery compartment. The returned paths do
+     * not include transient devices, such as USB flash drives.
+     * <p>
+     * An application may store data on any or all of the returned devices. For
+     * example, an app may choose to store large files on the device with the
+     * most available space, as measured by {@link StatFs}.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+     * are required to write to the returned paths; they're always accessible to
+     * the calling app. Before then,
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
+     * write. Write access outside of these paths on secondary external storage
+     * devices is not available. To request external storage access in a
+     * backwards compatible way, consider using {@code android:maxSdkVersion}
+     * like this:
+     *
+     * <pre class="prettyprint">&lt;uses-permission
+     *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+     *     android:maxSdkVersion="18" /&gt;</pre>
+     * <p>
+     * The first path returned is the same as {@link Context#getObbDir()}.
+     * Returned paths may be {@code null} if a storage device is unavailable.
+     *
+     * @see Context#getObbDir()
+     * @see EnvironmentCompat#getStorageState(File)
+     */
+    public static File[] getObbDirs(Context context) {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 19) {
+            return ContextCompatKitKat.getObbDirs(context);
+        } else {
+            final File single;
+            if (version >= 11) {
+                single = ContextCompatHoneycomb.getObbDir(context);
+            } else {
+                single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_OBB,
+                        context.getPackageName());
+            }
+            return new File[] { single };
+        }
+    }
+
+    /**
+     * Returns absolute paths to application-specific directories on all
+     * external storage devices where the application can place persistent files
+     * it owns. These files are internal to the application, and not typically
+     * visible to the user as media.
+     * <p>
+     * This is like {@link Context#getFilesDir()} in that these files will be
+     * deleted when the application is uninstalled, however there are some
+     * important differences:
+     * <ul>
+     * <li>External files are not always available: they will disappear if the
+     * user mounts the external storage on a computer or removes it.
+     * <li>There is no security enforced with these files.
+     * </ul>
+     * <p>
+     * External storage devices returned here are considered a permanent part of
+     * the device, including both emulated external storage and physical media
+     * slots, such as SD cards in a battery compartment. The returned paths do
+     * not include transient devices, such as USB flash drives.
+     * <p>
+     * An application may store data on any or all of the returned devices. For
+     * example, an app may choose to store large files on the device with the
+     * most available space, as measured by {@link StatFs}.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+     * are required to write to the returned paths; they're always accessible to
+     * the calling app. Before then,
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
+     * write. Write access outside of these paths on secondary external storage
+     * devices is not available. To request external storage access in a
+     * backwards compatible way, consider using {@code android:maxSdkVersion}
+     * like this:
+     *
+     * <pre class="prettyprint">&lt;uses-permission
+     *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+     *     android:maxSdkVersion="18" /&gt;</pre>
+     * <p>
+     * The first path returned is the same as
+     * {@link Context#getExternalFilesDir(String)}. Returned paths may be
+     * {@code null} if a storage device is unavailable.
+     *
+     * @see Context#getExternalFilesDir(String)
+     * @see EnvironmentCompat#getStorageState(File)
+     */
+    public static File[] getExternalFilesDirs(Context context, String type) {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 19) {
+            return ContextCompatKitKat.getExternalFilesDirs(context, type);
+        } else {
+            final File single;
+            if (version >= 8) {
+                single = ContextCompatFroyo.getExternalFilesDir(context, type);
+            } else {
+                single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_DATA,
+                        context.getPackageName(), DIR_FILES, type);
+            }
+            return new File[] { single };
+        }
+    }
+
+    /**
+     * Returns absolute paths to application-specific directories on all
+     * external storage devices where the application can place cache files it
+     * owns. These files are internal to the application, and not typically
+     * visible to the user as media.
+     * <p>
+     * This is like {@link Context#getCacheDir()} in that these files will be
+     * deleted when the application is uninstalled, however there are some
+     * important differences:
+     * <ul>
+     * <li>External files are not always available: they will disappear if the
+     * user mounts the external storage on a computer or removes it.
+     * <li>There is no security enforced with these files.
+     * </ul>
+     * <p>
+     * External storage devices returned here are considered a permanent part of
+     * the device, including both emulated external storage and physical media
+     * slots, such as SD cards in a battery compartment. The returned paths do
+     * not include transient devices, such as USB flash drives.
+     * <p>
+     * An application may store data on any or all of the returned devices. For
+     * example, an app may choose to store large files on the device with the
+     * most available space, as measured by {@link StatFs}.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+     * are required to write to the returned paths; they're always accessible to
+     * the calling app. Before then,
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
+     * write. Write access outside of these paths on secondary external storage
+     * devices is not available. To request external storage access in a
+     * backwards compatible way, consider using {@code android:maxSdkVersion}
+     * like this:
+     *
+     * <pre class="prettyprint">&lt;uses-permission
+     *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+     *     android:maxSdkVersion="18" /&gt;</pre>
+     * <p>
+     * The first path returned is the same as
+     * {@link Context#getExternalCacheDir()}. Returned paths may be {@code null}
+     * if a storage device is unavailable.
+     *
+     * @see Context#getExternalCacheDir()
+     * @see EnvironmentCompat#getStorageState(File)
+     */
+    public static File[] getExternalCacheDirs(Context context) {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 19) {
+            return ContextCompatKitKat.getExternalCacheDirs(context);
+        } else {
+            final File single;
+            if (version >= 8) {
+                single = ContextCompatFroyo.getExternalCacheDir(context);
+            } else {
+                single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_DATA,
+                        context.getPackageName(), DIR_CACHE);
+            }
+            return new File[] { single };
+        }
+    }
+
+    private static File buildPath(File base, String... segments) {
+        File cur = base;
+        for (String segment : segments) {
+            if (cur == null) {
+                cur = new File(segment);
+            } else if (segment != null) {
+                cur = new File(cur, segment);
+            }
+        }
+        return cur;
+    }
 }
diff --git a/v4/java/android/support/v4/os/EnvironmentCompat.java b/v4/java/android/support/v4/os/EnvironmentCompat.java
new file mode 100644
index 0000000..66a2a8e
--- /dev/null
+++ b/v4/java/android/support/v4/os/EnvironmentCompat.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.os;
+
+import android.os.Build;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Helper for accessing features in {@link Environment} introduced after API
+ * level 4 in a backwards compatible fashion.
+ */
+public class EnvironmentCompat {
+    private static final String TAG = "EnvironmentCompat";
+
+    /**
+     * Unknown storage state, such as when a path isn't backed by known storage
+     * media.
+     *
+     * @see #getStorageState(File)
+     */
+    public static final String MEDIA_UNKNOWN = "unknown";
+
+    /**
+     * Returns the current state of the storage device that provides the given
+     * path.
+     *
+     * @return one of {@link #MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
+     *         {@link Environment#MEDIA_UNMOUNTED},
+     *         {@link Environment#MEDIA_CHECKING},
+     *         {@link Environment#MEDIA_NOFS},
+     *         {@link Environment#MEDIA_MOUNTED},
+     *         {@link Environment#MEDIA_MOUNTED_READ_ONLY},
+     *         {@link Environment#MEDIA_SHARED},
+     *         {@link Environment#MEDIA_BAD_REMOVAL}, or
+     *         {@link Environment#MEDIA_UNMOUNTABLE}.
+     */
+    public static String getStorageState(File path) {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 19) {
+            return EnvironmentCompatKitKat.getStorageState(path);
+        }
+
+        try {
+            final String canonicalPath = path.getCanonicalPath();
+            final String canonicalExternal = Environment.getExternalStorageDirectory()
+                    .getCanonicalPath();
+
+            if (canonicalPath.startsWith(canonicalExternal)) {
+                return Environment.getExternalStorageState();
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to resolve canonical path: " + e);
+        }
+
+        return MEDIA_UNKNOWN;
+    }
+}
diff --git a/v4/kitkat/android/support/v4/content/ContextCompatKitKat.java b/v4/kitkat/android/support/v4/content/ContextCompatKitKat.java
new file mode 100644
index 0000000..6b58827
--- /dev/null
+++ b/v4/kitkat/android/support/v4/content/ContextCompatKitKat.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content;
+
+import android.content.Context;
+
+import java.io.File;
+
+class ContextCompatKitKat {
+    public static File[] getExternalCacheDirs(Context context) {
+        return context.getExternalCacheDirs();
+    }
+
+    public static File[] getExternalFilesDirs(Context context, String type) {
+        return context.getExternalFilesDirs(type);
+    }
+
+    public static File[] getObbDirs(Context context) {
+        return context.getObbDirs();
+    }
+}
diff --git a/v4/kitkat/android/support/v4/os/EnvironmentCompatKitKat.java b/v4/kitkat/android/support/v4/os/EnvironmentCompatKitKat.java
new file mode 100644
index 0000000..9a803db
--- /dev/null
+++ b/v4/kitkat/android/support/v4/os/EnvironmentCompatKitKat.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.os;
+
+import android.os.Environment;
+
+import java.io.File;
+
+class EnvironmentCompatKitKat {
+    public static String getStorageState(File path) {
+        return Environment.getStorageState(path);
+    }
+}