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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user