diff options
Diffstat (limited to 'services/java/com/android/server/MountService.java')
-rw-r--r-- | services/java/com/android/server/MountService.java | 2833 |
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(); - } - } -} |