summaryrefslogtreecommitdiffstats
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.java2833
1 files changed, 0 insertions, 2833 deletions
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
deleted file mode 100644
index e60231a..0000000
--- a/services/java/com/android/server/MountService.java
+++ /dev/null
@@ -1,2833 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-import android.Manifest;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-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;
-import android.content.res.XmlResourceParser;
-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.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.storage.IMountService;
-import android.os.storage.IMountServiceListener;
-import android.os.storage.IMountShutdownObserver;
-import android.os.storage.IObbActionListener;
-import android.os.storage.OnObbStateChangeListener;
-import android.os.storage.StorageResultCode;
-import android.os.storage.StorageVolume;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IMediaContainerService;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
-import com.android.server.NativeDaemonConnector.Command;
-import com.android.server.NativeDaemonConnector.SensitiveArg;
-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;
-import java.math.BigInteger;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-
-/**
- * MountService implements back-end services for platform storage
- * management.
- * @hide - Applications should use android.os.storage.StorageManager
- * to access the MountService.
- */
-class MountService extends IMountService.Stub
- implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
-
- // TODO: listen for user creation/deletion
-
- private static final boolean LOCAL_LOGD = false;
- private static final boolean DEBUG_UNMOUNT = false;
- private static final boolean DEBUG_EVENTS = false;
- private static final boolean DEBUG_OBB = false;
-
- // Disable this since it messes up long-running cryptfs operations.
- private static final boolean WATCHDOG_ENABLE = false;
-
- private static final String TAG = "MountService";
-
- private static final String VOLD_TAG = "VoldConnector";
-
- /** Maximum number of ASEC containers allowed to be mounted. */
- private static final int MAX_CONTAINERS = 250;
-
- /*
- * Internal vold volume state constants
- */
- class VolumeState {
- public static final int Init = -1;
- public static final int NoMedia = 0;
- public static final int Idle = 1;
- public static final int Pending = 2;
- public static final int Checking = 3;
- public static final int Mounted = 4;
- public static final int Unmounting = 5;
- public static final int Formatting = 6;
- public static final int Shared = 7;
- public static final int SharedMnt = 8;
- }
-
- /*
- * Internal vold response code constants
- */
- class VoldResponseCode {
- /*
- * 100 series - Requestion action was initiated; expect another reply
- * before proceeding with a new command.
- */
- public static final int VolumeListResult = 110;
- public static final int AsecListResult = 111;
- public static final int StorageUsersListResult = 112;
-
- /*
- * 200 series - Requestion action has been successfully completed.
- */
- public static final int ShareStatusResult = 210;
- public static final int AsecPathResult = 211;
- public static final int ShareEnabledResult = 212;
-
- /*
- * 400 series - Command was accepted, but the requested action
- * did not take place.
- */
- public static final int OpFailedNoMedia = 401;
- public static final int OpFailedMediaBlank = 402;
- public static final int OpFailedMediaCorrupt = 403;
- public static final int OpFailedVolNotMounted = 404;
- public static final int OpFailedStorageBusy = 405;
- public static final int OpFailedStorageNotFound = 406;
-
- /*
- * 600 series - Unsolicited broadcasts.
- */
- public static final int VolumeStateChange = 605;
- public static final int VolumeUuidChange = 613;
- public static final int VolumeUserLabelChange = 614;
- public static final int VolumeDiskInserted = 630;
- public static final int VolumeDiskRemoved = 631;
- public static final int VolumeBadRemoval = 632;
-
- /*
- * 700 series - fstrim
- */
- public static final int FstrimCompleted = 700;
- }
-
- private Context mContext;
- private NativeDaemonConnector mConnector;
-
- private final Object mVolumesLock = new Object();
-
- /** When defined, base template for user-specific {@link StorageVolume}. */
- private StorageVolume mEmulatedTemplate;
-
- // TODO: separate storage volumes on per-user basis
-
- @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 final CountDownLatch mConnectedSignal = new CountDownLatch(1);
- private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
- private boolean mSendUmsConnectedOnBoot = false;
-
- /**
- * Private hash of currently mounted secure containers.
- * Used as a lock in methods to manipulate secure containers.
- */
- final private HashSet<String> mAsecMountSet = new HashSet<String>();
-
- /**
- * The size of the crypto algorithm key in bits for OBB files. Currently
- * Twofish is used which takes 128-bit keys.
- */
- private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128;
-
- /**
- * The number of times to run SHA1 in the PBKDF2 function for OBB files.
- * 1024 is reasonably secure and not too slow.
- */
- private static final int PBKDF2_HASH_ROUNDS = 1024;
-
- /**
- * Mounted OBB tracking information. Used to track the current state of all
- * OBBs.
- */
- final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
-
- /** Map from raw paths to {@link ObbState}. */
- final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
-
- class ObbState implements IBinder.DeathRecipient {
- public ObbState(String rawPath, String canonicalPath, int callingUid,
- IObbActionListener token, int nonce) {
- this.rawPath = rawPath;
- this.canonicalPath = canonicalPath.toString();
-
- final int userId = UserHandle.getUserId(callingUid);
- this.ownerPath = buildObbPath(canonicalPath, userId, false);
- this.voldPath = buildObbPath(canonicalPath, userId, true);
-
- this.ownerGid = UserHandle.getSharedAppGid(callingUid);
- this.token = token;
- this.nonce = nonce;
- }
-
- final String rawPath;
- final String canonicalPath;
- final String ownerPath;
- final String voldPath;
-
- final int ownerGid;
-
- // Token of remote Binder caller
- final IObbActionListener token;
-
- // Identifier to pass back to the token
- final int nonce;
-
- public IBinder getBinder() {
- return token.asBinder();
- }
-
- @Override
- public void binderDied() {
- ObbAction action = new UnmountObbAction(this, true);
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
- }
-
- public void link() throws RemoteException {
- getBinder().linkToDeath(this, 0);
- }
-
- public void unlink() {
- getBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder("ObbState{");
- sb.append("rawPath=").append(rawPath);
- sb.append(",canonicalPath=").append(canonicalPath);
- sb.append(",ownerPath=").append(ownerPath);
- sb.append(",voldPath=").append(voldPath);
- sb.append(",ownerGid=").append(ownerGid);
- sb.append(",token=").append(token);
- sb.append(",binder=").append(getBinder());
- sb.append('}');
- return sb.toString();
- }
- }
-
- // OBB Action Handler
- final private ObbActionHandler mObbActionHandler;
-
- // OBB action handler messages
- private static final int OBB_RUN_ACTION = 1;
- private static final int OBB_MCS_BOUND = 2;
- private static final int OBB_MCS_UNBIND = 3;
- private static final int OBB_MCS_RECONNECT = 4;
- private static final int OBB_FLUSH_MOUNT_STATE = 5;
-
- /*
- * Default Container Service information
- */
- static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
- "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService");
-
- final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
-
- class DefaultContainerConnection implements ServiceConnection {
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG_OBB)
- Slog.i(TAG, "onServiceConnected");
- IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
- }
-
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG_OBB)
- Slog.i(TAG, "onServiceDisconnected");
- }
- };
-
- // Used in the ObbActionHandler
- private IMediaContainerService mContainerService = null;
-
- // Handler messages
- 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;
-
- class UnmountCallBack {
- final String path;
- final boolean force;
- final boolean removeEncryption;
- int retries;
-
- UnmountCallBack(String path, boolean force, boolean removeEncryption) {
- retries = 0;
- this.path = path;
- this.force = force;
- this.removeEncryption = removeEncryption;
- }
-
- void handleFinished() {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path);
- doUnmountVolume(path, true, removeEncryption);
- }
- }
-
- class UmsEnableCallBack extends UnmountCallBack {
- final String method;
-
- UmsEnableCallBack(String path, String method, boolean force) {
- super(path, force, false);
- this.method = method;
- }
-
- @Override
- void handleFinished() {
- super.handleFinished();
- doShareUnshareVolume(path, method, true);
- }
- }
-
- class ShutdownCallBack extends UnmountCallBack {
- IMountShutdownObserver observer;
- ShutdownCallBack(String path, IMountShutdownObserver observer) {
- super(path, true, false);
- this.observer = observer;
- }
-
- @Override
- void handleFinished() {
- int ret = doUnmountVolume(path, true, removeEncryption);
- if (observer != null) {
- try {
- observer.onShutDownComplete(ret);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException when shutting down");
- }
- }
- }
- }
-
- class MountServiceHandler extends Handler {
- ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
- boolean mUpdatingStatus = false;
-
- MountServiceHandler(Looper l) {
- super(l);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case H_UNMOUNT_PM_UPDATE: {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE");
- UnmountCallBack ucb = (UnmountCallBack) msg.obj;
- mForceUnmounts.add(ucb);
- if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus);
- // Register only if needed.
- if (!mUpdatingStatus) {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
- mUpdatingStatus = true;
- mPms.updateExternalMediaStatus(false, true);
- }
- break;
- }
- case H_UNMOUNT_PM_DONE: {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE");
- if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests");
- mUpdatingStatus = false;
- int size = mForceUnmounts.size();
- int sizeArr[] = new int[size];
- int sizeArrN = 0;
- // Kill processes holding references first
- ActivityManagerService ams = (ActivityManagerService)
- ServiceManager.getService("activity");
- for (int i = 0; i < size; i++) {
- UnmountCallBack ucb = mForceUnmounts.get(i);
- String path = ucb.path;
- boolean done = false;
- if (!ucb.force) {
- done = true;
- } else {
- int pids[] = getStorageUsers(path);
- if (pids == null || pids.length == 0) {
- done = true;
- } else {
- // Eliminate system process here?
- ams.killPids(pids, "unmount media", true);
- // Confirm if file references have been freed.
- pids = getStorageUsers(path);
- if (pids == null || pids.length == 0) {
- done = true;
- }
- }
- }
- if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
- // Retry again
- Slog.i(TAG, "Retrying to kill storage users again");
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
- ucb.retries++),
- RETRY_UNMOUNT_DELAY);
- } else {
- if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
- Slog.i(TAG, "Failed to unmount media inspite of " +
- MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
- }
- sizeArr[sizeArrN++] = i;
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
- ucb));
- }
- }
- // Remove already processed elements from list.
- for (int i = (sizeArrN-1); i >= 0; i--) {
- mForceUnmounts.remove(sizeArr[i]);
- }
- break;
- }
- 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;
- }
- }
- }
- };
-
- private final Handler mHandler;
-
- void waitForAsecScan() {
- waitForLatch(mAsecsScanned);
- }
-
- private void waitForReady() {
- waitForLatch(mConnectedSignal);
- }
-
- private void waitForLatch(CountDownLatch latch) {
- for (;;) {
- try {
- if (latch.await(5000, TimeUnit.MILLISECONDS)) {
- return;
- } else {
- Slog.w(TAG, "Thread " + Thread.currentThread().getName()
- + " still waiting for MountService ready...");
- }
- } catch (InterruptedException e) {
- Slog.w(TAG, "Interrupt while waiting for MountService to be ready.");
- }
- }
- }
-
- 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 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 BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- waitForReady();
- String action = intent.getAction();
- // Since fstrim will be run on a daily basis we do not expect
- // fstrim to be too long, so it is not interruptible. We will
- // implement interruption only in case we see issues.
- if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
- try {
- // This method runs on the handler thread,
- // so it is safe to directly call into vold.
- mConnector.execute("fstrim", "dotrim");
- EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
- } catch (NativeDaemonConnectorException ndce) {
- Slog.e(TAG, "Failed to run fstrim!");
- }
- }
- }
- };
-
- private final class MountServiceBinderListener implements IBinder.DeathRecipient {
- final IMountServiceListener mListener;
-
- MountServiceBinderListener(IMountServiceListener listener) {
- mListener = listener;
-
- }
-
- public void binderDied() {
- if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
- synchronized (mListeners) {
- mListeners.remove(this);
- mListener.asBinder().unlinkToDeath(this, 0);
- }
- }
- }
-
- private void doShareUnshareVolume(String path, String method, boolean enable) {
- // TODO: Add support for multiple share methods
- if (!method.equals("ums")) {
- throw new IllegalArgumentException(String.format("Method %s not supported", method));
- }
-
- try {
- mConnector.execute("volume", enable ? "share" : "unshare", path, method);
- } catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to share/unshare", e);
- }
- }
-
- private void updatePublicVolumeState(StorageVolume volume, String state) {
- final String path = volume.getPath();
- final String oldState;
- synchronized (mVolumesLock) {
- oldState = mVolumeStates.put(path, state);
- volume.setState(state);
- }
-
- if (state.equals(oldState)) {
- Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
- state, state, path));
- return;
- }
-
- Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
-
- // 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);
- }
- }
-
- synchronized (mListeners) {
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- bl.mListener.onStorageStateChanged(path, oldState, state);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Listener dead");
- mListeners.remove(i);
- } catch (Exception ex) {
- Slog.e(TAG, "Listener failed", ex);
- }
- }
- }
- }
-
- /**
- * Callback from NativeDaemonConnector
- */
- public void onDaemonConnected() {
- /*
- * Since we'll be calling back into the NativeDaemonConnector,
- * we need to do our work in a new thread.
- */
- new Thread("MountService#onDaemonConnected") {
- @Override
- public void run() {
- /**
- * Determine media state and UMS detection status
- */
- try {
- final String[] vols = NativeDaemonEvent.filterMessageList(
- mConnector.executeForList("volume", "list"),
- VoldResponseCode.VolumeListResult);
- for (String volstr : vols) {
- String[] tok = volstr.split(" ");
- // FMT: <label> <mountpoint> <state>
- 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;
- } else if (st == VolumeState.Idle) {
- state = Environment.MEDIA_UNMOUNTED;
- } else if (st == VolumeState.Mounted) {
- state = Environment.MEDIA_MOUNTED;
- Slog.i(TAG, "Media already mounted on daemon connection");
- } else if (st == VolumeState.Shared) {
- state = Environment.MEDIA_SHARED;
- Slog.i(TAG, "Media shared on daemon connection");
- } else {
- throw new Exception(String.format("Unexpected state %d", st));
- }
-
- if (state != null) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
- updatePublicVolumeState(volume, state);
- }
- }
- } catch (Exception e) {
- Slog.e(TAG, "Error processing initial volume state", e);
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null) {
- updatePublicVolumeState(primary, Environment.MEDIA_REMOVED);
- }
- }
-
- /*
- * Now that we've done our initialization, release
- * the hounds!
- */
- mConnectedSignal.countDown();
-
- // Let package manager load internal ASECs.
- mPms.scanAvailableAsecs();
-
- // Notify people waiting for ASECs to be scanned that it's done.
- mAsecsScanned.countDown();
- }
- }.start();
- }
-
- /**
- * Callback from NativeDaemonConnector
- */
- public boolean onEvent(int code, String raw, String[] cooked) {
- if (DEBUG_EVENTS) {
- StringBuilder builder = new StringBuilder();
- builder.append("onEvent::");
- builder.append(" raw= " + raw);
- if (cooked != null) {
- builder.append(" cooked = " );
- for (String str : cooked) {
- builder.append(" " + str);
- }
- }
- Slog.i(TAG, builder.toString());
- }
- if (code == VoldResponseCode.VolumeStateChange) {
- /*
- * One of the volumes we're managing has changed state.
- * Format: "NNN Volume <label> <path> state changed
- * from <old_#> (<old_str>) to <new_#> (<new_str>)"
- */
- notifyVolumeStateChange(
- cooked[2], cooked[3], Integer.parseInt(cooked[7]),
- Integer.parseInt(cooked[10]));
- } else if (code == VoldResponseCode.VolumeUuidChange) {
- // Format: nnn <label> <path> <uuid>
- final String path = cooked[2];
- final String uuid = (cooked.length > 3) ? cooked[3] : null;
-
- final StorageVolume vol = mVolumesByPath.get(path);
- if (vol != null) {
- vol.setUuid(uuid);
- }
-
- } else if (code == VoldResponseCode.VolumeUserLabelChange) {
- // Format: nnn <label> <path> <label>
- final String path = cooked[2];
- final String userLabel = (cooked.length > 3) ? cooked[3] : null;
-
- final StorageVolume vol = mVolumesByPath.get(path);
- if (vol != null) {
- vol.setUserLabel(userLabel);
- }
-
- } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
- (code == VoldResponseCode.VolumeDiskRemoved) ||
- (code == VoldResponseCode.VolumeBadRemoval)) {
- // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
- // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
- // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
- String action = null;
- final String label = cooked[2];
- final String path = cooked[3];
- int major = -1;
- int minor = -1;
-
- try {
- String devComp = cooked[6].substring(1, cooked[6].length() -1);
- String[] devTok = devComp.split(":");
- major = Integer.parseInt(devTok[0]);
- minor = Integer.parseInt(devTok[1]);
- } catch (Exception ex) {
- 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("MountService#VolumeDiskInserted") {
- @Override
- public void run() {
- try {
- int rc;
- if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
- Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
- }
- } catch (Exception ex) {
- Slog.w(TAG, "Failed to mount media on insertion", ex);
- }
- }
- }.start();
- } else if (code == VoldResponseCode.VolumeDiskRemoved) {
- /*
- * This event gets trumped if we're already in BAD_REMOVAL state
- */
- if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
- return true;
- }
- /* Send the media unmounted event first */
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Environment.MEDIA_UNMOUNTED, volume, UserHandle.ALL);
-
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending 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(volume, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
-
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
- updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL);
- action = Intent.ACTION_MEDIA_BAD_REMOVAL;
- } else if (code == VoldResponseCode.FstrimCompleted) {
- EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
- } else {
- Slog.e(TAG, String.format("Unknown code {%d}", code));
- }
-
- if (action != null) {
- sendStorageIntent(action, volume, UserHandle.ALL);
- }
- } else {
- return false;
- }
-
- return true;
- }
-
- private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
- 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, volume, UserHandle.ALL);
- }
-
- if (newState == VolumeState.Init) {
- } else if (newState == VolumeState.NoMedia) {
- // NoMedia is handled via Disk Remove events
- } else if (newState == VolumeState.Idle) {
- /*
- * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
- * if we're in the process of enabling UMS
- */
- 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(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(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(volume, Environment.MEDIA_MOUNTED);
- action = Intent.ACTION_MEDIA_MOUNTED;
- } else if (newState == VolumeState.Unmounting) {
- action = Intent.ACTION_MEDIA_EJECT;
- } else if (newState == VolumeState.Formatting) {
- } else if (newState == VolumeState.Shared) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
- /* Send the media unmounted event first */
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
-
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating 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) {
- Slog.e(TAG, "Live shared mounts not supported yet!");
- return;
- } else {
- Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
- }
-
- if (action != null) {
- 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);
- } catch (NativeDaemonConnectorException e) {
- /*
- * Mount failed for some reason
- */
- String action = null;
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedNoMedia) {
- /*
- * Attempt to mount but no media inserted
- */
- rc = StorageResultCode.OperationFailedNoMedia;
- } else if (code == VoldResponseCode.OpFailedMediaBlank) {
- if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs");
- /*
- * Media is blank or does not contain a supported filesystem
- */
- updatePublicVolumeState(volume, Environment.MEDIA_NOFS);
- action = Intent.ACTION_MEDIA_NOFS;
- rc = StorageResultCode.OperationFailedMediaBlank;
- } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
- if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
- /*
- * Volume consistency check failed
- */
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE);
- action = Intent.ACTION_MEDIA_UNMOUNTABLE;
- rc = StorageResultCode.OperationFailedMediaCorrupt;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
- }
-
- /*
- * Send broadcast intent (if required for the failure)
- */
- if (action != null) {
- sendStorageIntent(action, volume, UserHandle.ALL);
- }
- }
-
- return rc;
- }
-
- /*
- * If force is not set, we do not unmount if there are
- * processes holding references to the volume about to be unmounted.
- * If force is set, all the processes holding references need to be
- * killed via the ActivityManager before actually unmounting the volume.
- * This might even take a while and might be retried after timed delays
- * to make sure we dont end up in an instable state and kill some core
- * processes.
- * If removeEncryption is set, force is implied, and the system will remove any encryption
- * mapping set on the volume when unmounting.
- */
- private int doUnmountVolume(String path, boolean force, boolean removeEncryption) {
- if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
- return VoldResponseCode.OpFailedVolNotMounted;
- }
-
- /*
- * Force a GC to make sure AssetManagers in other threads of the
- * system_server are cleaned up. We have to do this since AssetManager
- * instances are kept as a WeakReference and it's possible we have files
- * open on the external storage.
- */
- Runtime.getRuntime().gc();
-
- // Redundant probably. But no harm in updating state again.
- mPms.updateExternalMediaStatus(false, false);
- try {
- final Command cmd = new Command("volume", "unmount", path);
- if (removeEncryption) {
- cmd.appendArg("force_and_revert");
- } else if (force) {
- cmd.appendArg("force");
- }
- mConnector.execute(cmd);
- // We unmounted the volume. None of the asec containers are available now.
- synchronized (mAsecMountSet) {
- mAsecMountSet.clear();
- }
- return StorageResultCode.OperationSucceeded;
- } catch (NativeDaemonConnectorException e) {
- // Don't worry about mismatch in PackageManager since the
- // call back will handle the status changes any way.
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedVolNotMounted) {
- return StorageResultCode.OperationFailedStorageNotMounted;
- } else if (code == VoldResponseCode.OpFailedStorageBusy) {
- return StorageResultCode.OperationFailedStorageBusy;
- } else {
- return StorageResultCode.OperationFailedInternalError;
- }
- }
- }
-
- private int doFormatVolume(String path) {
- try {
- mConnector.execute("volume", "format", path);
- return StorageResultCode.OperationSucceeded;
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedNoMedia) {
- return StorageResultCode.OperationFailedNoMedia;
- } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
- return StorageResultCode.OperationFailedMediaCorrupt;
- } else {
- return StorageResultCode.OperationFailedInternalError;
- }
- }
- }
-
- private boolean doGetVolumeShared(String path, String method) {
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("volume", "shared", path, method);
- } catch (NativeDaemonConnectorException ex) {
- Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method);
- return false;
- }
-
- if (event.getCode() == VoldResponseCode.ShareEnabledResult) {
- return event.getMessage().endsWith("enabled");
- } else {
- return false;
- }
- }
-
- private void notifyShareAvailabilityChange(final boolean avail) {
- synchronized (mListeners) {
- mUmsAvailable = avail;
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- bl.mListener.onUsbMassStorageConnectionChanged(avail);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Listener dead");
- mListeners.remove(i);
- } catch (Exception ex) {
- Slog.e(TAG, "Listener failed", ex);
- }
- }
- }
-
- if (mSystemReady == true) {
- sendUmsIntent(avail);
- } else {
- mSendUmsConnectedOnBoot = avail;
- }
-
- 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
- */
- new Thread("MountService#AvailabilityChange") {
- @Override
- public void run() {
- try {
- int rc;
- Slog.w(TAG, "Disabling UMS after cable disconnect");
- doShareUnshareVolume(path, "ums", false);
- if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
- Slog.e(TAG, String.format(
- "Failed to remount {%s} on UMS enabled-disconnect (%d)",
- path, rc));
- }
- } catch (Exception ex) {
- Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
- }
- }
- }.start();
- }
- }
-
- 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) {
- mContext.sendBroadcastAsUser(
- new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)),
- UserHandle.ALL);
- }
-
- private void validatePermission(String perm) {
- if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(String.format("Requires %s permission", perm));
- }
- }
-
- // Storage list XML tags
- private static final String TAG_STORAGE_LIST = "StorageList";
- private static final String TAG_STORAGE = "storage";
-
- private void readStorageListLocked() {
- mVolumes.clear();
- mVolumeStates.clear();
-
- Resources resources = mContext.getResources();
-
- int id = com.android.internal.R.xml.storage_list;
- XmlResourceParser parser = resources.getXml(id);
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- try {
- XmlUtils.beginDocument(parser, TAG_STORAGE_LIST);
- while (true) {
- XmlUtils.nextElement(parser);
-
- String element = parser.getName();
- if (element == null) break;
-
- if (TAG_STORAGE.equals(element)) {
- TypedArray a = resources.obtainAttributes(attrs,
- com.android.internal.R.styleable.Storage);
-
- String path = a.getString(
- com.android.internal.R.styleable.Storage_mountPoint);
- int descriptionId = a.getResourceId(
- com.android.internal.R.styleable.Storage_storageDescription, -1);
- CharSequence description = a.getText(
- com.android.internal.R.styleable.Storage_storageDescription);
- boolean primary = a.getBoolean(
- com.android.internal.R.styleable.Storage_primary, false);
- boolean removable = a.getBoolean(
- com.android.internal.R.styleable.Storage_removable, false);
- boolean emulated = a.getBoolean(
- com.android.internal.R.styleable.Storage_emulated, false);
- int mtpReserve = a.getInt(
- com.android.internal.R.styleable.Storage_mtpReserve, 0);
- boolean allowMassStorage = a.getBoolean(
- com.android.internal.R.styleable.Storage_allowMassStorage, false);
- // resource parser does not support longs, so XML value is in megabytes
- long maxFileSize = a.getInt(
- com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L;
-
- Slog.d(TAG, "got storage path: " + path + " description: " + description +
- " primary: " + primary + " removable: " + removable +
- " emulated: " + emulated + " mtpReserve: " + mtpReserve +
- " allowMassStorage: " + allowMassStorage +
- " maxFileSize: " + maxFileSize);
-
- 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(false)) {
- createEmulatedVolumeForUserLocked(user.getUserHandle());
- }
-
- } else {
- if (path == null || description == null) {
- Slog.e(TAG, "Missing storage path or description in readStorageList");
- } else {
- final StorageVolume volume = new StorageVolume(new File(path),
- descriptionId, primary, removable, emulated, mtpReserve,
- allowMassStorage, maxFileSize, null);
- addVolumeLocked(volume);
-
- // Until we hear otherwise, treat as unmounted
- mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
- volume.setState(Environment.MEDIA_UNMOUNTED);
- }
- }
-
- a.recycle();
- }
- }
- } catch (XmlPullParserException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- // 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);
- volume.setState(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;
-
- synchronized (mVolumesLock) {
- readStorageListLocked();
- }
-
- // XXX: This will go away soon in favor of IMountServiceObserver
- mPms = (PackageManagerService) ServiceManager.getService("package");
-
- HandlerThread hthread = new HandlerThread(TAG);
- hthread.start();
- mHandler = new MountServiceHandler(hthread.getLooper());
-
- // 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);
- }
-
- // Watch for idle maintenance changes
- IntentFilter idleMaintenanceFilter = new IntentFilter();
- idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
- mContext.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL,
- idleMaintenanceFilter, null, mHandler);
-
- // Add OBB Action Handler to MountService thread.
- mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
-
- /*
- * Create the connection to vold with a maximum queue of twice the
- * amount of containers we'd ever expect to have. This keeps an
- * "asec list" from blocking a thread repeatedly.
- */
- mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25);
-
- Thread thread = new Thread(mConnector, VOLD_TAG);
- thread.start();
-
- // Add ourself to the Watchdog monitors if enabled.
- if (WATCHDOG_ENABLE) {
- Watchdog.getInstance().addMonitor(this);
- }
- }
-
- public void systemReady() {
- mSystemReady = true;
- mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
- }
-
- /**
- * Exposed API calls below here
- */
-
- public void registerListener(IMountServiceListener listener) {
- synchronized (mListeners) {
- MountServiceBinderListener bl = new MountServiceBinderListener(listener);
- try {
- listener.asBinder().linkToDeath(bl, 0);
- mListeners.add(bl);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Failed to link to listener death");
- }
- }
- }
-
- public void unregisterListener(IMountServiceListener listener) {
- synchronized (mListeners) {
- for(MountServiceBinderListener bl : mListeners) {
- if (bl.mListener == listener) {
- mListeners.remove(mListeners.indexOf(bl));
- listener.asBinder().unlinkToDeath(bl, 0);
- return;
- }
- }
- }
- }
-
- public void shutdown(final IMountShutdownObserver observer) {
- validatePermission(android.Manifest.permission.SHUTDOWN);
-
- Slog.i(TAG, "Shutting down");
- synchronized (mVolumesLock) {
- for (String path : mVolumeStates.keySet()) {
- String state = mVolumeStates.get(path);
-
- if (state.equals(Environment.MEDIA_SHARED)) {
- /*
- * If the media is currently shared, unshare it.
- * XXX: This is still dangerous!. We should not
- * be rebooting at *all* if UMS is enabled, since
- * the UMS host could have dirty FAT cache entries
- * yet to flush.
- */
- setUsbMassStorageEnabled(false);
- } else if (state.equals(Environment.MEDIA_CHECKING)) {
- /*
- * If the media is being checked, then we need to wait for
- * it to complete before being able to proceed.
- */
- // XXX: @hackbod - Should we disable the ANR timer here?
- int retries = 30;
- while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException iex) {
- Slog.e(TAG, "Interrupted while waiting for media", iex);
- break;
- }
- state = Environment.getExternalStorageState();
- }
- if (retries == 0) {
- Slog.e(TAG, "Timed out waiting for media to check");
- }
- }
-
- if (state.equals(Environment.MEDIA_MOUNTED)) {
- // Post a unmount message.
- ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
- } else if (observer != null) {
- /*
- * Observer is waiting for onShutDownComplete when we are done.
- * Since nothing will be done send notification directly so shutdown
- * sequence can continue.
- */
- try {
- observer.onShutDownComplete(StorageResultCode.OperationSucceeded);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException when shutting down");
- }
- }
- }
- }
- }
-
- private boolean getUmsEnabling() {
- synchronized (mListeners) {
- return mUmsEnabling;
- }
- }
-
- private void setUmsEnabling(boolean enable) {
- synchronized (mListeners) {
- mUmsEnabling = enable;
- }
- }
-
- public boolean isUsbMassStorageConnected() {
- waitForReady();
-
- if (getUmsEnabling()) {
- return true;
- }
- synchronized (mListeners) {
- return mUmsAvailable;
- }
- }
-
- public void setUsbMassStorageEnabled(boolean enable) {
- 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 = primary.getPath();
- String vs = getVolumeState(path);
- String method = "ums";
- if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
- // Override for isUsbMassStorageEnabled()
- setUmsEnabling(enable);
- UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
- // Clear override
- setUmsEnabling(false);
- }
- /*
- * If we disabled UMS then mount the volume
- */
- if (!enable) {
- doShareUnshareVolume(path, method, enable);
- if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
- Slog.e(TAG, "Failed to remount " + path +
- " after disabling share method " + method);
- /*
- * Even though the mount failed, the unshare didn't so don't indicate an error.
- * The mountVolume() call will have set the storage state and sent the necessary
- * broadcasts.
- */
- }
- }
- }
-
- public boolean isUsbMassStorageEnabled() {
- waitForReady();
-
- 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 (mVolumesLock) {
- String state = mVolumeStates.get(mountPoint);
- if (state == null) {
- Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
- if (SystemProperties.get("vold.encrypt_progress").length() != 0) {
- state = Environment.MEDIA_REMOVED;
- } else {
- throw new IllegalArgumentException();
- }
- }
-
- return state;
- }
- }
-
- @Override
- public boolean isExternalStorageEmulated() {
- return mEmulatedTemplate != null;
- }
-
- public int mountVolume(String path) {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-
- waitForReady();
- return doMountVolume(path);
- }
-
- public void unmountVolume(String path, boolean force, boolean removeEncryption) {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
-
- String volState = getVolumeState(path);
- if (DEBUG_UNMOUNT) {
- Slog.i(TAG, "Unmounting " + path
- + " force = " + force
- + " removeEncryption = " + removeEncryption);
- }
- if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
- Environment.MEDIA_REMOVED.equals(volState) ||
- Environment.MEDIA_SHARED.equals(volState) ||
- Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
- // Media already unmounted or cannot be unmounted.
- // TODO return valid return code when adding observer call back.
- return;
- }
- UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
- }
-
- public int formatVolume(String path) {
- validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
- waitForReady();
-
- return doFormatVolume(path);
- }
-
- public int[] getStorageUsers(String path) {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
- try {
- final String[] r = NativeDaemonEvent.filterMessageList(
- mConnector.executeForList("storage", "users", path),
- VoldResponseCode.StorageUsersListResult);
-
- // FMT: <pid> <process name>
- int[] data = new int[r.length];
- for (int i = 0; i < r.length; i++) {
- String[] tok = r[i].split(" ");
- try {
- data[i] = Integer.parseInt(tok[0]);
- } catch (NumberFormatException nfe) {
- Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
- return new int[0];
- }
- }
- return data;
- } catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to retrieve storage users list", e);
- return new int[0];
- }
- }
-
- private void warnOnNotMounted() {
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null) {
- boolean mounted = false;
- try {
- mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()));
- } catch (IllegalArgumentException e) {
- }
-
- if (!mounted) {
- Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
- }
- }
- }
-
- public String[] getSecureContainerList() {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
- waitForReady();
- warnOnNotMounted();
-
- try {
- return NativeDaemonEvent.filterMessageList(
- mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
- } catch (NativeDaemonConnectorException e) {
- return new String[0];
- }
- }
-
- public int createSecureContainer(String id, int sizeMb, String fstype, String key,
- int ownerUid, boolean external) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
- waitForReady();
- warnOnNotMounted();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key),
- ownerUid, external ? "1" : "0");
- } catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- synchronized (mAsecMountSet) {
- mAsecMountSet.add(id);
- }
- }
- return rc;
- }
-
- public int finalizeSecureContainer(String id) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
- warnOnNotMounted();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "finalize", id);
- /*
- * Finalization does a remount, so no need
- * to update mAsecMountSet
- */
- } catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- return rc;
- }
-
- public int fixPermissionsSecureContainer(String id, int gid, String filename) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
- warnOnNotMounted();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "fixperms", id, gid, filename);
- /*
- * Fix permissions does a remount, so no need to update
- * mAsecMountSet
- */
- } catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- return rc;
- }
-
- public int destroySecureContainer(String id, boolean force) {
- validatePermission(android.Manifest.permission.ASEC_DESTROY);
- waitForReady();
- warnOnNotMounted();
-
- /*
- * Force a GC to make sure AssetManagers in other threads of the
- * system_server are cleaned up. We have to do this since AssetManager
- * instances are kept as a WeakReference and it's possible we have files
- * open on the external storage.
- */
- Runtime.getRuntime().gc();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- final Command cmd = new Command("asec", "destroy", id);
- if (force) {
- cmd.appendArg("force");
- }
- mConnector.execute(cmd);
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedStorageBusy;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- synchronized (mAsecMountSet) {
- if (mAsecMountSet.contains(id)) {
- mAsecMountSet.remove(id);
- }
- }
- }
-
- return rc;
- }
-
- public int mountSecureContainer(String id, String key, int ownerUid) {
- validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
- waitForReady();
- warnOnNotMounted();
-
- synchronized (mAsecMountSet) {
- if (mAsecMountSet.contains(id)) {
- return StorageResultCode.OperationFailedStorageMounted;
- }
- }
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid);
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code != VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- synchronized (mAsecMountSet) {
- mAsecMountSet.add(id);
- }
- }
- return rc;
- }
-
- public int unmountSecureContainer(String id, boolean force) {
- validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
- waitForReady();
- warnOnNotMounted();
-
- synchronized (mAsecMountSet) {
- if (!mAsecMountSet.contains(id)) {
- return StorageResultCode.OperationFailedStorageNotMounted;
- }
- }
-
- /*
- * Force a GC to make sure AssetManagers in other threads of the
- * system_server are cleaned up. We have to do this since AssetManager
- * instances are kept as a WeakReference and it's possible we have files
- * open on the external storage.
- */
- Runtime.getRuntime().gc();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- final Command cmd = new Command("asec", "unmount", id);
- if (force) {
- cmd.appendArg("force");
- }
- mConnector.execute(cmd);
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedStorageBusy;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- synchronized (mAsecMountSet) {
- mAsecMountSet.remove(id);
- }
- }
- return rc;
- }
-
- public boolean isSecureContainerMounted(String id) {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
- waitForReady();
- warnOnNotMounted();
-
- synchronized (mAsecMountSet) {
- return mAsecMountSet.contains(id);
- }
- }
-
- public int renameSecureContainer(String oldId, String newId) {
- validatePermission(android.Manifest.permission.ASEC_RENAME);
- waitForReady();
- warnOnNotMounted();
-
- synchronized (mAsecMountSet) {
- /*
- * Because a mounted container has active internal state which cannot be
- * changed while active, we must ensure both ids are not currently mounted.
- */
- if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
- return StorageResultCode.OperationFailedStorageMounted;
- }
- }
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "rename", oldId, newId);
- } catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
-
- return rc;
- }
-
- public String getSecureContainerPath(String id) {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
- waitForReady();
- warnOnNotMounted();
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("asec", "path", id);
- event.checkCode(VoldResponseCode.AsecPathResult);
- return event.getMessage();
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageNotFound) {
- Slog.i(TAG, String.format("Container '%s' not found", id));
- return null;
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
- }
-
- public String getSecureContainerFilesystemPath(String id) {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
- waitForReady();
- warnOnNotMounted();
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("asec", "fspath", id);
- event.checkCode(VoldResponseCode.AsecPathResult);
- return event.getMessage();
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageNotFound) {
- Slog.i(TAG, String.format("Container '%s' not found", id));
- return null;
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
- }
-
- public void finishMediaUpdate() {
- mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
- }
-
- private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
- if (callerUid == android.os.Process.SYSTEM_UID) {
- return true;
- }
-
- if (packageName == null) {
- return false;
- }
-
- final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid));
-
- if (DEBUG_OBB) {
- Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
- packageUid + ", callerUid = " + callerUid);
- }
-
- return callerUid == packageUid;
- }
-
- public String getMountedObbPath(String rawPath) {
- Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
-
- waitForReady();
- warnOnNotMounted();
-
- final ObbState state;
- synchronized (mObbPathToStateMap) {
- state = mObbPathToStateMap.get(rawPath);
- }
- if (state == null) {
- Slog.w(TAG, "Failed to find OBB mounted at " + rawPath);
- return null;
- }
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("obb", "path", state.voldPath);
- event.checkCode(VoldResponseCode.AsecPathResult);
- return event.getMessage();
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageNotFound) {
- return null;
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
- }
-
- @Override
- public boolean isObbMounted(String rawPath) {
- Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
- synchronized (mObbMounts) {
- return mObbPathToStateMap.containsKey(rawPath);
- }
- }
-
- @Override
- public void mountObb(
- String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) {
- Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
- Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null");
- Preconditions.checkNotNull(token, "token cannot be null");
-
- final int callingUid = Binder.getCallingUid();
- final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce);
- final ObbAction action = new MountObbAction(obbState, key, callingUid);
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
-
- if (DEBUG_OBB)
- Slog.i(TAG, "Send to OBB handler: " + action.toString());
- }
-
- @Override
- public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) {
- Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
-
- final ObbState existingState;
- synchronized (mObbPathToStateMap) {
- existingState = mObbPathToStateMap.get(rawPath);
- }
-
- if (existingState != null) {
- // TODO: separate state object from request data
- final int callingUid = Binder.getCallingUid();
- final ObbState newState = new ObbState(
- rawPath, existingState.canonicalPath, callingUid, token, nonce);
- final ObbAction action = new UnmountObbAction(newState, force);
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
-
- if (DEBUG_OBB)
- Slog.i(TAG, "Send to OBB handler: " + action.toString());
- } else {
- Slog.w(TAG, "Unknown OBB mount at " + rawPath);
- }
- }
-
- @Override
- public int getEncryptionState() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
- "no permission to access the crypt keeper");
-
- waitForReady();
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("cryptfs", "cryptocomplete");
- return Integer.parseInt(event.getMessage());
- } catch (NumberFormatException e) {
- // Bad result - unexpected.
- Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
- return ENCRYPTION_STATE_ERROR_UNKNOWN;
- } catch (NativeDaemonConnectorException e) {
- // Something bad happened.
- Slog.w(TAG, "Error in communicating with cryptfs in validating");
- return ENCRYPTION_STATE_ERROR_UNKNOWN;
- }
- }
-
- @Override
- public int decryptStorage(String password) {
- if (TextUtils.isEmpty(password)) {
- throw new IllegalArgumentException("password cannot be empty");
- }
-
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
- "no permission to access the crypt keeper");
-
- waitForReady();
-
- if (DEBUG_EVENTS) {
- Slog.i(TAG, "decrypting storage...");
- }
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("cryptfs", "checkpw", new SensitiveArg(password));
-
- final int code = Integer.parseInt(event.getMessage());
- if (code == 0) {
- // Decrypt was successful. Post a delayed message before restarting in order
- // to let the UI to clear itself
- mHandler.postDelayed(new Runnable() {
- public void run() {
- try {
- mConnector.execute("cryptfs", "restart");
- } catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "problem executing in background", e);
- }
- }
- }, 1000); // 1 second
- }
-
- return code;
- } catch (NativeDaemonConnectorException e) {
- // Decryption failed
- return e.getCode();
- }
- }
-
- public int encryptStorage(String password) {
- if (TextUtils.isEmpty(password)) {
- throw new IllegalArgumentException("password cannot be empty");
- }
-
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
- "no permission to access the crypt keeper");
-
- waitForReady();
-
- if (DEBUG_EVENTS) {
- Slog.i(TAG, "encrypting storage...");
- }
-
- try {
- mConnector.execute("cryptfs", "enablecrypto", "inplace", new SensitiveArg(password));
- } catch (NativeDaemonConnectorException e) {
- // Encryption failed
- return e.getCode();
- }
-
- return 0;
- }
-
- public int changeEncryptionPassword(String password) {
- if (TextUtils.isEmpty(password)) {
- throw new IllegalArgumentException("password cannot be empty");
- }
-
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
- "no permission to access the crypt keeper");
-
- waitForReady();
-
- if (DEBUG_EVENTS) {
- Slog.i(TAG, "changing encryption password...");
- }
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("cryptfs", "changepw", new SensitiveArg(password));
- return Integer.parseInt(event.getMessage());
- } catch (NativeDaemonConnectorException e) {
- // Encryption failed
- return e.getCode();
- }
- }
-
- /**
- * Validate a user-supplied password string with cryptfs
- */
- @Override
- public int verifyEncryptionPassword(String password) throws RemoteException {
- // Only the system process is permitted to validate passwords
- if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
- throw new SecurityException("no permission to access the crypt keeper");
- }
-
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
- "no permission to access the crypt keeper");
-
- if (TextUtils.isEmpty(password)) {
- throw new IllegalArgumentException("password cannot be empty");
- }
-
- waitForReady();
-
- if (DEBUG_EVENTS) {
- Slog.i(TAG, "validating encryption password...");
- }
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("cryptfs", "verifypw", new SensitiveArg(password));
- Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
- return Integer.parseInt(event.getMessage());
- } catch (NativeDaemonConnectorException e) {
- // Encryption failed
- return e.getCode();
- }
- }
-
- @Override
- public int mkdirs(String callingPkg, String appPath) {
- final int userId = UserHandle.getUserId(Binder.getCallingUid());
- final UserEnvironment userEnv = new UserEnvironment(userId);
-
- // Validate that reported package name belongs to caller
- final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
- Context.APP_OPS_SERVICE);
- appOps.checkPackage(Binder.getCallingUid(), callingPkg);
-
- try {
- appPath = new File(appPath).getCanonicalPath();
- } catch (IOException e) {
- Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
- return -1;
- }
-
- if (!appPath.endsWith("/")) {
- appPath = appPath + "/";
- }
-
- // Try translating the app path into a vold path, but require that it
- // belong to the calling package.
- String voldPath = maybeTranslatePathForVold(appPath,
- userEnv.buildExternalStorageAppDataDirs(callingPkg),
- userEnv.buildExternalStorageAppDataDirsForVold(callingPkg));
- if (voldPath != null) {
- try {
- mConnector.execute("volume", "mkdirs", voldPath);
- return 0;
- } catch (NativeDaemonConnectorException e) {
- return e.getCode();
- }
- }
-
- voldPath = maybeTranslatePathForVold(appPath,
- userEnv.buildExternalStorageAppObbDirs(callingPkg),
- userEnv.buildExternalStorageAppObbDirsForVold(callingPkg));
- if (voldPath != null) {
- try {
- mConnector.execute("volume", "mkdirs", voldPath);
- return 0;
- } catch (NativeDaemonConnectorException e) {
- return e.getCode();
- }
- }
-
- throw new SecurityException("Invalid mkdirs path: " + appPath);
- }
-
- /**
- * Translate the given path from an app-visible path to a vold-visible path,
- * but only if it's under the given whitelisted paths.
- *
- * @param path a canonicalized app-visible path.
- * @param appPaths list of app-visible paths that are allowed.
- * @param voldPaths list of vold-visible paths directly corresponding to the
- * allowed app-visible paths argument.
- * @return a vold-visible path representing the original path, or
- * {@code null} if the given path didn't have an app-to-vold
- * mapping.
- */
- @VisibleForTesting
- public static String maybeTranslatePathForVold(
- String path, File[] appPaths, File[] voldPaths) {
- if (appPaths.length != voldPaths.length) {
- throw new IllegalStateException("Paths must be 1:1 mapping");
- }
-
- for (int i = 0; i < appPaths.length; i++) {
- final String appPath = appPaths[i].getAbsolutePath() + "/";
- if (path.startsWith(appPath)) {
- path = new File(voldPaths[i], path.substring(appPath.length()))
- .getAbsolutePath();
- if (!path.endsWith("/")) {
- path = path + "/";
- }
- return path;
- }
- }
- return null;
- }
-
- @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 filtered.toArray(new StorageVolume[filtered.size()]);
- }
- }
-
- private void addObbStateLocked(ObbState obbState) throws RemoteException {
- final IBinder binder = obbState.getBinder();
- List<ObbState> obbStates = mObbMounts.get(binder);
-
- if (obbStates == null) {
- obbStates = new ArrayList<ObbState>();
- mObbMounts.put(binder, obbStates);
- } else {
- for (final ObbState o : obbStates) {
- if (o.rawPath.equals(obbState.rawPath)) {
- throw new IllegalStateException("Attempt to add ObbState twice. "
- + "This indicates an error in the MountService logic.");
- }
- }
- }
-
- obbStates.add(obbState);
- try {
- obbState.link();
- } catch (RemoteException e) {
- /*
- * The binder died before we could link it, so clean up our state
- * and return failure.
- */
- obbStates.remove(obbState);
- if (obbStates.isEmpty()) {
- mObbMounts.remove(binder);
- }
-
- // Rethrow the error so mountObb can get it
- throw e;
- }
-
- mObbPathToStateMap.put(obbState.rawPath, obbState);
- }
-
- private void removeObbStateLocked(ObbState obbState) {
- final IBinder binder = obbState.getBinder();
- final List<ObbState> obbStates = mObbMounts.get(binder);
- if (obbStates != null) {
- if (obbStates.remove(obbState)) {
- obbState.unlink();
- }
- if (obbStates.isEmpty()) {
- mObbMounts.remove(binder);
- }
- }
-
- mObbPathToStateMap.remove(obbState.rawPath);
- }
-
- private class ObbActionHandler extends Handler {
- private boolean mBound = false;
- private final List<ObbAction> mActions = new LinkedList<ObbAction>();
-
- ObbActionHandler(Looper l) {
- super(l);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case OBB_RUN_ACTION: {
- final ObbAction action = (ObbAction) msg.obj;
-
- if (DEBUG_OBB)
- Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
-
- // If a bind was already initiated we don't really
- // need to do anything. The pending install
- // will be processed later on.
- if (!mBound) {
- // If this is the only one pending we might
- // have to bind to the service again.
- if (!connectToService()) {
- Slog.e(TAG, "Failed to bind to media container service");
- action.handleError();
- return;
- }
- }
-
- mActions.add(action);
- break;
- }
- case OBB_MCS_BOUND: {
- if (DEBUG_OBB)
- Slog.i(TAG, "OBB_MCS_BOUND");
- if (msg.obj != null) {
- mContainerService = (IMediaContainerService) msg.obj;
- }
- if (mContainerService == null) {
- // Something seriously wrong. Bail out
- Slog.e(TAG, "Cannot bind to media container service");
- for (ObbAction action : mActions) {
- // Indicate service bind error
- action.handleError();
- }
- mActions.clear();
- } else if (mActions.size() > 0) {
- final ObbAction action = mActions.get(0);
- if (action != null) {
- action.execute(this);
- }
- } else {
- // Should never happen ideally.
- Slog.w(TAG, "Empty queue");
- }
- break;
- }
- case OBB_MCS_RECONNECT: {
- if (DEBUG_OBB)
- Slog.i(TAG, "OBB_MCS_RECONNECT");
- if (mActions.size() > 0) {
- if (mBound) {
- disconnectService();
- }
- if (!connectToService()) {
- Slog.e(TAG, "Failed to bind to media container service");
- for (ObbAction action : mActions) {
- // Indicate service bind error
- action.handleError();
- }
- mActions.clear();
- }
- }
- break;
- }
- case OBB_MCS_UNBIND: {
- if (DEBUG_OBB)
- Slog.i(TAG, "OBB_MCS_UNBIND");
-
- // Delete pending install
- if (mActions.size() > 0) {
- mActions.remove(0);
- }
- if (mActions.size() == 0) {
- if (mBound) {
- disconnectService();
- }
- } else {
- // There are more pending requests in queue.
- // Just post MCS_BOUND message to trigger processing
- // of next pending install.
- mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
- }
- break;
- }
- case OBB_FLUSH_MOUNT_STATE: {
- final String path = (String) msg.obj;
-
- if (DEBUG_OBB)
- Slog.i(TAG, "Flushing all OBB state for path " + path);
-
- synchronized (mObbMounts) {
- final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
-
- final Iterator<ObbState> i = mObbPathToStateMap.values().iterator();
- while (i.hasNext()) {
- final ObbState state = i.next();
-
- /*
- * If this entry's source file is in the volume path
- * that got unmounted, remove it because it's no
- * longer valid.
- */
- if (state.canonicalPath.startsWith(path)) {
- obbStatesToRemove.add(state);
- }
- }
-
- for (final ObbState obbState : obbStatesToRemove) {
- if (DEBUG_OBB)
- Slog.i(TAG, "Removing state for " + obbState.rawPath);
-
- removeObbStateLocked(obbState);
-
- try {
- obbState.token.onObbResult(obbState.rawPath, obbState.nonce,
- OnObbStateChangeListener.UNMOUNTED);
- } catch (RemoteException e) {
- Slog.i(TAG, "Couldn't send unmount notification for OBB: "
- + obbState.rawPath);
- }
- }
- }
- break;
- }
- }
- }
-
- private boolean connectToService() {
- if (DEBUG_OBB)
- Slog.i(TAG, "Trying to bind to DefaultContainerService");
-
- Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
- if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
- mBound = true;
- return true;
- }
- return false;
- }
-
- private void disconnectService() {
- mContainerService = null;
- mBound = false;
- mContext.unbindService(mDefContainerConn);
- }
- }
-
- abstract class ObbAction {
- private static final int MAX_RETRIES = 3;
- private int mRetries;
-
- ObbState mObbState;
-
- ObbAction(ObbState obbState) {
- mObbState = obbState;
- }
-
- public void execute(ObbActionHandler handler) {
- try {
- if (DEBUG_OBB)
- Slog.i(TAG, "Starting to execute action: " + toString());
- mRetries++;
- if (mRetries > MAX_RETRIES) {
- Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
- mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
- handleError();
- return;
- } else {
- handleExecute();
- if (DEBUG_OBB)
- Slog.i(TAG, "Posting install MCS_UNBIND");
- mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
- }
- } catch (RemoteException e) {
- if (DEBUG_OBB)
- Slog.i(TAG, "Posting install MCS_RECONNECT");
- mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT);
- } catch (Exception e) {
- if (DEBUG_OBB)
- Slog.d(TAG, "Error handling OBB action", e);
- handleError();
- mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
- }
- }
-
- abstract void handleExecute() throws RemoteException, IOException;
- abstract void handleError();
-
- protected ObbInfo getObbInfo() throws IOException {
- ObbInfo obbInfo;
- try {
- obbInfo = mContainerService.getObbInfo(mObbState.ownerPath);
- } catch (RemoteException e) {
- Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
- + mObbState.ownerPath);
- obbInfo = null;
- }
- if (obbInfo == null) {
- throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath);
- }
- return obbInfo;
- }
-
- protected void sendNewStatusOrIgnore(int status) {
- if (mObbState == null || mObbState.token == null) {
- return;
- }
-
- try {
- mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status);
- } catch (RemoteException e) {
- Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
- }
- }
- }
-
- class MountObbAction extends ObbAction {
- private final String mKey;
- private final int mCallingUid;
-
- MountObbAction(ObbState obbState, String key, int callingUid) {
- super(obbState);
- mKey = key;
- mCallingUid = callingUid;
- }
-
- @Override
- public void handleExecute() throws IOException, RemoteException {
- waitForReady();
- warnOnNotMounted();
-
- final ObbInfo obbInfo = getObbInfo();
-
- if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) {
- Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename
- + " which is owned by " + obbInfo.packageName);
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
- return;
- }
-
- final boolean isMounted;
- synchronized (mObbMounts) {
- isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath);
- }
- if (isMounted) {
- Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename);
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED);
- return;
- }
-
- final String hashedKey;
- if (mKey == null) {
- hashedKey = "none";
- } else {
- try {
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
-
- KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt,
- PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE);
- SecretKey key = factory.generateSecret(ks);
- BigInteger bi = new BigInteger(key.getEncoded());
- hashedKey = bi.toString(16);
- } catch (NoSuchAlgorithmException e) {
- Slog.e(TAG, "Could not load PBKDF2 algorithm", e);
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
- return;
- } catch (InvalidKeySpecException e) {
- Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e);
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
- return;
- }
- }
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("obb", "mount", mObbState.voldPath, new SensitiveArg(hashedKey),
- mObbState.ownerGid);
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code != VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- if (DEBUG_OBB)
- Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath);
-
- synchronized (mObbMounts) {
- addObbStateLocked(mObbState);
- }
-
- sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED);
- } else {
- Slog.e(TAG, "Couldn't mount OBB file: " + rc);
-
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT);
- }
- }
-
- @Override
- public void handleError() {
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("MountObbAction{");
- sb.append(mObbState);
- sb.append('}');
- return sb.toString();
- }
- }
-
- class UnmountObbAction extends ObbAction {
- private final boolean mForceUnmount;
-
- UnmountObbAction(ObbState obbState, boolean force) {
- super(obbState);
- mForceUnmount = force;
- }
-
- @Override
- public void handleExecute() throws IOException {
- waitForReady();
- warnOnNotMounted();
-
- final ObbInfo obbInfo = getObbInfo();
-
- final ObbState existingState;
- synchronized (mObbMounts) {
- existingState = mObbPathToStateMap.get(mObbState.rawPath);
- }
-
- if (existingState == null) {
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED);
- return;
- }
-
- if (existingState.ownerGid != mObbState.ownerGid) {
- Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath
- + " (owned by GID " + existingState.ownerGid + ")");
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
- return;
- }
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- final Command cmd = new Command("obb", "unmount", mObbState.voldPath);
- if (mForceUnmount) {
- cmd.appendArg("force");
- }
- mConnector.execute(cmd);
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedStorageBusy;
- } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
- // If it's not mounted then we've already won.
- rc = StorageResultCode.OperationSucceeded;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- synchronized (mObbMounts) {
- removeObbStateLocked(existingState);
- }
-
- sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED);
- } else {
- Slog.w(TAG, "Could not unmount OBB: " + existingState);
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT);
- }
- }
-
- @Override
- public void handleError() {
- sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("UnmountObbAction{");
- sb.append(mObbState);
- sb.append(",force=");
- sb.append(mForceUnmount);
- sb.append('}');
- return sb.toString();
- }
- }
-
- @VisibleForTesting
- public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) {
- // TODO: allow caller to provide Environment for full testing
- // TODO: extend to support OBB mounts on secondary external storage
-
- // Only adjust paths when storage is emulated
- if (!Environment.isExternalStorageEmulated()) {
- return canonicalPath;
- }
-
- String path = canonicalPath.toString();
-
- // First trim off any external storage prefix
- final UserEnvironment userEnv = new UserEnvironment(userId);
-
- // /storage/emulated/0
- final String externalPath = userEnv.getExternalStorageDirectory().getAbsolutePath();
- // /storage/emulated_legacy
- final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory()
- .getAbsolutePath();
-
- if (path.startsWith(externalPath)) {
- path = path.substring(externalPath.length() + 1);
- } else if (path.startsWith(legacyExternalPath)) {
- path = path.substring(legacyExternalPath.length() + 1);
- } else {
- return canonicalPath;
- }
-
- // Handle special OBB paths on emulated storage
- final String obbPath = "Android/obb";
- if (path.startsWith(obbPath)) {
- path = path.substring(obbPath.length() + 1);
-
- if (forVold) {
- return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath();
- } else {
- final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
- return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
- .getAbsolutePath();
- }
- }
-
- // Handle normal external storage paths
- if (forVold) {
- return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
- } else {
- return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath();
- }
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160);
-
- synchronized (mObbMounts) {
- pw.println("mObbMounts:");
- pw.increaseIndent();
- final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet()
- .iterator();
- while (binders.hasNext()) {
- Entry<IBinder, List<ObbState>> e = binders.next();
- pw.println(e.getKey() + ":");
- pw.increaseIndent();
- final List<ObbState> obbStates = e.getValue();
- for (final ObbState obbState : obbStates) {
- pw.println(obbState);
- }
- pw.decreaseIndent();
- }
- pw.decreaseIndent();
-
- pw.println();
- pw.println("mObbPathToStateMap:");
- pw.increaseIndent();
- final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
- while (maps.hasNext()) {
- final Entry<String, ObbState> e = maps.next();
- pw.print(e.getKey());
- pw.print(" -> ");
- pw.println(e.getValue());
- }
- pw.decreaseIndent();
- }
-
- synchronized (mVolumesLock) {
- pw.println();
- pw.println("mVolumes:");
- pw.increaseIndent();
- for (StorageVolume volume : mVolumes) {
- pw.println(volume);
- pw.increaseIndent();
- pw.println("Current state: " + mVolumeStates.get(volume.getPath()));
- pw.decreaseIndent();
- }
- pw.decreaseIndent();
- }
-
- pw.println();
- pw.println("mConnection:");
- pw.increaseIndent();
- mConnector.dump(fd, pw, args);
- pw.decreaseIndent();
- }
-
- /** {@inheritDoc} */
- public void monitor() {
- if (mConnector != null) {
- mConnector.monitor();
- }
- }
-}