Merge "APIs for multiple external storage devices." into klp-dev
diff --git a/api/current.txt b/api/current.txt
index 6fb0815..ed893a2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5718,11 +5718,14 @@
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 @@
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 @@
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 @@
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";
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index eb5c3e7..cdec399 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -111,6 +111,7 @@
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 @@
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 @@
@Override
public File getExternalFilesDir(String type) {
+ // Operates on primary external storage
+ return getExternalFilesDirs(type)[0];
+ }
+
+ @Override
+ public File[] getExternalFilesDirs(String type) {
synchronized (mSync) {
- if (mExternalFilesDir == null) {
- mExternalFilesDir = Environment.getExternalStorageAppFilesDirectory(
- getPackageName());
+ if (mExternalFilesDirs == null) {
+ mExternalFilesDirs = Environment.buildExternalStorageAppFilesDirs(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;
- }
+
+ // Splice in requested type, if any
+ File[] dirs = mExternalFilesDirs;
+ if (type != null) {
+ dirs = Environment.buildPaths(dirs, type);
}
- if (type == null) {
- return mExternalFilesDir;
- }
- File dir = new File(mExternalFilesDir, type);
- if (!dir.exists()) {
- if (!dir.mkdirs()) {
- Log.w(TAG, "Unable to create external media directory " + dir);
- return null;
- }
- }
- 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 @@
@Override
public File getExternalCacheDir() {
+ // Operates on primary external storage
+ return getExternalCacheDirs()[0];
+ }
+
+ @Override
+ public File[] getExternalCacheDirs() {
synchronized (mSync) {
- if (mExternalCacheDir == null) {
- mExternalCacheDir = Environment.getExternalStorageAppCacheDirectory(
- getPackageName());
+ if (mExternalCacheDirs == null) {
+ mExternalCacheDirs = Environment.buildExternalStorageAppCacheDirs(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;
- }
- }
- return mExternalCacheDir;
+
+ // Create dirs if needed
+ return ensureDirsExistOrFilter(mExternalCacheDirs);
}
}
@@ -2081,6 +2083,25 @@
"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;
+ }
+
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 63c6acd..b84889f 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -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.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 @@
? 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) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 81a5e57..ff81f72 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -590,7 +590,7 @@
* 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 @@
*
* {@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,18 +665,66 @@
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
* device runs low on storage.
@@ -697,7 +747,8 @@
* 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 @@
* <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
@@ -736,6 +790,27 @@
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.
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 606a1f4..e09d367 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -203,12 +203,22 @@
public File getExternalFilesDir(String type) {
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();
@@ -220,6 +230,11 @@
}
@Override
+ public File[] getExternalCacheDirs() {
+ return mBase.getExternalCacheDirs();
+ }
+
+ @Override
public File getDir(String name, int mode) {
return mBase.getDir(name, mode);
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 70a1edc..3b9456f 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -16,6 +16,7 @@
package android.os;
+import android.content.Context;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
@@ -41,7 +42,16 @@
private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
/** {@hide} */
- public static String DIRECTORY_ANDROID = "Android";
+ public static final String DIR_ANDROID = "Android";
+ private static final String DIR_DATA = "data";
+ private static final String DIR_MEDIA = "media";
+ private static final String DIR_OBB = "obb";
+ private static final String DIR_FILES = "files";
+ private static final String DIR_CACHE = "cache";
+
+ /** {@hide} */
+ @Deprecated
+ public static final String DIRECTORY_ANDROID = DIR_ANDROID;
private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");
@@ -98,12 +108,10 @@
/** {@hide} */
public static class UserEnvironment {
// TODO: generalize further to create package-specific environment
+ // TODO: add support for secondary external storage
- private final File mExternalStorage;
- private final File mExternalStorageAndroidData;
- private final File mExternalStorageAndroidMedia;
- private final File mExternalStorageAndroidObb;
- private final File mMediaStorage;
+ private final File[] mExternalDirs;
+ private final File mMediaDir;
public UserEnvironment(int userId) {
// See storage config details at http://source.android.com/tech/storage/
@@ -122,9 +130,9 @@
final File mediaBase = new File(rawMediaStorage);
// /storage/emulated/0
- mExternalStorage = buildPath(emulatedBase, rawUserId);
+ mExternalDirs = new File[] { buildPath(emulatedBase, rawUserId) };
// /data/media/0
- mMediaStorage = buildPath(mediaBase, rawUserId);
+ mMediaDir = buildPath(mediaBase, rawUserId);
} else {
// Device has physical external storage; use plain paths.
@@ -134,54 +142,60 @@
}
// /storage/sdcard0
- mExternalStorage = new File(rawExternalStorage);
+ mExternalDirs = new File[] { new File(rawExternalStorage) };
// /data/media
- mMediaStorage = new File(rawMediaStorage);
+ mMediaDir = new File(rawMediaStorage);
}
-
- mExternalStorageAndroidObb = buildPath(mExternalStorage, DIRECTORY_ANDROID, "obb");
- mExternalStorageAndroidData = buildPath(mExternalStorage, DIRECTORY_ANDROID, "data");
- mExternalStorageAndroidMedia = buildPath(mExternalStorage, DIRECTORY_ANDROID, "media");
}
+ @Deprecated
public File getExternalStorageDirectory() {
- return mExternalStorage;
+ return mExternalDirs[0];
}
- public File getExternalStorageObbDirectory() {
- return mExternalStorageAndroidObb;
- }
-
+ @Deprecated
public File getExternalStoragePublicDirectory(String type) {
- return new File(mExternalStorage, type);
+ return buildExternalStoragePublicDirs(type)[0];
}
- public File getExternalStorageAndroidDataDir() {
- return mExternalStorageAndroidData;
+ public File[] getExternalDirs() {
+ return mExternalDirs;
}
- public File getExternalStorageAppDataDirectory(String packageName) {
- return new File(mExternalStorageAndroidData, packageName);
+ public File getMediaDir() {
+ return mMediaDir;
}
- public File getExternalStorageAppMediaDirectory(String packageName) {
- return new File(mExternalStorageAndroidMedia, packageName);
+ public File[] buildExternalStoragePublicDirs(String type) {
+ return buildPaths(mExternalDirs, type);
}
- public File getExternalStorageAppObbDirectory(String packageName) {
- return new File(mExternalStorageAndroidObb, packageName);
+ public File[] buildExternalStorageAndroidDataDirs() {
+ return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA);
}
- public File getExternalStorageAppFilesDirectory(String packageName) {
- return new File(new File(mExternalStorageAndroidData, packageName), "files");
+ public File[] buildExternalStorageAndroidObbDirs() {
+ return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB);
}
- public File getExternalStorageAppCacheDirectory(String packageName) {
- return new File(new File(mExternalStorageAndroidData, packageName), "cache");
+ public File[] buildExternalStorageAppDataDirs(String packageName) {
+ return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName);
}
- public File getMediaStorageDirectory() {
- return mMediaStorage;
+ public File[] buildExternalStorageAppMediaDirs(String packageName) {
+ return buildPaths(mExternalDirs, DIR_ANDROID, DIR_MEDIA, packageName);
+ }
+
+ public File[] buildExternalStorageAppObbDirs(String packageName) {
+ return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB, packageName);
+ }
+
+ public File[] buildExternalStorageAppFilesDirs(String packageName) {
+ return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
+ }
+
+ public File[] buildExternalStorageAppCacheDirs(String packageName) {
+ return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
}
}
@@ -230,7 +244,7 @@
*/
public static File getMediaStorageDirectory() {
throwIfUserRequired();
- return sCurrentUser.getMediaStorageDirectory();
+ return sCurrentUser.getMediaDir();
}
/**
@@ -266,58 +280,64 @@
private static final File DOWNLOAD_CACHE_DIRECTORY = getDirectory("DOWNLOAD_CACHE", "/cache");
/**
- * Gets the Android data directory.
+ * Return the user data directory.
*/
public static File getDataDirectory() {
return DATA_DIRECTORY;
}
/**
- * Gets the Android external storage directory. This directory may not
+ * Return the primary external storage directory. This directory may not
* currently be accessible if it has been mounted by the user on their
* computer, has been removed from the device, or some other problem has
- * happened. You can determine its current state with
+ * happened. You can determine its current state with
* {@link #getExternalStorageState()}.
- *
- * <p><em>Note: don't be confused by the word "external" here. This
- * directory can better be thought as media/shared storage. It is a
- * filesystem that can hold a relatively large amount of data and that
- * is shared across all applications (does not enforce permissions).
- * Traditionally this is an SD card, but it may also be implemented as
- * built-in storage in a device that is distinct from the protected
- * internal storage and can be mounted as a filesystem on a computer.</em></p>
- *
- * <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>In devices with multiple "external" storage directories (such as
- * both secure app storage and mountable shared storage), this directory
+ * <p>
+ * <em>Note: don't be confused by the word "external" here. This directory
+ * can better be thought as media/shared storage. It is a filesystem that
+ * can hold a relatively large amount of data and that is shared across all
+ * applications (does not enforce permissions). Traditionally this is an SD
+ * card, but it may also be implemented as built-in storage in a device that
+ * is distinct from the protected internal storage and can be mounted as a
+ * filesystem on a computer.</em>
+ * <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>
+ * In devices with multiple "external" storage directories, this directory
* represents the "primary" external storage that the user will interact
- * with.</p>
- *
- * <p>Applications should not directly use this top-level directory, in
- * order to avoid polluting the user's root namespace. Any files that are
- * private to the application should be placed in a directory returned
- * by {@link android.content.Context#getExternalFilesDir
+ * with. Access to secondary storage is available through
+ * <p>
+ * Applications should not directly use this top-level directory, in order
+ * to avoid polluting the user's root namespace. Any files that are private
+ * to the application should be placed in a directory returned by
+ * {@link android.content.Context#getExternalFilesDir
* Context.getExternalFilesDir}, which the system will take care of deleting
- * if the application is uninstalled. Other shared files should be placed
- * in one of the directories returned by
- * {@link #getExternalStoragePublicDirectory}.</p>
- *
- * <p>Writing to this path requires the
- * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission. In
- * a future platform release, access to this path will require the
+ * if the application is uninstalled. Other shared files should be placed in
+ * one of the directories returned by
+ * {@link #getExternalStoragePublicDirectory}.
+ * <p>
+ * Writing to this path requires the
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission,
+ * and starting in read access requires the
* {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission,
- * which is automatically granted if you hold the write permission.</p>
- *
- * <p>This path may change between platform versions, so applications
- * should only persist relative paths.</p>
- *
- * <p>Here is an example of typical code to monitor the state of
- * external storage:</p>
- *
- * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+ * which is automatically granted if you hold the write permission.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, if your
+ * application only needs to store internal data, consider using
+ * {@link Context#getExternalFilesDir(String)} or
+ * {@link Context#getExternalCacheDir()}, which require no permissions to
+ * read or write.
+ * <p>
+ * This path may change between platform versions, so applications should
+ * only persist relative paths.
+ * <p>
+ * Here is an example of typical code to monitor the state of external
+ * storage:
+ * <p>
+ * {@sample
+ * development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* monitor_storage}
*
* @see #getExternalStorageState()
@@ -325,7 +345,7 @@
*/
public static File getExternalStorageDirectory() {
throwIfUserRequired();
- return sCurrentUser.getExternalStorageDirectory();
+ return sCurrentUser.getExternalDirs()[0];
}
/** {@hide} */
@@ -335,7 +355,7 @@
/** {@hide} */
public static File getLegacyExternalStorageObbDirectory() {
- return buildPath(getLegacyExternalStorageDirectory(), DIRECTORY_ANDROID, "obb");
+ return buildPath(getLegacyExternalStorageDirectory(), DIR_ANDROID, DIR_OBB);
}
/** {@hide} */
@@ -347,7 +367,7 @@
/** {@hide} */
public static File getEmulatedStorageObbSource() {
// /mnt/shell/emulated/obb
- return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), "obb");
+ return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), DIR_OBB);
}
/**
@@ -472,139 +492,192 @@
*/
public static File getExternalStoragePublicDirectory(String type) {
throwIfUserRequired();
- return sCurrentUser.getExternalStoragePublicDirectory(type);
+ return sCurrentUser.buildExternalStoragePublicDirs(type)[0];
}
/**
* Returns the path for android-specific data on the SD card.
* @hide
*/
- public static File getExternalStorageAndroidDataDir() {
+ public static File[] buildExternalStorageAndroidDataDirs() {
throwIfUserRequired();
- return sCurrentUser.getExternalStorageAndroidDataDir();
+ return sCurrentUser.buildExternalStorageAndroidDataDirs();
}
-
+
/**
* Generates the raw path to an application's data
* @hide
*/
- public static File getExternalStorageAppDataDirectory(String packageName) {
+ public static File[] buildExternalStorageAppDataDirs(String packageName) {
throwIfUserRequired();
- return sCurrentUser.getExternalStorageAppDataDirectory(packageName);
+ return sCurrentUser.buildExternalStorageAppDataDirs(packageName);
}
/**
* Generates the raw path to an application's media
* @hide
*/
- public static File getExternalStorageAppMediaDirectory(String packageName) {
+ public static File[] buildExternalStorageAppMediaDirs(String packageName) {
throwIfUserRequired();
- return sCurrentUser.getExternalStorageAppMediaDirectory(packageName);
+ return sCurrentUser.buildExternalStorageAppMediaDirs(packageName);
}
/**
* Generates the raw path to an application's OBB files
* @hide
*/
- public static File getExternalStorageAppObbDirectory(String packageName) {
+ public static File[] buildExternalStorageAppObbDirs(String packageName) {
throwIfUserRequired();
- return sCurrentUser.getExternalStorageAppObbDirectory(packageName);
+ return sCurrentUser.buildExternalStorageAppObbDirs(packageName);
}
/**
* Generates the path to an application's files.
* @hide
*/
- public static File getExternalStorageAppFilesDirectory(String packageName) {
+ public static File[] buildExternalStorageAppFilesDirs(String packageName) {
throwIfUserRequired();
- return sCurrentUser.getExternalStorageAppFilesDirectory(packageName);
+ return sCurrentUser.buildExternalStorageAppFilesDirs(packageName);
}
/**
* Generates the path to an application's cache.
* @hide
*/
- public static File getExternalStorageAppCacheDirectory(String packageName) {
+ public static File[] buildExternalStorageAppCacheDirs(String packageName) {
throwIfUserRequired();
- return sCurrentUser.getExternalStorageAppCacheDirectory(packageName);
+ return sCurrentUser.buildExternalStorageAppCacheDirs(packageName);
}
/**
- * Gets the Android download/cache content directory.
+ * Return the download/cache content directory.
*/
public static File getDownloadCacheDirectory() {
return DOWNLOAD_CACHE_DIRECTORY;
}
/**
- * {@link #getExternalStorageState()} returns MEDIA_REMOVED if the media is not present.
+ * 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";
+
+ /**
+ * Storage state if the media is not present.
+ *
+ * @see #getStorageState(File)
*/
public static final String MEDIA_REMOVED = "removed";
-
+
/**
- * {@link #getExternalStorageState()} returns MEDIA_UNMOUNTED if the media is present
- * but not mounted.
+ * Storage state if the media is present but not mounted.
+ *
+ * @see #getStorageState(File)
*/
public static final String MEDIA_UNMOUNTED = "unmounted";
/**
- * {@link #getExternalStorageState()} returns MEDIA_CHECKING if the media is present
- * and being disk-checked
+ * Storage state if the media is present and being disk-checked.
+ *
+ * @see #getStorageState(File)
*/
public static final String MEDIA_CHECKING = "checking";
/**
- * {@link #getExternalStorageState()} returns MEDIA_NOFS if the media is present
- * but is blank or is using an unsupported filesystem
+ * Storage state if the media is present but is blank or is using an
+ * unsupported filesystem.
+ *
+ * @see #getStorageState(File)
*/
public static final String MEDIA_NOFS = "nofs";
/**
- * {@link #getExternalStorageState()} returns MEDIA_MOUNTED if the media is present
- * and mounted at its mount point with read/write access.
+ * Storage state if the media is present and mounted at its mount point with
+ * read/write access.
+ *
+ * @see #getStorageState(File)
*/
public static final String MEDIA_MOUNTED = "mounted";
/**
- * {@link #getExternalStorageState()} returns MEDIA_MOUNTED_READ_ONLY if the media is present
- * and mounted at its mount point with read only access.
+ * Storage state if the media is present and mounted at its mount point with
+ * read-only access.
+ *
+ * @see #getStorageState(File)
*/
public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
/**
- * {@link #getExternalStorageState()} returns MEDIA_SHARED if the media is present
- * not mounted, and shared via USB mass storage.
+ * Storage state if the media is present not mounted, and shared via USB
+ * mass storage.
+ *
+ * @see #getStorageState(File)
*/
public static final String MEDIA_SHARED = "shared";
/**
- * {@link #getExternalStorageState()} returns MEDIA_BAD_REMOVAL if the media was
- * removed before it was unmounted.
+ * Storage state if the media was removed before it was unmounted.
+ *
+ * @see #getStorageState(File)
*/
public static final String MEDIA_BAD_REMOVAL = "bad_removal";
/**
- * {@link #getExternalStorageState()} returns MEDIA_UNMOUNTABLE if the media is present
- * but cannot be mounted. Typically this happens if the file system on the
- * media is corrupted.
+ * Storage state if the media is present but cannot be mounted. Typically
+ * this happens if the file system on the media is corrupted.
+ *
+ * @see #getStorageState(File)
*/
public static final String MEDIA_UNMOUNTABLE = "unmountable";
/**
- * Gets the current state of the primary "external" storage device.
+ * Returns the current state of the primary "external" storage device.
*
* @see #getExternalStorageDirectory()
+ * @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
+ * {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING},
+ * {@link #MEDIA_NOFS}, {@link #MEDIA_MOUNTED},
+ * {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED},
+ * {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
*/
public static String getExternalStorageState() {
+ return getStorageState(getExternalStorageDirectory());
+ }
+
+ /**
+ * Returns the current state of the storage device that provides the given
+ * path.
+ *
+ * @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
+ * {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING},
+ * {@link #MEDIA_NOFS}, {@link #MEDIA_MOUNTED},
+ * {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED},
+ * {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
+ */
+ public static String getStorageState(File path) {
+ final String rawPath;
try {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- final StorageVolume primary = getPrimaryVolume();
- return mountService.getVolumeState(primary.getPath());
- } catch (RemoteException rex) {
- Log.w(TAG, "Failed to read external storage state; assuming REMOVED: " + rex);
- return Environment.MEDIA_REMOVED;
+ rawPath = path.getCanonicalPath();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to resolve target path: " + e);
+ return Environment.MEDIA_UNKNOWN;
}
+
+ try {
+ final IMountService mountService = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
+ final StorageVolume[] volumes = mountService.getVolumeList();
+ for (StorageVolume volume : volumes) {
+ if (rawPath.startsWith(volume.getPath())) {
+ return mountService.getVolumeState(volume.getPath());
+ }
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to find external storage state: " + e);
+ }
+ return Environment.MEDIA_UNKNOWN;
}
/**
@@ -668,7 +741,25 @@
}
}
- private static File buildPath(File base, String... segments) {
+ /**
+ * Append path segments to each given base path, returning result.
+ *
+ * @hide
+ */
+ public static File[] buildPaths(File[] base, String... segments) {
+ File[] result = new File[base.length];
+ for (int i = 0; i < base.length; i++) {
+ result[i] = buildPath(base[i], segments);
+ }
+ return result;
+ }
+
+ /**
+ * Append path segments to given base path, returning result.
+ *
+ * @hide
+ */
+ public static File buildPath(File base, String... segments) {
File cur = base;
for (String segment : segments) {
if (cur == null) {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index a537e99..ad9192a 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -2877,7 +2877,7 @@
// Save associated .obb content if it exists and we did save the apk
// check for .obb and save those too
final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_OWNER);
- final File obbDir = userEnv.getExternalStorageAppObbDirectory(pkg.packageName);
+ final File obbDir = userEnv.buildExternalStorageAppObbDirs(pkg.packageName)[0];
if (obbDir != null) {
if (MORE_DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
File[] obbFiles = obbDir.listFiles();
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 83739f5..1facb80 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -190,6 +190,8 @@
/** When defined, base template for user-specific {@link StorageVolume}. */
private StorageVolume mEmulatedTemplate;
+ // TODO: separate storage volumes on per-user basis
+
@GuardedBy("mVolumesLock")
private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList();
/** Map from path to {@link StorageVolume} */
@@ -2605,6 +2607,7 @@
@VisibleForTesting
public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) {
// TODO: allow caller to provide Environment for full testing
+ // TODO: extend to support OBB mounts on secondary external storage
// Only adjust paths when storage is emulated
if (!Environment.isExternalStorageEmulated()) {
@@ -2617,10 +2620,10 @@
final UserEnvironment userEnv = new UserEnvironment(userId);
// /storage/emulated/0
- final String externalPath = userEnv.getExternalStorageDirectory().toString();
+ final String externalPath = userEnv.getExternalStorageDirectory().getAbsolutePath();
// /storage/emulated_legacy
final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory()
- .toString();
+ .getAbsolutePath();
if (path.startsWith(externalPath)) {
path = path.substring(externalPath.length() + 1);
@@ -2636,18 +2639,19 @@
path = path.substring(obbPath.length() + 1);
if (forVold) {
- return new File(Environment.getEmulatedStorageObbSource(), path).toString();
+ return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath();
} else {
final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
- return new File(ownerEnv.getExternalStorageObbDirectory(), path).toString();
+ return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
+ .getAbsolutePath();
}
}
// Handle normal external storage paths
if (forVold) {
- return new File(Environment.getEmulatedStorageSource(userId), path).toString();
+ return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
} else {
- return new File(userEnv.getExternalStorageDirectory(), path).toString();
+ return new File(userEnv.getExternalDirs()[0], path).getAbsolutePath();
}
}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 80e20a5..656080b 100755
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -6794,31 +6794,20 @@
if (mounted) {
final UserEnvironment userEnv = new UserEnvironment(mStats.userHandle);
- final File externalCacheDir = userEnv
- .getExternalStorageAppCacheDirectory(mStats.packageName);
- final long externalCacheSize = mContainerService
- .calculateDirectorySize(externalCacheDir.getPath());
- mStats.externalCacheSize = externalCacheSize;
+ mStats.externalCacheSize = calculateDirectorySize(mContainerService,
+ userEnv.buildExternalStorageAppCacheDirs(mStats.packageName));
- final File externalDataDir = userEnv
- .getExternalStorageAppDataDirectory(mStats.packageName);
- long externalDataSize = mContainerService.calculateDirectorySize(externalDataDir
- .getPath());
+ mStats.externalDataSize = calculateDirectorySize(mContainerService,
+ userEnv.buildExternalStorageAppDataDirs(mStats.packageName));
- if (externalCacheDir.getParentFile().equals(externalDataDir)) {
- externalDataSize -= externalCacheSize;
- }
- mStats.externalDataSize = externalDataSize;
+ // Always subtract cache size, since it's a subdirectory
+ mStats.externalDataSize -= mStats.externalCacheSize;
- final File externalMediaDir = userEnv
- .getExternalStorageAppMediaDirectory(mStats.packageName);
- mStats.externalMediaSize = mContainerService
- .calculateDirectorySize(externalMediaDir.getPath());
+ mStats.externalMediaSize = calculateDirectorySize(mContainerService,
+ userEnv.buildExternalStorageAppMediaDirs(mStats.packageName));
- final File externalObbDir = userEnv
- .getExternalStorageAppObbDirectory(mStats.packageName);
- mStats.externalObbSize = mContainerService.calculateDirectorySize(externalObbDir
- .getPath());
+ mStats.externalObbSize = calculateDirectorySize(mContainerService,
+ userEnv.buildExternalStorageAppObbDirs(mStats.packageName));
}
}
@@ -6840,6 +6829,24 @@
}
}
+ private static long calculateDirectorySize(IMediaContainerService mcs, File[] paths)
+ throws RemoteException {
+ long result = 0;
+ for (File path : paths) {
+ result += mcs.calculateDirectorySize(path.getAbsolutePath());
+ }
+ return result;
+ }
+
+ private static void clearDirectory(IMediaContainerService mcs, File[] paths) {
+ for (File path : paths) {
+ try {
+ mcs.clearDirectory(path.getAbsolutePath());
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
class InstallParams extends HandlerParams {
final IPackageInstallObserver observer;
int flags;
@@ -9291,25 +9298,13 @@
}
final UserEnvironment userEnv = new UserEnvironment(curUser);
- final File externalCacheDir = userEnv
- .getExternalStorageAppCacheDirectory(packageName);
- try {
- conn.mContainerService.clearDirectory(externalCacheDir.toString());
- } catch (RemoteException e) {
- }
+ clearDirectory(conn.mContainerService,
+ userEnv.buildExternalStorageAppCacheDirs(packageName));
if (allData) {
- final File externalDataDir = userEnv
- .getExternalStorageAppDataDirectory(packageName);
- try {
- conn.mContainerService.clearDirectory(externalDataDir.toString());
- } catch (RemoteException e) {
- }
- final File externalMediaDir = userEnv
- .getExternalStorageAppMediaDirectory(packageName);
- try {
- conn.mContainerService.clearDirectory(externalMediaDir.toString());
- } catch (RemoteException e) {
- }
+ clearDirectory(conn.mContainerService,
+ userEnv.buildExternalStorageAppDataDirs(packageName));
+ clearDirectory(conn.mContainerService,
+ userEnv.buildExternalStorageAppMediaDirs(packageName));
}
}
} finally {