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
This commit is contained in:
Jeff Sharkey
2012-09-07 23:16:01 -07:00
parent b1ee588602
commit b049e212ab
13 changed files with 648 additions and 288 deletions

View File

@@ -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() {
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);
}
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)) {
/*
* 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<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]);
}
}
// 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<StorageVolume> 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<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();