Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1abdb712 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

APIs for multiple external storage devices.

Provide developer APIs to discover application-specific paths on
secondary external storage devices.  Covers files, cache, and OBB
directories.  Apps will not have write access outside their package-
specific directories on secondary devices, so only primary storage is
exposed through Environment.

Creation of .nomedia files will be handled by FUSE daemon in future
change.

Change-Id: Ifcce6201a686d80269d7285adb597c008cf8fa7c
parent 09335703
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -5718,11 +5718,14 @@ package android.content {
    method public abstract java.io.File getDatabasePath(java.lang.String);
    method public abstract java.io.File getDir(java.lang.String, int);
    method public abstract java.io.File getExternalCacheDir();
    method public abstract java.io.File[] getExternalCacheDirs();
    method public abstract java.io.File getExternalFilesDir(java.lang.String);
    method public abstract java.io.File[] getExternalFilesDirs(java.lang.String);
    method public abstract java.io.File getFileStreamPath(java.lang.String);
    method public abstract java.io.File getFilesDir();
    method public abstract android.os.Looper getMainLooper();
    method public abstract java.io.File getObbDir();
    method public abstract java.io.File[] getObbDirs();
    method public abstract java.lang.String getPackageCodePath();
    method public abstract android.content.pm.PackageManager getPackageManager();
    method public abstract java.lang.String getPackageName();
@@ -5873,11 +5876,14 @@ package android.content {
    method public java.io.File getDatabasePath(java.lang.String);
    method public java.io.File getDir(java.lang.String, int);
    method public java.io.File getExternalCacheDir();
    method public java.io.File[] getExternalCacheDirs();
    method public java.io.File getExternalFilesDir(java.lang.String);
    method public java.io.File[] getExternalFilesDirs(java.lang.String);
    method public java.io.File getFileStreamPath(java.lang.String);
    method public java.io.File getFilesDir();
    method public android.os.Looper getMainLooper();
    method public java.io.File getObbDir();
    method public java.io.File[] getObbDirs();
    method public java.lang.String getPackageCodePath();
    method public android.content.pm.PackageManager getPackageManager();
    method public java.lang.String getPackageName();
@@ -17530,6 +17536,7 @@ package android.os {
    method public static java.io.File getExternalStoragePublicDirectory(java.lang.String);
    method public static java.lang.String getExternalStorageState();
    method public static java.io.File getRootDirectory();
    method public static java.lang.String getStorageState(java.io.File);
    method public static boolean isExternalStorageEmulated();
    method public static boolean isExternalStorageRemovable();
    field public static java.lang.String DIRECTORY_ALARMS;
@@ -17548,6 +17555,7 @@ package android.os {
    field public static final java.lang.String MEDIA_NOFS = "nofs";
    field public static final java.lang.String MEDIA_REMOVED = "removed";
    field public static final java.lang.String MEDIA_SHARED = "shared";
    field public static final java.lang.String MEDIA_UNKNOWN = "unknown";
    field public static final java.lang.String MEDIA_UNMOUNTABLE = "unmountable";
    field public static final java.lang.String MEDIA_UNMOUNTED = "unmounted";
  }
+67 −46
Original line number Diff line number Diff line
@@ -111,6 +111,7 @@ import android.accounts.AccountManager;
import android.accounts.IAccountManager;
import android.app.admin.DevicePolicyManager;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.os.IDropBoxManagerService;

@@ -197,13 +198,21 @@ class ContextImpl extends Context {

    private final Object mSync = new Object();

    @GuardedBy("mSync")
    private File mDatabasesDir;
    @GuardedBy("mSync")
    private File mPreferencesDir;
    @GuardedBy("mSync")
    private File mFilesDir;
    @GuardedBy("mSync")
    private File mCacheDir;
    private File mObbDir;
    private File mExternalFilesDir;
    private File mExternalCacheDir;

    @GuardedBy("mSync")
    private File[] mExternalObbDirs;
    @GuardedBy("mSync")
    private File[] mExternalFilesDirs;
    @GuardedBy("mSync")
    private File[] mExternalCacheDirs;

    private static final String[] EMPTY_FILE_LIST = {};

@@ -802,44 +811,41 @@ class ContextImpl extends Context {

    @Override
    public File getExternalFilesDir(String type) {
        synchronized (mSync) {
            if (mExternalFilesDir == null) {
                mExternalFilesDir = Environment.getExternalStorageAppFilesDirectory(
                        getPackageName());
            }
            if (!mExternalFilesDir.exists()) {
                try {
                    (new File(Environment.getExternalStorageAndroidDataDir(),
                            ".nomedia")).createNewFile();
                } catch (IOException e) {
                }
                if (!mExternalFilesDir.mkdirs()) {
                    Log.w(TAG, "Unable to create external files directory");
                    return null;
                }
            }
            if (type == null) {
                return mExternalFilesDir;
        // Operates on primary external storage
        return getExternalFilesDirs(type)[0];
    }
            File dir = new File(mExternalFilesDir, type);
            if (!dir.exists()) {
                if (!dir.mkdirs()) {
                    Log.w(TAG, "Unable to create external media directory " + dir);
                    return null;

    @Override
    public File[] getExternalFilesDirs(String type) {
        synchronized (mSync) {
            if (mExternalFilesDirs == null) {
                mExternalFilesDirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
            }

            // Splice in requested type, if any
            File[] dirs = mExternalFilesDirs;
            if (type != null) {
                dirs = Environment.buildPaths(dirs, type);
            }
            return dir;

            // Create dirs if needed
            return ensureDirsExistOrFilter(dirs);
        }
    }

    @Override
    public File getObbDir() {
        // Operates on primary external storage
        return getObbDirs()[0];
    }

    @Override
    public File[] getObbDirs() {
        synchronized (mSync) {
            if (mObbDir == null) {
                mObbDir = Environment.getExternalStorageAppObbDirectory(
                        getPackageName());
            if (mExternalObbDirs == null) {
                mExternalObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
            }
            return mObbDir;
            return mExternalObbDirs;
        }
    }

@@ -865,23 +871,19 @@ class ContextImpl extends Context {

    @Override
    public File getExternalCacheDir() {
        synchronized (mSync) {
            if (mExternalCacheDir == null) {
                mExternalCacheDir = Environment.getExternalStorageAppCacheDirectory(
                        getPackageName());
            }
            if (!mExternalCacheDir.exists()) {
                try {
                    (new File(Environment.getExternalStorageAndroidDataDir(),
                            ".nomedia")).createNewFile();
                } catch (IOException e) {
                }
                if (!mExternalCacheDir.mkdirs()) {
                    Log.w(TAG, "Unable to create external cache directory");
                    return null;
        // Operates on primary external storage
        return getExternalCacheDirs()[0];
    }

    @Override
    public File[] getExternalCacheDirs() {
        synchronized (mSync) {
            if (mExternalCacheDirs == null) {
                mExternalCacheDirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
            }
            return mExternalCacheDir;

            // Create dirs if needed
            return ensureDirsExistOrFilter(mExternalCacheDirs);
        }
    }

@@ -2081,6 +2083,25 @@ class ContextImpl extends Context {
                "File " + name + " contains a path separator");
    }

    /**
     * Ensure that given directories exist, trying to create them if missing. If
     * unable to create, they are filtered by replacing with {@code null}.
     */
    private static File[] ensureDirsExistOrFilter(File[] dirs) {
        File[] result = new File[dirs.length];
        for (int i = 0; i < dirs.length; i++) {
            File dir = dirs[i];
            if (!dir.exists()) {
                if (!dir.mkdirs()) {
                    Log.w(TAG, "Failed to ensure directory: " + dir);
                    dir = null;
                }
            }
            result[i] = dir;
        }
        return result;
    }

    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
+4 −5
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import android.content.Context;
@@ -23,17 +24,15 @@ import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.AttributeSet;
import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.WindowManager;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;

import java.io.File;
@@ -176,8 +175,8 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
                ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;

        mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(),
                 getFilesDir().toString(), getObbDir().toString(),
                 Environment.getExternalStorageAppFilesDirectory(ai.packageName).toString(),
                 getFilesDir().getAbsolutePath(), getObbDir().getAbsolutePath(),
                 getExternalFilesDir(null).getAbsolutePath(),
                 Build.VERSION.SDK_INT, getAssets(), nativeSavedState);
        
        if (mNativeHandle == 0) {
+89 −14
Original line number Diff line number Diff line
@@ -590,7 +590,7 @@ public abstract class Context {
     * Returns the absolute path to the directory on the external filesystem
     * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
     * Environment.getExternalStorageDirectory()}) where the application can
     * place persistent files it owns.  These files are private to the
     * place persistent files it owns.  These files are internal to the
     * applications, and not typically visible to the user as media.
     *
     * <p>This is like {@link #getFilesDir()} in that these
@@ -637,9 +637,11 @@ public abstract class Context {
     *
     * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
     * private_picture}
     *
     * <p>Writing to this path requires the
     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission.</p>
     * <p>
     * Starting in {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, no
     * permissions are required for the owning application to read or write to
     * this path. Otherwise, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
     * or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
     *
     * @param type The type of files directory to return.  May be null for
     * the root of the files directory or one of
@@ -663,17 +665,65 @@ public abstract class Context {
    public abstract File getExternalFilesDir(String type);

    /**
     * Return the directory where this application's OBB files (if there
     * are any) can be found.  Note if the application does not have any OBB
     * files, this directory may not exist.
     * 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>
     * External storage devices returned here are considered a permanent part of
     * the device, including both emulated external storage and physical media
     * slots. This does not include transient devices, such as USB flash drives.
     * <p>
     * Starting in {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, no
     * permissions are required for the owning application to read or write to
     * these paths.
     * <p>
     * The returned paths include any path that would be returned by
     * {@link #getExternalFilesDir(String)}.
     *
     * <p>On devices with multiple users (as described by {@link UserManager}),
     * @see #getExternalFilesDir(String)
     */
    public abstract File[] getExternalFilesDirs(String type);

    /**
     * Return the directory where this application's OBB files (if there are
     * any) can be found. Note if the application does not have any OBB files,
     * this directory may not exist.
     * <p>
     * On devices with multiple users (as described by {@link UserManager}),
     * multiple users may share the same OBB storage location. Applications
     * should ensure that multiple instances running under different users
     * don't interfere with each other.</p>
     * should ensure that multiple instances running under different users don't
     * interfere with each other.
     * <p>
     * Starting in {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, no
     * permissions are required for the owning application to read or write to
     * this path. Otherwise,
     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} or
     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
     */
    public abstract File getObbDir();

    /**
     * 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>
     * External storage devices returned here are considered a permanent part of
     * the device, including both emulated external storage and physical media
     * slots. This does not include transient devices, such as USB flash drives.
     * <p>
     * Starting in {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, no
     * permissions are required for the owning application to read or write to
     * this path.
     * <p>
     * The returned paths include any path that would be returned by
     * {@link #getObbDir()}
     *
     * @see #getObbDir()
     */
    public abstract File[] getObbDirs();

    /**
     * Returns the absolute path to the application specific cache directory
     * on the filesystem. These files will be ones that get deleted first when the
@@ -697,7 +747,8 @@ public abstract class Context {
     * Returns the absolute path to the directory on the external filesystem
     * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
     * Environment.getExternalStorageDirectory()} where the application can
     * place cache files it owns.
     * 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 #getCacheDir()} in that these
     * files will be deleted when the application is uninstalled, however there
@@ -722,9 +773,12 @@ public abstract class Context {
     * <p>On devices with multiple users (as described by {@link UserManager}),
     * each user has their own isolated external storage. Applications only
     * have access to the external storage for the user they're running as.</p>
     *
     * <p>Writing to this path requires the
     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission.</p>
     * <p>
     * Starting in {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, no
     * permissions are required for the owning application to read or write to
     * this path. Otherwise,
     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} or
     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
     *
     * @return The path of the directory holding application cache files
     * on external storage.  Returns null if external storage is not currently
@@ -735,6 +789,27 @@ public abstract class Context {
     */
    public abstract File getExternalCacheDir();

    /**
     * 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>
     * External storage devices returned here are considered a permanent part of
     * the device, including both emulated external storage and physical media
     * slots. This does not include transient devices, such as USB flash drives.
     * <p>
     * Starting in {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, no
     * permissions are required for the owning application to read or write to
     * these paths.
     * <p>
     * The returned paths include any path that would be returned by
     * {@link #getExternalCacheDir()}.
     *
     * @see #getExternalCacheDir()
     */
    public abstract File[] getExternalCacheDirs();

    /**
     * Returns an array of strings naming the private files associated with
     * this Context's application package.
+17 −2
Original line number Diff line number Diff line
@@ -204,11 +204,21 @@ public class ContextWrapper extends Context {
        return mBase.getExternalFilesDir(type);
    }

    @Override
    public File[] getExternalFilesDirs(String type) {
        return mBase.getExternalFilesDirs(type);
    }

    @Override
    public File getObbDir() {
        return mBase.getObbDir();
    }

    @Override
    public File[] getObbDirs() {
        return mBase.getObbDirs();
    }

    @Override
    public File getCacheDir() {
        return mBase.getCacheDir();
@@ -219,6 +229,11 @@ public class ContextWrapper extends Context {
        return mBase.getExternalCacheDir();
    }

    @Override
    public File[] getExternalCacheDirs() {
        return mBase.getExternalCacheDirs();
    }

    @Override
    public File getDir(String name, int mode) {
        return mBase.getDir(name, mode);
Loading