summaryrefslogtreecommitdiff
path: root/services/java/com/android/server/MountService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/MountService.java')
-rw-r--r--services/java/com/android/server/MountService.java573
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();