diff options
author | 2013-08-11 16:28:14 -0700 | |
---|---|---|
committer | 2013-08-11 17:07:44 -0700 | |
commit | 1abdb7123025e52512b2ed7a518f8c754c35f50a (patch) | |
tree | c6ee8a4b4b4f8cf7b865d2d537823d6a59dca441 | |
parent | 09335703572db7d6a9b43f3aba32074e473d6a0f (diff) |
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
-rw-r--r-- | api/current.txt | 8 | ||||
-rw-r--r-- | core/java/android/app/ContextImpl.java | 113 | ||||
-rw-r--r-- | core/java/android/app/NativeActivity.java | 9 | ||||
-rw-r--r-- | core/java/android/content/Context.java | 103 | ||||
-rw-r--r-- | core/java/android/content/ContextWrapper.java | 19 | ||||
-rw-r--r-- | core/java/android/os/Environment.java | 331 | ||||
-rw-r--r-- | services/java/com/android/server/BackupManagerService.java | 2 | ||||
-rw-r--r-- | services/java/com/android/server/MountService.java | 16 | ||||
-rwxr-xr-x | services/java/com/android/server/pm/PackageManagerService.java | 73 |
9 files changed, 441 insertions, 233 deletions
diff --git a/api/current.txt b/api/current.txt index d79a8c049201..3baeea3251d2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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"; } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index eb5c3e72fa13..cdec39983227 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -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) { + // 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 (!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; + if (mExternalFilesDirs == null) { + mExternalFilesDirs = Environment.buildExternalStorageAppFilesDirs(getPackageName()); } - File dir = new File(mExternalFilesDir, type); - if (!dir.exists()) { - if (!dir.mkdirs()) { - Log.w(TAG, "Unable to create external media directory " + dir); - return null; - } + + // 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() { + // 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 @@ 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; + } + // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java index 63c6acd4f396..b84889faedaf 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.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) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 81a5e577f3d8..ff81f726962c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -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,18 +665,66 @@ 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 * device runs low on storage. @@ -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 @@ -736,6 +790,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. * diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 606a1f4dadbd..e09d36743dcf 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -203,12 +203,22 @@ public class ContextWrapper extends Context { 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 @@ public class ContextWrapper extends Context { } @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 70a1edc610f4..3b9456f79875 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 @@ public class Environment { 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 @@ public class Environment { /** {@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 @@ public class Environment { 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 @@ public class Environment { } // /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 buildExternalStoragePublicDirs(type)[0]; } - public File getExternalStoragePublicDirectory(String type) { - return new File(mExternalStorage, type); + public File[] getExternalDirs() { + return mExternalDirs; + } + + public File getMediaDir() { + return mMediaDir; + } + + public File[] buildExternalStoragePublicDirs(String type) { + return buildPaths(mExternalDirs, type); } - public File getExternalStorageAndroidDataDir() { - return mExternalStorageAndroidData; + public File[] buildExternalStorageAndroidDataDirs() { + return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA); } - public File getExternalStorageAppDataDirectory(String packageName) { - return new File(mExternalStorageAndroidData, packageName); + public File[] buildExternalStorageAndroidObbDirs() { + return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB); } - public File getExternalStorageAppMediaDirectory(String packageName) { - return new File(mExternalStorageAndroidMedia, packageName); + public File[] buildExternalStorageAppDataDirs(String packageName) { + return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName); } - public File getExternalStorageAppObbDirectory(String packageName) { - return new File(mExternalStorageAndroidObb, packageName); + public File[] buildExternalStorageAppMediaDirs(String packageName) { + return buildPaths(mExternalDirs, DIR_ANDROID, DIR_MEDIA, packageName); } - public File getExternalStorageAppFilesDirectory(String packageName) { - return new File(new File(mExternalStorageAndroidData, packageName), "files"); + public File[] buildExternalStorageAppObbDirs(String packageName) { + return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB, packageName); } - public File getExternalStorageAppCacheDirectory(String packageName) { - return new File(new File(mExternalStorageAndroidData, packageName), "cache"); + public File[] buildExternalStorageAppFilesDirs(String packageName) { + return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES); } - public File getMediaStorageDirectory() { - return mMediaStorage; + public File[] buildExternalStorageAppCacheDirs(String packageName) { + return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE); } } @@ -230,7 +244,7 @@ public class Environment { */ public static File getMediaStorageDirectory() { throwIfUserRequired(); - return sCurrentUser.getMediaStorageDirectory(); + return sCurrentUser.getMediaDir(); } /** @@ -266,58 +280,64 @@ public class Environment { 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 class Environment { */ public static File getExternalStorageDirectory() { throwIfUserRequired(); - return sCurrentUser.getExternalStorageDirectory(); + return sCurrentUser.getExternalDirs()[0]; } /** {@hide} */ @@ -335,7 +355,7 @@ public class Environment { /** {@hide} */ public static File getLegacyExternalStorageObbDirectory() { - return buildPath(getLegacyExternalStorageDirectory(), DIRECTORY_ANDROID, "obb"); + return buildPath(getLegacyExternalStorageDirectory(), DIR_ANDROID, DIR_OBB); } /** {@hide} */ @@ -347,7 +367,7 @@ public class Environment { /** {@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 class Environment { */ 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 @@ public class Environment { } } - 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 a537e99dfa98..ad9192a570ca 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -2877,7 +2877,7 @@ class BackupManagerService extends IBackupManager.Stub { // 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 83739f55e000..1facb8093625 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -190,6 +190,8 @@ class MountService extends IMountService.Stub /** 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 @@ class MountService extends IMountService.Stub @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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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 80e20a537592..656080b4bf5c 100755 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -6794,31 +6794,20 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { } } + 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 @@ public class PackageManagerService extends IPackageManager.Stub { } 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 { |