From b049e212ab7fe8967893c202efcb30fecfdb82fb Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Fri, 7 Sep 2012 23:16:01 -0700 Subject: [PATCH] Include user identifier in external storage paths. When building external storage paths, always include user in path to enable cross-user paths and aid debugging. Each Zygote process continues to only have access to the appropriate user-specific emulated storage through bind mounts. A second set of mounts continue supporting legacy /sdcard-style paths. For example, a process running as owner has these mount points: /storage/emulated_legacy /storage/emulated_legacy/Android/obb /storage/emulated/0 /storage/emulated/obb Since Environment is created before Zygote forks, we need to update its internal paths after each process launches. Bug: 7131382 Change-Id: I6f8c6971f2a8edfb415c14cb4ed05ff97e587a21 --- core/java/android/app/ActivityThread.java | 3 + core/java/android/content/pm/UserInfo.java | 6 +- core/java/android/os/Debug.java | 2 +- core/java/android/os/Environment.java | 194 ++++-- .../android/os/storage/IMountService.java | 12 +- .../android/os/storage/StorageManager.java | 23 + .../android/os/storage/StorageVolume.java | 63 +- .../os/storage/ExternalStorageFormatter.java | 6 +- .../android/server/BackupManagerService.java | 7 +- .../java/com/android/server/MountService.java | 579 ++++++++++++------ .../java/com/android/server/SystemServer.java | 8 +- .../server/pm/PackageManagerService.java | 24 +- .../android/server/usb/UsbDeviceManager.java | 9 +- 13 files changed, 648 insertions(+), 288 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 09fa99cb5b477..812ac9ec73db9 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -52,6 +52,7 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; import android.os.Debug; +import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -4877,6 +4878,8 @@ public final class ActivityThread { // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); + Environment.initForCurrentUser(); + Security.addProvider(new AndroidKeyStoreProvider()); Process.setArgV0(""); diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 060a235b60b4c..6bc9a1f541bc2 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -18,7 +18,7 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; -import android.os.Parcelable.Creator; +import android.os.UserHandle; /** * Per-user information. @@ -92,6 +92,10 @@ public class UserInfo implements Parcelable { serialNumber = orig.serialNumber; } + public UserHandle getUserHandle() { + return new UserHandle(id); + } + @Override public String toString() { return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}"; diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 591cd0efbecd7..c08bfeb17d548 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -95,7 +95,7 @@ public final class Debug * Default trace file path and file */ private static final String DEFAULT_TRACE_PATH_PREFIX = - Environment.getExternalStorageDirectory().getPath() + "/"; + Environment.getLegacyExternalStorageDirectory().getPath() + "/"; private static final String DEFAULT_TRACE_BODY = "dmtrace"; private static final String DEFAULT_TRACE_EXTENSION = ".trace"; private static final String DEFAULT_TRACE_FILE_PATH = diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 2fbcf3fe29690..6667a41f49ca1 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -16,9 +16,10 @@ package android.os; -import android.content.res.Resources; import android.os.storage.IMountService; +import android.os.storage.StorageManager; import android.os.storage.StorageVolume; +import android.text.TextUtils; import android.util.Log; import java.io.File; @@ -29,31 +30,125 @@ import java.io.File; public class Environment { private static final String TAG = "Environment"; + private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE"; + private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET"; + private static final File ROOT_DIRECTORY = getDirectory("ANDROID_ROOT", "/system"); private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; - private static final Object mLock = new Object(); + private static UserEnvironment sCurrentUser; - private volatile static StorageVolume mPrimaryVolume = null; + private static final Object sLock = new Object(); + + // @GuardedBy("sLock") + private static volatile StorageVolume sPrimaryVolume; private static StorageVolume getPrimaryVolume() { - if (mPrimaryVolume == null) { - synchronized (mLock) { - if (mPrimaryVolume == null) { + if (sPrimaryVolume == null) { + synchronized (sLock) { + if (sPrimaryVolume == null) { try { IMountService mountService = IMountService.Stub.asInterface(ServiceManager .getService("mount")); - Parcelable[] volumes = mountService.getVolumeList(); - mPrimaryVolume = (StorageVolume)volumes[0]; + final StorageVolume[] volumes = mountService.getVolumeList(); + sPrimaryVolume = StorageManager.getPrimaryVolume(volumes); } catch (Exception e) { Log.e(TAG, "couldn't talk to MountService", e); } } } } - return mPrimaryVolume; + return sPrimaryVolume; + } + + static { + initForCurrentUser(); + } + + /** {@hide} */ + public static void initForCurrentUser() { + final int userId = UserHandle.myUserId(); + sCurrentUser = new UserEnvironment(userId); + + synchronized (sLock) { + sPrimaryVolume = null; + } + } + + /** {@hide} */ + public static class UserEnvironment { + // TODO: generalize further to create package-specific environment + + private final File mExternalStorage; + private final File mExternalStorageAndroidData; + private final File mExternalStorageAndroidMedia; + private final File mExternalStorageAndroidObb; + + public UserEnvironment(int userId) { + // See storage config details at http://source.android.com/tech/storage/ + String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE); + String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET); + + if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) { + // Device has emulated storage; external storage paths should have + // userId burned into them. + final File emulatedBase = new File(rawEmulatedStorageTarget); + + // /storage/emulated/0 + mExternalStorage = buildPath(emulatedBase, Integer.toString(userId)); + // /storage/emulated/obb + mExternalStorageAndroidObb = buildPath(emulatedBase, "obb"); + + } else { + // Device has physical external storage; use plain paths. + if (TextUtils.isEmpty(rawExternalStorage)) { + Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default"); + rawExternalStorage = "/storage/sdcard0"; + } + + // /storage/sdcard0 + mExternalStorage = new File(rawExternalStorage); + // /storage/sdcard0/Android/obb + mExternalStorageAndroidObb = buildPath(mExternalStorage, "Android", "obb"); + } + + mExternalStorageAndroidData = buildPath(mExternalStorage, "Android", "data"); + mExternalStorageAndroidMedia = buildPath(mExternalStorage, "Android", "media"); + } + + public File getExternalStorageDirectory() { + return mExternalStorage; + } + + public File getExternalStoragePublicDirectory(String type) { + return new File(mExternalStorage, type); + } + + public File getExternalStorageAndroidDataDir() { + return mExternalStorageAndroidData; + } + + public File getExternalStorageAppDataDirectory(String packageName) { + return new File(mExternalStorageAndroidData, packageName); + } + + public File getExternalStorageAppMediaDirectory(String packageName) { + return new File(mExternalStorageAndroidMedia, packageName); + } + + public File getExternalStorageAppObbDirectory(String packageName) { + return new File(mExternalStorageAndroidObb, packageName); + } + + public File getExternalStorageAppFilesDirectory(String packageName) { + return new File(new File(mExternalStorageAndroidData, packageName), "files"); + } + + public File getExternalStorageAppCacheDirectory(String packageName) { + return new File(new File(mExternalStorageAndroidData, packageName), "cache"); + } } /** @@ -137,20 +232,7 @@ public class Environment { private static final File MEDIA_STORAGE_DIRECTORY = getDirectory("MEDIA_STORAGE", "/data/media"); - private static final File EXTERNAL_STORAGE_DIRECTORY - = getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"); - - private static final File EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY = new File(new File( - getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "data"); - - private static final File EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY = new File(new File( - getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "media"); - - private static final File EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY = new File(new File( - getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "obb"); - - private static final File DOWNLOAD_CACHE_DIRECTORY - = getDirectory("DOWNLOAD_CACHE", "/cache"); + private static final File DOWNLOAD_CACHE_DIRECTORY = getDirectory("DOWNLOAD_CACHE", "/cache"); /** * Gets the Android data directory. @@ -198,7 +280,13 @@ public class Environment { * @see #isExternalStorageRemovable() */ public static File getExternalStorageDirectory() { - return EXTERNAL_STORAGE_DIRECTORY; + throwIfSystem(); + return sCurrentUser.getExternalStorageDirectory(); + } + + /** {@hide} */ + public static File getLegacyExternalStorageDirectory() { + return new File(System.getenv(ENV_EXTERNAL_STORAGE)); } /** @@ -318,7 +406,8 @@ public class Environment { * using it such as with {@link File#mkdirs File.mkdirs()}. */ public static File getExternalStoragePublicDirectory(String type) { - return new File(getExternalStorageDirectory(), type); + throwIfSystem(); + return sCurrentUser.getExternalStoragePublicDirectory(type); } /** @@ -326,7 +415,8 @@ public class Environment { * @hide */ public static File getExternalStorageAndroidDataDir() { - return EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY; + throwIfSystem(); + return sCurrentUser.getExternalStorageAndroidDataDir(); } /** @@ -334,7 +424,8 @@ public class Environment { * @hide */ public static File getExternalStorageAppDataDirectory(String packageName) { - return new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, packageName); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppDataDirectory(packageName); } /** @@ -342,7 +433,8 @@ public class Environment { * @hide */ public static File getExternalStorageAppMediaDirectory(String packageName) { - return new File(EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY, packageName); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppMediaDirectory(packageName); } /** @@ -350,7 +442,8 @@ public class Environment { * @hide */ public static File getExternalStorageAppObbDirectory(String packageName) { - return new File(EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY, packageName); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppObbDirectory(packageName); } /** @@ -358,17 +451,17 @@ public class Environment { * @hide */ public static File getExternalStorageAppFilesDirectory(String packageName) { - return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, - packageName), "files"); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppFilesDirectory(packageName); } - + /** * Generates the path to an application's cache. * @hide */ public static File getExternalStorageAppCacheDirectory(String packageName) { - return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, - packageName), "cache"); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppCacheDirectory(packageName); } /** @@ -441,9 +534,10 @@ public class Environment { try { IMountService mountService = IMountService.Stub.asInterface(ServiceManager .getService("mount")); - return mountService.getVolumeState(getExternalStorageDirectory() - .toString()); - } catch (Exception rex) { + 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; } } @@ -457,8 +551,8 @@ public class Environment { *

See {@link #getExternalStorageDirectory()} for more information. */ public static boolean isExternalStorageRemovable() { - StorageVolume volume = getPrimaryVolume(); - return (volume != null && volume.isRemovable()); + final StorageVolume primary = getPrimaryVolume(); + return (primary != null && primary.isRemovable()); } /** @@ -475,12 +569,30 @@ public class Environment { * android.content.ComponentName, boolean)} for additional details. */ public static boolean isExternalStorageEmulated() { - StorageVolume volume = getPrimaryVolume(); - return (volume != null && volume.isEmulated()); + final StorageVolume primary = getPrimaryVolume(); + return (primary != null && primary.isEmulated()); } static File getDirectory(String variableName, String defaultPath) { String path = System.getenv(variableName); return path == null ? new File(defaultPath) : new File(path); } + + private static void throwIfSystem() { + if (Process.myUid() == Process.SYSTEM_UID) { + Log.wtf(TAG, "Static storage paths aren't available from AID_SYSTEM", new Throwable()); + } + } + + private static File buildPath(File base, String... segments) { + File cur = base; + for (String segment : segments) { + if (cur == null) { + cur = new File(segment); + } else { + cur = new File(cur, segment); + } + } + return cur; + } } diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index ab64866fc30e6..0b1631684257c 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -677,15 +677,15 @@ public interface IMountService extends IInterface { return _result; } - public Parcelable[] getVolumeList() throws RemoteException { + public StorageVolume[] getVolumeList() throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); - Parcelable[] _result; + StorageVolume[] _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0); _reply.readException(); - _result = _reply.readParcelableArray(StorageVolume.class.getClassLoader()); + _result = _reply.createTypedArray(StorageVolume.CREATOR); } finally { _reply.recycle(); _data.recycle(); @@ -1119,9 +1119,9 @@ public interface IMountService extends IInterface { } case TRANSACTION_getVolumeList: { data.enforceInterface(DESCRIPTOR); - Parcelable[] result = getVolumeList(); + StorageVolume[] result = getVolumeList(); reply.writeNoException(); - reply.writeParcelableArray(result, 0); + reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; } case TRANSACTION_getSecureContainerFilesystemPath: { @@ -1358,7 +1358,7 @@ public interface IMountService extends IInterface { /** * Returns list of all mountable volumes. */ - public Parcelable[] getVolumeList() throws RemoteException; + public StorageVolume[] getVolumeList() throws RemoteException; /** * Gets the path on the filesystem for the ASEC container itself. diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 8a20a6ea863d1..54c8709d605b8 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -16,6 +16,8 @@ package android.os.storage; +import android.app.NotificationManager; +import android.content.Context; import android.os.Environment; import android.os.Handler; import android.os.Looper; @@ -285,6 +287,11 @@ public class StorageManager } } + /** {@hide} */ + public static StorageManager from(Context context) { + return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); + } + /** * Constructs a StorageManager object through which an application can * can communicate with the systems mount service. @@ -594,4 +601,20 @@ public class StorageManager } return paths; } + + /** {@hide} */ + public StorageVolume getPrimaryVolume() { + return getPrimaryVolume(getVolumeList()); + } + + /** {@hide} */ + public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) { + for (StorageVolume volume : volumes) { + if (volume.isPrimary()) { + return volume; + } + } + Log.w(TAG, "No primary storage defined"); + return null; + } } diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index b5983d1f99a8b..177a9554191aa 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -19,16 +19,22 @@ package android.os.storage; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; + +import java.io.File; /** - * A class representing a storage volume + * Description of a storage volume and its capabilities, including the + * filesystem path where it may be mounted. + * * @hide */ public class StorageVolume implements Parcelable { + // TODO: switch to more durable token private int mStorageId; - private final String mPath; + private final File mPath; private final int mDescriptionId; private final boolean mPrimary; private final boolean mRemovable; @@ -37,14 +43,17 @@ public class StorageVolume implements Parcelable { private final boolean mAllowMassStorage; /** Maximum file size for the storage, or zero for no limit */ private final long mMaxFileSize; + /** When set, indicates exclusive ownership of this volume */ + private final UserHandle mOwner; // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING, // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED, // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts. public static final String EXTRA_STORAGE_VOLUME = "storage_volume"; - public StorageVolume(String path, int descriptionId, boolean primary, boolean removable, - boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize) { + public StorageVolume(File path, int descriptionId, boolean primary, boolean removable, + boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize, + UserHandle owner) { mPath = path; mDescriptionId = descriptionId; mPrimary = primary; @@ -53,18 +62,26 @@ public class StorageVolume implements Parcelable { mMtpReserveSpace = mtpReserveSpace; mAllowMassStorage = allowMassStorage; mMaxFileSize = maxFileSize; + mOwner = owner; } private StorageVolume(Parcel in) { mStorageId = in.readInt(); - mPath = in.readString(); + mPath = new File(in.readString()); mDescriptionId = in.readInt(); - mPrimary = in.readByte() != 0; - mRemovable = in.readByte() != 0; - mEmulated = in.readByte() != 0; + mPrimary = in.readInt() != 0; + mRemovable = in.readInt() != 0; + mEmulated = in.readInt() != 0; mMtpReserveSpace = in.readInt(); - mAllowMassStorage = in.readByte() != 0; + mAllowMassStorage = in.readInt() != 0; mMaxFileSize = in.readLong(); + mOwner = in.readParcelable(null); + } + + public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) { + return new StorageVolume(path, template.mDescriptionId, template.mPrimary, + template.mRemovable, template.mEmulated, template.mMtpReserveSpace, + template.mAllowMassStorage, template.mMaxFileSize, owner); } /** @@ -73,6 +90,10 @@ public class StorageVolume implements Parcelable { * @return the mount path */ public String getPath() { + return mPath.toString(); + } + + public File getPathFile() { return mPath; } @@ -164,6 +185,10 @@ public class StorageVolume implements Parcelable { return mMaxFileSize; } + public UserHandle getOwner() { + return mOwner; + } + @Override public boolean equals(Object obj) { if (obj instanceof StorageVolume && mPath != null) { @@ -180,10 +205,19 @@ public class StorageVolume implements Parcelable { @Override public String toString() { - return "StorageVolume [mAllowMassStorage=" + mAllowMassStorage + ", mDescriptionId=" - + mDescriptionId + ", mEmulated=" + mEmulated + ", mMaxFileSize=" + mMaxFileSize - + ", mMtpReserveSpace=" + mMtpReserveSpace + ", mPath=" + mPath + ", mRemovable=" - + mRemovable + ", mStorageId=" + mStorageId + "]"; + final StringBuilder builder = new StringBuilder("StorageVolume ["); + builder.append("mStorageId=").append(mStorageId); + builder.append(" mPath=").append(mPath); + builder.append(" mDescriptionId=").append(mDescriptionId); + builder.append(" mPrimary=").append(mPrimary); + builder.append(" mRemovable=").append(mRemovable); + builder.append(" mEmulated=").append(mEmulated); + builder.append(" mMtpReserveSpace=").append(mMtpReserveSpace); + builder.append(" mAllowMassStorage=").append(mAllowMassStorage); + builder.append(" mMaxFileSize=").append(mMaxFileSize); + builder.append(" mOwner=").append(mOwner); + builder.append("]"); + return builder.toString(); } public static final Creator CREATOR = new Creator() { @@ -206,7 +240,7 @@ public class StorageVolume implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mStorageId); - parcel.writeString(mPath); + parcel.writeString(mPath.toString()); parcel.writeInt(mDescriptionId); parcel.writeInt(mPrimary ? 1 : 0); parcel.writeInt(mRemovable ? 1 : 0); @@ -214,5 +248,6 @@ public class StorageVolume implements Parcelable { parcel.writeInt(mMtpReserveSpace); parcel.writeInt(mAllowMassStorage ? 1 : 0); parcel.writeLong(mMaxFileSize); + parcel.writeParcelable(mOwner, flags); } } diff --git a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java index 3905c88ac2825..fb7f215607563 100644 --- a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java +++ b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java @@ -122,7 +122,7 @@ public class ExternalStorageFormatter extends Service public void onCancel(DialogInterface dialog) { IMountService mountService = getMountService(); String extStoragePath = mStorageVolume == null ? - Environment.getExternalStorageDirectory().toString() : + Environment.getLegacyExternalStorageDirectory().toString() : mStorageVolume.getPath(); try { mountService.mountVolume(extStoragePath); @@ -149,7 +149,7 @@ public class ExternalStorageFormatter extends Service updateProgressDialog(R.string.progress_unmounting); IMountService mountService = getMountService(); final String extStoragePath = mStorageVolume == null ? - Environment.getExternalStorageDirectory().toString() : + Environment.getLegacyExternalStorageDirectory().toString() : mStorageVolume.getPath(); try { // Remove encryption mapping if this is an unmount for a factory reset. @@ -163,7 +163,7 @@ public class ExternalStorageFormatter extends Service updateProgressDialog(R.string.progress_erasing); final IMountService mountService = getMountService(); final String extStoragePath = mStorageVolume == null ? - Environment.getExternalStorageDirectory().toString() : + Environment.getLegacyExternalStorageDirectory().toString() : mStorageVolume.getPath(); if (mountService != null) { new Thread() { diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 1d40f4f427f9e..5e2b42551d95a 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -67,6 +67,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; +import android.os.Environment.UserEnvironment; import android.os.storage.IMountService; import android.provider.Settings; import android.util.EventLog; @@ -2720,9 +2721,13 @@ class BackupManagerService extends IBackupManager.Stub { FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null, apkDir, appSourceDir, output); + // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM + // doesn't have access to external storage. + // Save associated .obb content if it exists and we did save the apk // check for .obb and save those too - final File obbDir = Environment.getExternalStorageAppObbDirectory(pkg.packageName); + final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_OWNER); + final File obbDir = userEnv.getExternalStorageAppObbDirectory(pkg.packageName); 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 f40333d7f3392..32ab1542c3df2 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -16,11 +16,7 @@ package com.android.server; -import com.android.internal.app.IMediaContainerService; -import com.android.internal.util.XmlUtils; -import com.android.server.am.ActivityManagerService; -import com.android.server.pm.PackageManagerService; -import com.android.server.NativeDaemonConnector.Command; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.content.BroadcastReceiver; @@ -30,6 +26,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.ObbInfo; import android.content.res.Resources; import android.content.res.TypedArray; @@ -38,15 +35,14 @@ import android.hardware.usb.UsbManager; import android.net.Uri; import android.os.Binder; import android.os.Environment; +import android.os.Environment.UserEnvironment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.IMountService; @@ -61,9 +57,18 @@ import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; -import org.xmlpull.v1.XmlPullParser; +import com.android.internal.app.IMediaContainerService; +import com.android.internal.util.XmlUtils; +import com.android.server.NativeDaemonConnector.Command; +import com.android.server.am.ActivityManagerService; +import com.android.server.pm.PackageManagerService; +import com.android.server.pm.UserManagerService; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + import org.xmlpull.v1.XmlPullParserException; +import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -81,7 +86,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.Set; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; @@ -96,9 +100,11 @@ import javax.crypto.spec.PBEKeySpec; class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks, Watchdog.Monitor { - private static final boolean LOCAL_LOGD = false; - private static final boolean DEBUG_UNMOUNT = false; - private static final boolean DEBUG_EVENTS = false; + // TODO: listen for user creation/deletion + + private static final boolean LOCAL_LOGD = true; + private static final boolean DEBUG_UNMOUNT = true; + private static final boolean DEBUG_EVENTS = true; private static final boolean DEBUG_OBB = false; // Disable this since it messes up long-running cryptfs operations. @@ -166,25 +172,34 @@ class MountService extends IMountService.Stub public static final int VolumeBadRemoval = 632; } - private Context mContext; - private NativeDaemonConnector mConnector; - private final ArrayList mVolumes = new ArrayList(); - private StorageVolume mPrimaryVolume; - private final HashMap mVolumeStates = new HashMap(); - private final HashMap mVolumeMap = new HashMap(); - private String mExternalStoragePath; + private Context mContext; + private NativeDaemonConnector mConnector; + + private final Object mVolumesLock = new Object(); + + /** When defined, base template for user-specific {@link StorageVolume}. */ + private StorageVolume mEmulatedTemplate; + + // @GuardedBy("mVolumesLock") + private final ArrayList mVolumes = Lists.newArrayList(); + /** Map from path to {@link StorageVolume} */ + // @GuardedBy("mVolumesLock") + private final HashMap mVolumesByPath = Maps.newHashMap(); + /** Map from path to state */ + // @GuardedBy("mVolumesLock") + private final HashMap mVolumeStates = Maps.newHashMap(); + + private volatile boolean mSystemReady = false; + private PackageManagerService mPms; private boolean mUmsEnabling; private boolean mUmsAvailable = false; // Used as a lock for methods that register/unregister listeners. final private ArrayList mListeners = new ArrayList(); - private boolean mBooted = false; private CountDownLatch mConnectedSignal = new CountDownLatch(1); private CountDownLatch mAsecsScanned = new CountDownLatch(1); private boolean mSendUmsConnectedOnBoot = false; - // true if we should fake MEDIA_MOUNTED state for external storage - private boolean mEmulateExternalStorage = false; /** * Private hash of currently mounted secure containers. @@ -303,6 +318,8 @@ class MountService extends IMountService.Stub private static final int H_UNMOUNT_PM_UPDATE = 1; private static final int H_UNMOUNT_PM_DONE = 2; private static final int H_UNMOUNT_MS = 3; + private static final int H_SYSTEM_READY = 4; + private static final int RETRY_UNMOUNT_DELAY = 30; // in ms private static final int MAX_UNMOUNT_RETRIES = 4; @@ -437,17 +454,26 @@ class MountService extends IMountService.Stub } break; } - case H_UNMOUNT_MS : { + case H_UNMOUNT_MS: { if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS"); UnmountCallBack ucb = (UnmountCallBack) msg.obj; ucb.handleFinished(); break; } + case H_SYSTEM_READY: { + try { + handleSystemReady(); + } catch (Exception ex) { + Slog.e(TAG, "Boot-time mount exception", ex); + } + break; + } } } }; - final private HandlerThread mHandlerThread; - final private Handler mHandler; + + private final HandlerThread mHandlerThread; + private final Handler mHandler; void waitForAsecScan() { waitForLatch(mAsecsScanned); @@ -476,90 +502,119 @@ class MountService extends IMountService.Stub } } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + private void handleSystemReady() { + // Snapshot current volume states since it's not safe to call into vold + // while holding locks. + final HashMap snapshot; + synchronized (mVolumesLock) { + snapshot = new HashMap(mVolumeStates); + } + + for (Map.Entry entry : snapshot.entrySet()) { + final String path = entry.getKey(); + final String state = entry.getValue(); + + if (state.equals(Environment.MEDIA_UNMOUNTED)) { + int rc = doMountVolume(path); + if (rc != StorageResultCode.OperationSucceeded) { + Slog.e(TAG, String.format("Boot-time mount failed (%d)", + rc)); + } + } else if (state.equals(Environment.MEDIA_SHARED)) { + /* + * Bootstrap UMS enabled state since vold indicates + * the volume is shared (runtime restart while ums enabled) + */ + notifyVolumeStateChange(null, path, VolumeState.NoMedia, + VolumeState.Shared); + } + } + + // Push mounted state for all emulated storage + synchronized (mVolumesLock) { + for (StorageVolume volume : mVolumes) { + if (volume.isEmulated()) { + updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); + } + } + } + + /* + * If UMS was connected on boot, send the connected event + * now that we're up. + */ + if (mSendUmsConnectedOnBoot) { + sendUmsIntent(true); + mSendUmsConnectedOnBoot = false; + } + } + + private final BroadcastReceiver mBootReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId == -1) return; + final UserHandle user = new UserHandle(userId); - if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - mBooted = true; + Slog.d(TAG, "BOOT_COMPLETED for " + user); - /* - * In the simulator, we need to broadcast a volume mounted event - * to make the media scanner run. - */ - if ("simulator".equals(SystemProperties.get("ro.product.device"))) { - notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, - VolumeState.Mounted); - return; - } - new Thread() { - @Override - public void run() { - try { - // it is not safe to call vold with mVolumeStates locked - // so we make a copy of the paths and states and process them - // outside the lock - String[] paths; - String[] states; - int count; - synchronized (mVolumeStates) { - Set keys = mVolumeStates.keySet(); - count = keys.size(); - paths = keys.toArray(new String[count]); - states = new String[count]; - for (int i = 0; i < count; i++) { - states[i] = mVolumeStates.get(paths[i]); - } - } + // Broadcast mounted volumes to newly booted user. This kicks off + // media scanner when a user becomes active. + synchronized (mVolumesLock) { + for (StorageVolume volume : mVolumes) { + final UserHandle owner = volume.getOwner(); + final boolean ownerMatch = owner == null + || owner.getIdentifier() == user.getIdentifier(); - for (int i = 0; i < count; i++) { - String path = paths[i]; - String state = states[i]; + final String state = mVolumeStates.get(volume.getPath()); - if (state.equals(Environment.MEDIA_UNMOUNTED)) { - int rc = doMountVolume(path); - if (rc != StorageResultCode.OperationSucceeded) { - Slog.e(TAG, String.format("Boot-time mount failed (%d)", - rc)); - } - } else if (state.equals(Environment.MEDIA_SHARED)) { - /* - * Bootstrap UMS enabled state since vold indicates - * the volume is shared (runtime restart while ums enabled) - */ - notifyVolumeStateChange(null, path, VolumeState.NoMedia, - VolumeState.Shared); - } - } - - /* notify external storage has mounted to trigger media scanner */ - if (mEmulateExternalStorage) { - notifyVolumeStateChange(null, - Environment.getExternalStorageDirectory().getPath(), - VolumeState.NoMedia, VolumeState.Mounted); - } - - /* - * If UMS was connected on boot, send the connected event - * now that we're up. - */ - if (mSendUmsConnectedOnBoot) { - sendUmsIntent(true); - mSendUmsConnectedOnBoot = false; - } - } catch (Exception ex) { - Slog.e(TAG, "Boot-time mount exception", ex); - } + if (ownerMatch && (Environment.MEDIA_MOUNTED.equals(state) + || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state))) { + sendStorageIntent(Intent.ACTION_MEDIA_MOUNTED, volume, user); } - }.start(); - } else if (action.equals(UsbManager.ACTION_USB_STATE)) { - boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) && - intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false)); - notifyShareAvailabilityChange(available); + } } } }; + + private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId == -1) return; + final UserHandle user = new UserHandle(userId); + + final String action = intent.getAction(); + if (Intent.ACTION_USER_ADDED.equals(action)) { + synchronized (mVolumesLock) { + createEmulatedVolumeForUserLocked(user); + } + + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + synchronized (mVolumesLock) { + final List toRemove = Lists.newArrayList(); + for (StorageVolume volume : mVolumes) { + if (user.equals(volume.getOwner())) { + toRemove.add(volume); + } + } + for (StorageVolume volume : toRemove) { + removeVolumeLocked(volume); + } + } + } + } + }; + + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) && + intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false)); + notifyShareAvailabilityChange(available); + } + }; + private final class MountServiceBinderListener implements IBinder.DeathRecipient { final IMountServiceListener mListener; @@ -590,11 +645,13 @@ class MountService extends IMountService.Stub } } - private void updatePublicVolumeState(String path, String state) { - String oldState; - synchronized(mVolumeStates) { + private void updatePublicVolumeState(StorageVolume volume, String state) { + final String path = volume.getPath(); + final String oldState; + synchronized (mVolumesLock) { oldState = mVolumeStates.put(path, state); } + if (state.equals(oldState)) { Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s", state, state, path)); @@ -603,24 +660,24 @@ class MountService extends IMountService.Stub Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")"); - if (path.equals(mExternalStoragePath)) { - // Update state on PackageManager, but only of real events - if (!mEmulateExternalStorage) { - if (Environment.MEDIA_UNMOUNTED.equals(state)) { - mPms.updateExternalMediaStatus(false, false); + // Tell PackageManager about changes to primary volume state, but only + // when not emulated. + if (volume.isPrimary() && !volume.isEmulated()) { + if (Environment.MEDIA_UNMOUNTED.equals(state)) { + mPms.updateExternalMediaStatus(false, false); - /* - * Some OBBs might have been unmounted when this volume was - * unmounted, so send a message to the handler to let it know to - * remove those from the list of mounted OBBS. - */ - mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( - OBB_FLUSH_MOUNT_STATE, path)); - } else if (Environment.MEDIA_MOUNTED.equals(state)) { - mPms.updateExternalMediaStatus(true, false); - } + /* + * Some OBBs might have been unmounted when this volume was + * unmounted, so send a message to the handler to let it know to + * remove those from the list of mounted OBBS. + */ + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( + OBB_FLUSH_MOUNT_STATE, path)); + } else if (Environment.MEDIA_MOUNTED.equals(state)) { + mPms.updateExternalMediaStatus(true, false); } } + synchronized (mListeners) { for (int i = mListeners.size() -1; i >= 0; i--) { MountServiceBinderListener bl = mListeners.get(i); @@ -637,7 +694,6 @@ class MountService extends IMountService.Stub } /** - * * Callback from NativeDaemonConnector */ public void onDaemonConnected() { @@ -661,6 +717,11 @@ class MountService extends IMountService.Stub String path = tok[1]; String state = Environment.MEDIA_REMOVED; + final StorageVolume volume; + synchronized (mVolumesLock) { + volume = mVolumesByPath.get(path); + } + int st = Integer.parseInt(tok[2]); if (st == VolumeState.NoMedia) { state = Environment.MEDIA_REMOVED; @@ -678,12 +739,15 @@ class MountService extends IMountService.Stub if (state != null) { if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state); - updatePublicVolumeState(path, state); + updatePublicVolumeState(volume, state); } } } catch (Exception e) { Slog.e(TAG, "Error processing initial volume state", e); - updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED); + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary != null) { + updatePublicVolumeState(primary, Environment.MEDIA_REMOVED); + } } /* @@ -749,6 +813,13 @@ class MountService extends IMountService.Stub Slog.e(TAG, "Failed to parse major/minor", ex); } + final StorageVolume volume; + final String state; + synchronized (mVolumesLock) { + volume = mVolumesByPath.get(path); + state = mVolumeStates.get(path); + } + if (code == VoldResponseCode.VolumeDiskInserted) { new Thread() { @Override @@ -772,27 +843,27 @@ class MountService extends IMountService.Stub } /* Send the media unmounted event first */ if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); - sendStorageIntent(Environment.MEDIA_UNMOUNTED, path); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); + sendStorageIntent(Environment.MEDIA_UNMOUNTED, volume, UserHandle.ALL); if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed"); - updatePublicVolumeState(path, Environment.MEDIA_REMOVED); + updatePublicVolumeState(volume, Environment.MEDIA_REMOVED); action = Intent.ACTION_MEDIA_REMOVED; } else if (code == VoldResponseCode.VolumeBadRemoval) { if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); /* Send the media unmounted event first */ - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); action = Intent.ACTION_MEDIA_UNMOUNTED; if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal"); - updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL); + updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL); action = Intent.ACTION_MEDIA_BAD_REMOVAL; } else { Slog.e(TAG, String.format("Unknown code {%d}", code)); } if (action != null) { - sendStorageIntent(action, path); + sendStorageIntent(action, volume, UserHandle.ALL); } } else { return false; @@ -802,14 +873,20 @@ class MountService extends IMountService.Stub } private void notifyVolumeStateChange(String label, String path, int oldState, int newState) { - String vs = getVolumeState(path); - if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs); + final StorageVolume volume; + final String state; + synchronized (mVolumesLock) { + volume = mVolumesByPath.get(path); + state = getVolumeState(path); + } + + if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state); String action = null; if (oldState == VolumeState.Shared && newState != oldState) { if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent"); - sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, path); + sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL); } if (newState == VolumeState.Init) { @@ -820,22 +897,22 @@ class MountService extends IMountService.Stub * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or * if we're in the process of enabling UMS */ - if (!vs.equals( - Environment.MEDIA_BAD_REMOVAL) && !vs.equals( - Environment.MEDIA_NOFS) && !vs.equals( + if (!state.equals( + Environment.MEDIA_BAD_REMOVAL) && !state.equals( + Environment.MEDIA_NOFS) && !state.equals( Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) { if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable"); - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); action = Intent.ACTION_MEDIA_UNMOUNTED; } } else if (newState == VolumeState.Pending) { } else if (newState == VolumeState.Checking) { if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking"); - updatePublicVolumeState(path, Environment.MEDIA_CHECKING); + updatePublicVolumeState(volume, Environment.MEDIA_CHECKING); action = Intent.ACTION_MEDIA_CHECKING; } else if (newState == VolumeState.Mounted) { if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted"); - updatePublicVolumeState(path, Environment.MEDIA_MOUNTED); + updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); action = Intent.ACTION_MEDIA_MOUNTED; } else if (newState == VolumeState.Unmounting) { action = Intent.ACTION_MEDIA_EJECT; @@ -843,11 +920,11 @@ class MountService extends IMountService.Stub } else if (newState == VolumeState.Shared) { if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted"); /* Send the media unmounted event first */ - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); - sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, path); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); + sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL); if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared"); - updatePublicVolumeState(path, Environment.MEDIA_SHARED); + updatePublicVolumeState(volume, Environment.MEDIA_SHARED); action = Intent.ACTION_MEDIA_SHARED; if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent"); } else if (newState == VolumeState.SharedMnt) { @@ -858,13 +935,18 @@ class MountService extends IMountService.Stub } if (action != null) { - sendStorageIntent(action, path); + sendStorageIntent(action, volume, UserHandle.ALL); } } private int doMountVolume(String path) { int rc = StorageResultCode.OperationSucceeded; + final StorageVolume volume; + synchronized (mVolumesLock) { + volume = mVolumesByPath.get(path); + } + if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); try { mConnector.execute("volume", "mount", path); @@ -884,7 +966,7 @@ class MountService extends IMountService.Stub /* * Media is blank or does not contain a supported filesystem */ - updatePublicVolumeState(path, Environment.MEDIA_NOFS); + updatePublicVolumeState(volume, Environment.MEDIA_NOFS); action = Intent.ACTION_MEDIA_NOFS; rc = StorageResultCode.OperationFailedMediaBlank; } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { @@ -892,7 +974,7 @@ class MountService extends IMountService.Stub /* * Volume consistency check failed */ - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE); action = Intent.ACTION_MEDIA_UNMOUNTABLE; rc = StorageResultCode.OperationFailedMediaCorrupt; } else { @@ -903,7 +985,7 @@ class MountService extends IMountService.Stub * Send broadcast intent (if required for the failure) */ if (action != null) { - sendStorageIntent(action, path); + sendStorageIntent(action, volume, UserHandle.ALL); } } @@ -1011,14 +1093,16 @@ class MountService extends IMountService.Stub } } - if (mBooted == true) { + if (mSystemReady == true) { sendUmsIntent(avail); } else { mSendUmsConnectedOnBoot = avail; } - final String path = Environment.getExternalStorageDirectory().getPath(); - if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) { + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (avail == false && primary != null + && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) { + final String path = primary.getPath(); /* * USB mass storage disconnected while enabled */ @@ -1042,12 +1126,11 @@ class MountService extends IMountService.Stub } } - private void sendStorageIntent(String action, String path) { - Intent intent = new Intent(action, Uri.parse("file://" + path)); - // add StorageVolume extra - intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolumeMap.get(path)); - Slog.d(TAG, "sendStorageIntent " + intent); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) { + final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath())); + intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume); + Slog.d(TAG, "sendStorageIntent " + intent + " to " + user); + mContext.sendBroadcastAsUser(intent, user); } private void sendUmsIntent(boolean c) { @@ -1066,7 +1149,10 @@ class MountService extends IMountService.Stub private static final String TAG_STORAGE_LIST = "StorageList"; private static final String TAG_STORAGE = "storage"; - private void readStorageList() { + private void readStorageListLocked() { + mVolumes.clear(); + mVolumeStates.clear(); + Resources resources = mContext.getResources(); int id = com.android.internal.R.xml.storage_list; @@ -1085,7 +1171,7 @@ class MountService extends IMountService.Stub TypedArray a = resources.obtainAttributes(attrs, com.android.internal.R.styleable.Storage); - CharSequence path = a.getText( + String path = a.getString( com.android.internal.R.styleable.Storage_mountPoint); int descriptionId = a.getResourceId( com.android.internal.R.styleable.Storage_storageDescription, -1); @@ -1110,27 +1196,29 @@ class MountService extends IMountService.Stub " emulated: " + emulated + " mtpReserve: " + mtpReserve + " allowMassStorage: " + allowMassStorage + " maxFileSize: " + maxFileSize); - if (path == null || description == null) { - Slog.e(TAG, "path or description is null in readStorageList"); + + if (emulated) { + // For devices with emulated storage, we create separate + // volumes for each known user. + mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false, + true, mtpReserve, false, maxFileSize, null); + + final UserManagerService userManager = UserManagerService.getInstance(); + for (UserInfo user : userManager.getUsers()) { + createEmulatedVolumeForUserLocked(user.getUserHandle()); + } + } else { - String pathString = path.toString(); - StorageVolume volume = new StorageVolume(pathString, descriptionId, primary, - removable, emulated, mtpReserve, allowMassStorage, maxFileSize); - if (primary) { - if (mPrimaryVolume == null) { - mPrimaryVolume = volume; - } else { - Slog.e(TAG, "multiple primary volumes in storage list"); - } - } - if (mPrimaryVolume == volume) { - // primay volume must be first - mVolumes.add(0, volume); + if (path == null || description == null) { + Slog.e(TAG, "Missing storage path or description in readStorageList"); } else { - mVolumes.add(volume); + final StorageVolume volume = new StorageVolume(new File(path), + descriptionId, primary, removable, emulated, mtpReserve, + allowMassStorage, maxFileSize, null); + addVolumeLocked(volume); } - mVolumeMap.put(pathString, volume); } + a.recycle(); } } @@ -1139,15 +1227,69 @@ class MountService extends IMountService.Stub } catch (IOException e) { throw new RuntimeException(e); } finally { - // compute storage ID for each volume - int length = mVolumes.size(); - for (int i = 0; i < length; i++) { - mVolumes.get(i).setStorageId(i); + // Compute storage ID for each physical volume; emulated storage is + // always 0 when defined. + int index = isExternalStorageEmulated() ? 1 : 0; + for (StorageVolume volume : mVolumes) { + if (!volume.isEmulated()) { + volume.setStorageId(index++); + } } parser.close(); } } + /** + * Create and add new {@link StorageVolume} for given {@link UserHandle} + * using {@link #mEmulatedTemplate} as template. + */ + private void createEmulatedVolumeForUserLocked(UserHandle user) { + if (mEmulatedTemplate == null) { + throw new IllegalStateException("Missing emulated volume multi-user template"); + } + + final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier()); + final File path = userEnv.getExternalStorageDirectory(); + final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user); + volume.setStorageId(0); + addVolumeLocked(volume); + + if (mSystemReady) { + updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); + } else { + // Place stub status for early callers to find + mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED); + } + } + + private void addVolumeLocked(StorageVolume volume) { + Slog.d(TAG, "addVolumeLocked() " + volume); + mVolumes.add(volume); + final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume); + if (existing != null) { + throw new IllegalStateException( + "Volume at " + volume.getPath() + " already exists: " + existing); + } + } + + private void removeVolumeLocked(StorageVolume volume) { + Slog.d(TAG, "removeVolumeLocked() " + volume); + mVolumes.remove(volume); + mVolumesByPath.remove(volume.getPath()); + mVolumeStates.remove(volume.getPath()); + } + + private StorageVolume getPrimaryPhysicalVolume() { + synchronized (mVolumesLock) { + for (StorageVolume volume : mVolumes) { + if (volume.isPrimary() && !volume.isEmulated()) { + return volume; + } + } + } + return null; + } + /** * Constructs a new MountService instance * @@ -1155,32 +1297,35 @@ class MountService extends IMountService.Stub */ public MountService(Context context) { mContext = context; - readStorageList(); - if (mPrimaryVolume != null) { - mExternalStoragePath = mPrimaryVolume.getPath(); - mEmulateExternalStorage = mPrimaryVolume.isEmulated(); - if (mEmulateExternalStorage) { - Slog.d(TAG, "using emulated external storage"); - mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED); - } + synchronized (mVolumesLock) { + readStorageListLocked(); } // XXX: This will go away soon in favor of IMountServiceObserver mPms = (PackageManagerService) ServiceManager.getService("package"); - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BOOT_COMPLETED); - // don't bother monitoring USB if mass storage is not supported on our primary volume - if (mPrimaryVolume != null && mPrimaryVolume.allowMassStorage()) { - filter.addAction(UsbManager.ACTION_USB_STATE); - } - mContext.registerReceiver(mBroadcastReceiver, filter, null, null); - mHandlerThread = new HandlerThread("MountService"); mHandlerThread.start(); mHandler = new MountServiceHandler(mHandlerThread.getLooper()); + // Watch for user boot completion + mContext.registerReceiverAsUser(mBootReceiver, UserHandle.ALL, + new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, mHandler); + + // Watch for user changes + final IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_ADDED); + userFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler); + + // Watch for USB changes on primary volume + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary != null && primary.allowMassStorage()) { + mContext.registerReceiver( + mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler); + } + // Add OBB Action Handler to MountService thread. mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper()); @@ -1200,6 +1345,11 @@ class MountService extends IMountService.Stub } } + public void systemReady() { + mSystemReady = true; + mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); + } + /** * Exposed API calls below here */ @@ -1232,7 +1382,7 @@ class MountService extends IMountService.Stub validatePermission(android.Manifest.permission.SHUTDOWN); Slog.i(TAG, "Shutting down"); - synchronized (mVolumeStates) { + synchronized (mVolumesLock) { for (String path : mVolumeStates.keySet()) { String state = mVolumeStates.get(path); @@ -1313,12 +1463,15 @@ class MountService extends IMountService.Stub waitForReady(); validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary == null) return; + // TODO: Add support for multiple share methods /* * If the volume is mounted and we're enabling then unmount it */ - String path = Environment.getExternalStorageDirectory().getPath(); + String path = primary.getPath(); String vs = getVolumeState(path); String method = "ums"; if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { @@ -1348,14 +1501,20 @@ class MountService extends IMountService.Stub public boolean isUsbMassStorageEnabled() { waitForReady(); - return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums"); + + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary != null) { + return doGetVolumeShared(primary.getPath(), "ums"); + } else { + return false; + } } /** * @return state of the volume at the specified mount point */ public String getVolumeState(String mountPoint) { - synchronized (mVolumeStates) { + synchronized (mVolumesLock) { String state = mVolumeStates.get(mountPoint); if (state == null) { Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); @@ -1370,8 +1529,9 @@ class MountService extends IMountService.Stub } } + @Override public boolean isExternalStorageEmulated() { - return mEmulateExternalStorage; + return mEmulatedTemplate != null; } public int mountVolume(String path) { @@ -1437,7 +1597,9 @@ class MountService extends IMountService.Stub } private void warnOnNotMounted() { - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary != null + && Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()))) { Slog.w(TAG, "getSecureContainerList() called when storage not mounted"); } } @@ -1935,14 +2097,23 @@ class MountService extends IMountService.Stub } } - public Parcelable[] getVolumeList() { - synchronized(mVolumes) { - int size = mVolumes.size(); - Parcelable[] result = new Parcelable[size]; - for (int i = 0; i < size; i++) { - result[i] = mVolumes.get(i); + @Override + public StorageVolume[] getVolumeList() { + final int callingUserId = UserHandle.getCallingUserId(); + final boolean accessAll = (mContext.checkPermission( + android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, + Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED); + + synchronized (mVolumesLock) { + final ArrayList filtered = Lists.newArrayList(); + for (StorageVolume volume : mVolumes) { + final UserHandle owner = volume.getOwner(); + final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId; + if (accessAll || ownerMatch) { + filtered.add(volume); + } } - return result; + return filtered.toArray(new StorageVolume[filtered.size()]); } } @@ -2458,7 +2629,7 @@ class MountService extends IMountService.Stub pw.println(""); - synchronized (mVolumes) { + synchronized (mVolumesLock) { pw.println(" mVolumes:"); final int N = mVolumes.size(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 73e82aba3145d..439844101c970 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -125,6 +125,7 @@ class ServerThread extends Thread { BatteryService battery = null; VibratorService vibrator = null; AlarmManagerService alarm = null; + MountService mountService = null; NetworkManagementService networkManagement = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; @@ -374,7 +375,6 @@ class ServerThread extends Thread { } if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { - MountService mountService = null; if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) { try { /* @@ -813,6 +813,7 @@ class ServerThread extends Thread { // These are needed to propagate to the runnable below. final Context contextF = context; + final MountService mountServiceF = mountService; final BatteryService batteryF = battery; final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; @@ -846,6 +847,11 @@ class ServerThread extends Thread { Slog.i(TAG, "Making services ready"); if (!headless) startSystemUi(contextF); + try { + if (mountServiceF != null) mountServiceF.systemReady(); + } catch (Throwable e) { + reportWtf("making Mount Service ready", e); + } try { if (batteryF != null) batteryF.systemReady(); } catch (Throwable e) { diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index e19a80321b5d7..8ce474aa13b6e 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -109,6 +109,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.Environment.UserEnvironment; import android.provider.Settings.Secure; import android.security.SystemKeyStore; import android.util.DisplayMetrics; @@ -6135,19 +6136,20 @@ public class PackageManagerService extends IPackageManager.Stub { mounted = true; } else { final String status = Environment.getExternalStorageState(); - - mounted = status.equals(Environment.MEDIA_MOUNTED) - || status.equals(Environment.MEDIA_MOUNTED_READ_ONLY); + mounted = (Environment.MEDIA_MOUNTED.equals(status) + || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status)); } if (mounted) { - final File externalCacheDir = Environment + final UserEnvironment userEnv = new UserEnvironment(mStats.userHandle); + + final File externalCacheDir = userEnv .getExternalStorageAppCacheDirectory(mStats.packageName); final long externalCacheSize = mContainerService .calculateDirectorySize(externalCacheDir.getPath()); mStats.externalCacheSize = externalCacheSize; - final File externalDataDir = Environment + final File externalDataDir = userEnv .getExternalStorageAppDataDirectory(mStats.packageName); long externalDataSize = mContainerService.calculateDirectorySize(externalDataDir .getPath()); @@ -6157,12 +6159,12 @@ public class PackageManagerService extends IPackageManager.Stub { } mStats.externalDataSize = externalDataSize; - final File externalMediaDir = Environment + final File externalMediaDir = userEnv .getExternalStorageAppMediaDirectory(mStats.packageName); mStats.externalMediaSize = mContainerService .calculateDirectorySize(externalMediaDir.getPath()); - final File externalObbDir = Environment + final File externalObbDir = userEnv .getExternalStorageAppObbDirectory(mStats.packageName); mStats.externalObbSize = mContainerService.calculateDirectorySize(externalObbDir .getPath()); @@ -8361,20 +8363,22 @@ public class PackageManagerService extends IPackageManager.Stub { if (conn.mContainerService == null) { return; } - final File externalCacheDir = Environment + + final UserEnvironment userEnv = new UserEnvironment(curUser); + final File externalCacheDir = userEnv .getExternalStorageAppCacheDirectory(packageName); try { conn.mContainerService.clearDirectory(externalCacheDir.toString()); } catch (RemoteException e) { } if (allData) { - final File externalDataDir = Environment + final File externalDataDir = userEnv .getExternalStorageAppDataDirectory(packageName); try { conn.mContainerService.clearDirectory(externalDataDir.toString()); } catch (RemoteException e) { } - final File externalMediaDir = Environment + final File externalMediaDir = userEnv .getExternalStorageAppMediaDirectory(packageName); try { conn.mContainerService.clearDirectory(externalMediaDir.toString()); diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index 607ff390cc339..3ef6d4c414e66 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -183,12 +183,9 @@ public class UsbDeviceManager { // We do not show the USB notification if the primary volume supports mass storage. // The legacy mass storage UI will be used instead. boolean massStorageSupported = false; - StorageManager storageManager = (StorageManager) - mContext.getSystemService(Context.STORAGE_SERVICE); - StorageVolume[] volumes = storageManager.getVolumeList(); - if (volumes.length > 0) { - massStorageSupported = volumes[0].allowMassStorage(); - } + final StorageManager storageManager = StorageManager.from(mContext); + final StorageVolume primary = storageManager.getPrimaryVolume(); + massStorageSupported = primary != null && primary.allowMassStorage(); mUseUsbNotification = !massStorageSupported; // make sure the ADB_ENABLED setting value matches the current state