diff options
Diffstat (limited to 'services/java/com/android/server/MountService.java')
-rw-r--r-- | services/java/com/android/server/MountService.java | 573 |
1 files changed, 372 insertions, 201 deletions
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index f40333d7f339..32ab1542c3df 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<StorageVolume> mVolumes = new ArrayList<StorageVolume>(); - private StorageVolume mPrimaryVolume; - private final HashMap<String, String> mVolumeStates = new HashMap<String, String>(); - private final HashMap<String, StorageVolume> mVolumeMap = new HashMap<String, StorageVolume>(); - 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<StorageVolume> mVolumes = Lists.newArrayList(); + /** Map from path to {@link StorageVolume} */ + // @GuardedBy("mVolumesLock") + private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap(); + /** Map from path to state */ + // @GuardedBy("mVolumesLock") + private final HashMap<String, String> 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<MountServiceBinderListener> mListeners = new ArrayList<MountServiceBinderListener>(); - 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() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); + private void handleSystemReady() { + // Snapshot current volume states since it's not safe to call into vold + // while holding locks. + final HashMap<String, String> snapshot; + synchronized (mVolumesLock) { + snapshot = new HashMap<String, String>(mVolumeStates); + } - if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - mBooted = true; + for (Map.Entry<String, String> 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)) { /* - * In the simulator, we need to broadcast a volume mounted event - * to make the media scanner run. + * Bootstrap UMS enabled state since vold indicates + * the volume is shared (runtime restart while ums enabled) */ - if ("simulator".equals(SystemProperties.get("ro.product.device"))) { - notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, - VolumeState.Mounted); - return; + 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); } - 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<String> 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]); - } - } + } + } - for (int i = 0; i < count; i++) { - String path = paths[i]; - String state = states[i]; - - 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); - } - } + /* + * If UMS was connected on boot, send the connected event + * now that we're up. + */ + if (mSendUmsConnectedOnBoot) { + sendUmsIntent(true); + mSendUmsConnectedOnBoot = false; + } + } - /* notify external storage has mounted to trigger media scanner */ - if (mEmulateExternalStorage) { - notifyVolumeStateChange(null, - Environment.getExternalStorageDirectory().getPath(), - VolumeState.NoMedia, VolumeState.Mounted); - } + private final BroadcastReceiver mBootReceiver = 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); - /* - * 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); + Slog.d(TAG, "BOOT_COMPLETED for " + user); + + // 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(); + + final String state = mVolumeStates.get(volume.getPath()); + + if (ownerMatch && (Environment.MEDIA_MOUNTED.equals(state) + || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state))) { + sendStorageIntent(Intent.ACTION_MEDIA_MOUNTED, volume, user); + } + } + } + } + }; + + 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<StorageVolume> toRemove = Lists.newArrayList(); + for (StorageVolume volume : mVolumes) { + if (user.equals(volume.getOwner())) { + toRemove.add(volume); } } - }.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); + 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"); - } 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 (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()); } - if (mPrimaryVolume == volume) { - // primay volume must be first - mVolumes.add(0, volume); + + } else { + 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,48 +1227,105 @@ 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 * * @param context Binder context for this service */ 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<StorageVolume> 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(); |