2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.server;
19 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
21 import android.Manifest;
22 import android.app.AppOpsManager;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.ServiceConnection;
29 import android.content.pm.PackageManager;
30 import android.content.pm.UserInfo;
31 import android.content.res.ObbInfo;
32 import android.content.res.Resources;
33 import android.content.res.TypedArray;
34 import android.content.res.XmlResourceParser;
35 import android.hardware.usb.UsbManager;
36 import android.net.Uri;
37 import android.os.Binder;
38 import android.os.Environment;
39 import android.os.Environment.UserEnvironment;
40 import android.os.Handler;
41 import android.os.HandlerThread;
42 import android.os.IBinder;
43 import android.os.Looper;
44 import android.os.Message;
45 import android.os.RemoteException;
46 import android.os.ServiceManager;
47 import android.os.SystemClock;
48 import android.os.SystemProperties;
49 import android.os.UserHandle;
50 import android.os.storage.IMountService;
51 import android.os.storage.IMountServiceListener;
52 import android.os.storage.IMountShutdownObserver;
53 import android.os.storage.IObbActionListener;
54 import android.os.storage.OnObbStateChangeListener;
55 import android.os.storage.StorageResultCode;
56 import android.os.storage.StorageVolume;
57 import android.text.TextUtils;
58 import android.util.AttributeSet;
59 import android.util.Slog;
60 import android.util.Xml;
62 import com.android.internal.annotations.GuardedBy;
63 import com.android.internal.annotations.VisibleForTesting;
64 import com.android.internal.app.IMediaContainerService;
65 import com.android.internal.util.Preconditions;
66 import com.android.internal.util.XmlUtils;
67 import com.android.server.NativeDaemonConnector.Command;
68 import com.android.server.NativeDaemonConnector.SensitiveArg;
69 import com.android.server.am.ActivityManagerService;
70 import com.android.server.pm.PackageManagerService;
71 import com.android.server.pm.UserManagerService;
72 import com.google.android.collect.Lists;
73 import com.google.android.collect.Maps;
75 import org.xmlpull.v1.XmlPullParserException;
78 import java.io.FileDescriptor;
79 import java.io.IOException;
80 import java.io.PrintWriter;
81 import java.math.BigInteger;
82 import java.security.NoSuchAlgorithmException;
83 import java.security.spec.InvalidKeySpecException;
84 import java.security.spec.KeySpec;
85 import java.util.ArrayList;
86 import java.util.HashMap;
87 import java.util.HashSet;
88 import java.util.Iterator;
89 import java.util.LinkedList;
90 import java.util.List;
92 import java.util.Map.Entry;
93 import java.util.concurrent.CountDownLatch;
94 import java.util.concurrent.TimeUnit;
96 import javax.crypto.SecretKey;
97 import javax.crypto.SecretKeyFactory;
98 import javax.crypto.spec.PBEKeySpec;
101 * MountService implements back-end services for platform storage
103 * @hide - Applications should use android.os.storage.StorageManager
104 * to access the MountService.
106 class MountService extends IMountService.Stub
107 implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
109 // TODO: listen for user creation/deletion
111 private static final boolean LOCAL_LOGD = false;
112 private static final boolean DEBUG_UNMOUNT = false;
113 private static final boolean DEBUG_EVENTS = false;
114 private static final boolean DEBUG_OBB = false;
116 // Disable this since it messes up long-running cryptfs operations.
117 private static final boolean WATCHDOG_ENABLE = false;
119 private static final String TAG = "MountService";
121 private static final String VOLD_TAG = "VoldConnector";
123 /** Maximum number of ASEC containers allowed to be mounted. */
124 private static final int MAX_CONTAINERS = 250;
127 * Internal vold volume state constants
130 public static final int Init = -1;
131 public static final int NoMedia = 0;
132 public static final int Idle = 1;
133 public static final int Pending = 2;
134 public static final int Checking = 3;
135 public static final int Mounted = 4;
136 public static final int Unmounting = 5;
137 public static final int Formatting = 6;
138 public static final int Shared = 7;
139 public static final int SharedMnt = 8;
143 * Internal vold response code constants
145 class VoldResponseCode {
147 * 100 series - Requestion action was initiated; expect another reply
148 * before proceeding with a new command.
150 public static final int VolumeListResult = 110;
151 public static final int AsecListResult = 111;
152 public static final int StorageUsersListResult = 112;
155 * 200 series - Requestion action has been successfully completed.
157 public static final int ShareStatusResult = 210;
158 public static final int AsecPathResult = 211;
159 public static final int ShareEnabledResult = 212;
162 * 400 series - Command was accepted, but the requested action
163 * did not take place.
165 public static final int OpFailedNoMedia = 401;
166 public static final int OpFailedMediaBlank = 402;
167 public static final int OpFailedMediaCorrupt = 403;
168 public static final int OpFailedVolNotMounted = 404;
169 public static final int OpFailedStorageBusy = 405;
170 public static final int OpFailedStorageNotFound = 406;
173 * 600 series - Unsolicited broadcasts.
175 public static final int VolumeStateChange = 605;
176 public static final int VolumeDiskInserted = 630;
177 public static final int VolumeDiskRemoved = 631;
178 public static final int VolumeBadRemoval = 632;
181 * 700 series - fstrim
183 public static final int FstrimCompleted = 700;
186 private Context mContext;
187 private NativeDaemonConnector mConnector;
189 private final Object mVolumesLock = new Object();
191 /** When defined, base template for user-specific {@link StorageVolume}. */
192 private StorageVolume mEmulatedTemplate;
194 // TODO: separate storage volumes on per-user basis
196 @GuardedBy("mVolumesLock")
197 private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList();
198 /** Map from path to {@link StorageVolume} */
199 @GuardedBy("mVolumesLock")
200 private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap();
201 /** Map from path to state */
202 @GuardedBy("mVolumesLock")
203 private final HashMap<String, String> mVolumeStates = Maps.newHashMap();
205 private volatile boolean mSystemReady = false;
207 private PackageManagerService mPms;
208 private boolean mUmsEnabling;
209 private boolean mUmsAvailable = false;
210 // Used as a lock for methods that register/unregister listeners.
211 final private ArrayList<MountServiceBinderListener> mListeners =
212 new ArrayList<MountServiceBinderListener>();
213 private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
214 private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
215 private boolean mSendUmsConnectedOnBoot = false;
218 * Private hash of currently mounted secure containers.
219 * Used as a lock in methods to manipulate secure containers.
221 final private HashSet<String> mAsecMountSet = new HashSet<String>();
224 * The size of the crypto algorithm key in bits for OBB files. Currently
225 * Twofish is used which takes 128-bit keys.
227 private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128;
230 * The number of times to run SHA1 in the PBKDF2 function for OBB files.
231 * 1024 is reasonably secure and not too slow.
233 private static final int PBKDF2_HASH_ROUNDS = 1024;
236 * Mounted OBB tracking information. Used to track the current state of all
239 final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
241 /** Map from raw paths to {@link ObbState}. */
242 final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
244 class ObbState implements IBinder.DeathRecipient {
245 public ObbState(String rawPath, String canonicalPath, int callingUid,
246 IObbActionListener token, int nonce) {
247 this.rawPath = rawPath;
248 this.canonicalPath = canonicalPath.toString();
250 final int userId = UserHandle.getUserId(callingUid);
251 this.ownerPath = buildObbPath(canonicalPath, userId, false);
252 this.voldPath = buildObbPath(canonicalPath, userId, true);
254 this.ownerGid = UserHandle.getSharedAppGid(callingUid);
259 final String rawPath;
260 final String canonicalPath;
261 final String ownerPath;
262 final String voldPath;
266 // Token of remote Binder caller
267 final IObbActionListener token;
269 // Identifier to pass back to the token
272 public IBinder getBinder() {
273 return token.asBinder();
277 public void binderDied() {
278 ObbAction action = new UnmountObbAction(this, true);
279 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
282 public void link() throws RemoteException {
283 getBinder().linkToDeath(this, 0);
286 public void unlink() {
287 getBinder().unlinkToDeath(this, 0);
291 public String toString() {
292 StringBuilder sb = new StringBuilder("ObbState{");
293 sb.append("rawPath=").append(rawPath);
294 sb.append(",canonicalPath=").append(canonicalPath);
295 sb.append(",ownerPath=").append(ownerPath);
296 sb.append(",voldPath=").append(voldPath);
297 sb.append(",ownerGid=").append(ownerGid);
298 sb.append(",token=").append(token);
299 sb.append(",binder=").append(getBinder());
301 return sb.toString();
305 // OBB Action Handler
306 final private ObbActionHandler mObbActionHandler;
308 // OBB action handler messages
309 private static final int OBB_RUN_ACTION = 1;
310 private static final int OBB_MCS_BOUND = 2;
311 private static final int OBB_MCS_UNBIND = 3;
312 private static final int OBB_MCS_RECONNECT = 4;
313 private static final int OBB_FLUSH_MOUNT_STATE = 5;
316 * Default Container Service information
318 static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
319 "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService");
321 final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
323 class DefaultContainerConnection implements ServiceConnection {
324 public void onServiceConnected(ComponentName name, IBinder service) {
326 Slog.i(TAG, "onServiceConnected");
327 IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
328 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
331 public void onServiceDisconnected(ComponentName name) {
333 Slog.i(TAG, "onServiceDisconnected");
337 // Used in the ObbActionHandler
338 private IMediaContainerService mContainerService = null;
341 private static final int H_UNMOUNT_PM_UPDATE = 1;
342 private static final int H_UNMOUNT_PM_DONE = 2;
343 private static final int H_UNMOUNT_MS = 3;
344 private static final int H_SYSTEM_READY = 4;
346 private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
347 private static final int MAX_UNMOUNT_RETRIES = 4;
349 class UnmountCallBack {
352 final boolean removeEncryption;
355 UnmountCallBack(String path, boolean force, boolean removeEncryption) {
359 this.removeEncryption = removeEncryption;
362 void handleFinished() {
363 if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path);
364 doUnmountVolume(path, true, removeEncryption);
368 class UmsEnableCallBack extends UnmountCallBack {
371 UmsEnableCallBack(String path, String method, boolean force) {
372 super(path, force, false);
373 this.method = method;
377 void handleFinished() {
378 super.handleFinished();
379 doShareUnshareVolume(path, method, true);
383 class ShutdownCallBack extends UnmountCallBack {
384 IMountShutdownObserver observer;
385 ShutdownCallBack(String path, IMountShutdownObserver observer) {
386 super(path, true, false);
387 this.observer = observer;
391 void handleFinished() {
392 int ret = doUnmountVolume(path, true, removeEncryption);
393 if (observer != null) {
395 observer.onShutDownComplete(ret);
396 } catch (RemoteException e) {
397 Slog.w(TAG, "RemoteException when shutting down");
403 class MountServiceHandler extends Handler {
404 ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
405 boolean mUpdatingStatus = false;
407 MountServiceHandler(Looper l) {
412 public void handleMessage(Message msg) {
414 case H_UNMOUNT_PM_UPDATE: {
415 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE");
416 UnmountCallBack ucb = (UnmountCallBack) msg.obj;
417 mForceUnmounts.add(ucb);
418 if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus);
419 // Register only if needed.
420 if (!mUpdatingStatus) {
421 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
422 mUpdatingStatus = true;
423 mPms.updateExternalMediaStatus(false, true);
427 case H_UNMOUNT_PM_DONE: {
428 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE");
429 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests");
430 mUpdatingStatus = false;
431 int size = mForceUnmounts.size();
432 int sizeArr[] = new int[size];
434 // Kill processes holding references first
435 ActivityManagerService ams = (ActivityManagerService)
436 ServiceManager.getService("activity");
437 for (int i = 0; i < size; i++) {
438 UnmountCallBack ucb = mForceUnmounts.get(i);
439 String path = ucb.path;
440 boolean done = false;
444 int pids[] = getStorageUsers(path);
445 if (pids == null || pids.length == 0) {
448 // Eliminate system process here?
449 ams.killPids(pids, "unmount media", true);
450 // Confirm if file references have been freed.
451 pids = getStorageUsers(path);
452 if (pids == null || pids.length == 0) {
457 if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
459 Slog.i(TAG, "Retrying to kill storage users again");
460 mHandler.sendMessageDelayed(
461 mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
463 RETRY_UNMOUNT_DELAY);
465 if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
466 Slog.i(TAG, "Failed to unmount media inspite of " +
467 MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
469 sizeArr[sizeArrN++] = i;
470 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
474 // Remove already processed elements from list.
475 for (int i = (sizeArrN-1); i >= 0; i--) {
476 mForceUnmounts.remove(sizeArr[i]);
481 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS");
482 UnmountCallBack ucb = (UnmountCallBack) msg.obj;
483 ucb.handleFinished();
486 case H_SYSTEM_READY: {
489 } catch (Exception ex) {
490 Slog.e(TAG, "Boot-time mount exception", ex);
498 private final Handler mHandler;
500 void waitForAsecScan() {
501 waitForLatch(mAsecsScanned);
504 private void waitForReady() {
505 waitForLatch(mConnectedSignal);
508 private void waitForLatch(CountDownLatch latch) {
511 if (latch.await(5000, TimeUnit.MILLISECONDS)) {
514 Slog.w(TAG, "Thread " + Thread.currentThread().getName()
515 + " still waiting for MountService ready...");
517 } catch (InterruptedException e) {
518 Slog.w(TAG, "Interrupt while waiting for MountService to be ready.");
523 private void handleSystemReady() {
524 // Snapshot current volume states since it's not safe to call into vold
525 // while holding locks.
526 final HashMap<String, String> snapshot;
527 synchronized (mVolumesLock) {
528 snapshot = new HashMap<String, String>(mVolumeStates);
531 for (Map.Entry<String, String> entry : snapshot.entrySet()) {
532 final String path = entry.getKey();
533 final String state = entry.getValue();
535 if (state.equals(Environment.MEDIA_UNMOUNTED)) {
536 int rc = doMountVolume(path);
537 if (rc != StorageResultCode.OperationSucceeded) {
538 Slog.e(TAG, String.format("Boot-time mount failed (%d)",
541 } else if (state.equals(Environment.MEDIA_SHARED)) {
543 * Bootstrap UMS enabled state since vold indicates
544 * the volume is shared (runtime restart while ums enabled)
546 notifyVolumeStateChange(null, path, VolumeState.NoMedia,
551 // Push mounted state for all emulated storage
552 synchronized (mVolumesLock) {
553 for (StorageVolume volume : mVolumes) {
554 if (volume.isEmulated()) {
555 updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
561 * If UMS was connected on boot, send the connected event
564 if (mSendUmsConnectedOnBoot) {
566 mSendUmsConnectedOnBoot = false;
570 private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
572 public void onReceive(Context context, Intent intent) {
573 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
574 if (userId == -1) return;
575 final UserHandle user = new UserHandle(userId);
577 final String action = intent.getAction();
578 if (Intent.ACTION_USER_ADDED.equals(action)) {
579 synchronized (mVolumesLock) {
580 createEmulatedVolumeForUserLocked(user);
583 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
584 synchronized (mVolumesLock) {
585 final List<StorageVolume> toRemove = Lists.newArrayList();
586 for (StorageVolume volume : mVolumes) {
587 if (user.equals(volume.getOwner())) {
588 toRemove.add(volume);
591 for (StorageVolume volume : toRemove) {
592 removeVolumeLocked(volume);
599 private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
601 public void onReceive(Context context, Intent intent) {
602 boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) &&
603 intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false));
604 notifyShareAvailabilityChange(available);
608 private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
610 public void onReceive(Context context, Intent intent) {
612 String action = intent.getAction();
613 // Since fstrim will be run on a daily basis we do not expect
614 // fstrim to be too long, so it is not interruptible. We will
615 // implement interruption only in case we see issues.
616 if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
618 // This method runs on the handler thread,
619 // so it is safe to directly call into vold.
620 mConnector.execute("fstrim", "dotrim");
621 EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
622 } catch (NativeDaemonConnectorException ndce) {
623 Slog.e(TAG, "Failed to run fstrim!");
629 private final class MountServiceBinderListener implements IBinder.DeathRecipient {
630 final IMountServiceListener mListener;
632 MountServiceBinderListener(IMountServiceListener listener) {
633 mListener = listener;
637 public void binderDied() {
638 if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
639 synchronized (mListeners) {
640 mListeners.remove(this);
641 mListener.asBinder().unlinkToDeath(this, 0);
646 private void doShareUnshareVolume(String path, String method, boolean enable) {
647 // TODO: Add support for multiple share methods
648 if (!method.equals("ums")) {
649 throw new IllegalArgumentException(String.format("Method %s not supported", method));
653 mConnector.execute("volume", enable ? "share" : "unshare", path, method);
654 } catch (NativeDaemonConnectorException e) {
655 Slog.e(TAG, "Failed to share/unshare", e);
659 private void updatePublicVolumeState(StorageVolume volume, String state) {
660 final String path = volume.getPath();
661 final String oldState;
662 synchronized (mVolumesLock) {
663 oldState = mVolumeStates.put(path, state);
666 if (state.equals(oldState)) {
667 Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
668 state, state, path));
672 Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
674 // Tell PackageManager about changes to primary volume state, but only
675 // when not emulated.
676 if (volume.isPrimary() && !volume.isEmulated()) {
677 if (Environment.MEDIA_UNMOUNTED.equals(state)) {
678 mPms.updateExternalMediaStatus(false, false);
681 * Some OBBs might have been unmounted when this volume was
682 * unmounted, so send a message to the handler to let it know to
683 * remove those from the list of mounted OBBS.
685 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
686 OBB_FLUSH_MOUNT_STATE, path));
687 } else if (Environment.MEDIA_MOUNTED.equals(state)) {
688 mPms.updateExternalMediaStatus(true, false);
692 synchronized (mListeners) {
693 for (int i = mListeners.size() -1; i >= 0; i--) {
694 MountServiceBinderListener bl = mListeners.get(i);
696 bl.mListener.onStorageStateChanged(path, oldState, state);
697 } catch (RemoteException rex) {
698 Slog.e(TAG, "Listener dead");
699 mListeners.remove(i);
700 } catch (Exception ex) {
701 Slog.e(TAG, "Listener failed", ex);
708 * Callback from NativeDaemonConnector
710 public void onDaemonConnected() {
712 * Since we'll be calling back into the NativeDaemonConnector,
713 * we need to do our work in a new thread.
715 new Thread("MountService#onDaemonConnected") {
719 * Determine media state and UMS detection status
722 final String[] vols = NativeDaemonEvent.filterMessageList(
723 mConnector.executeForList("volume", "list"),
724 VoldResponseCode.VolumeListResult);
725 for (String volstr : vols) {
726 String[] tok = volstr.split(" ");
727 // FMT: <label> <mountpoint> <state>
728 String path = tok[1];
729 String state = Environment.MEDIA_REMOVED;
731 final StorageVolume volume;
732 synchronized (mVolumesLock) {
733 volume = mVolumesByPath.get(path);
736 int st = Integer.parseInt(tok[2]);
737 if (st == VolumeState.NoMedia) {
738 state = Environment.MEDIA_REMOVED;
739 } else if (st == VolumeState.Idle) {
740 state = Environment.MEDIA_UNMOUNTED;
741 } else if (st == VolumeState.Mounted) {
742 state = Environment.MEDIA_MOUNTED;
743 Slog.i(TAG, "Media already mounted on daemon connection");
744 } else if (st == VolumeState.Shared) {
745 state = Environment.MEDIA_SHARED;
746 Slog.i(TAG, "Media shared on daemon connection");
748 throw new Exception(String.format("Unexpected state %d", st));
752 if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
753 updatePublicVolumeState(volume, state);
756 } catch (Exception e) {
757 Slog.e(TAG, "Error processing initial volume state", e);
758 final StorageVolume primary = getPrimaryPhysicalVolume();
759 if (primary != null) {
760 updatePublicVolumeState(primary, Environment.MEDIA_REMOVED);
765 * Now that we've done our initialization, release
768 mConnectedSignal.countDown();
770 // Let package manager load internal ASECs.
771 mPms.scanAvailableAsecs();
773 // Notify people waiting for ASECs to be scanned that it's done.
774 mAsecsScanned.countDown();
780 * Callback from NativeDaemonConnector
782 public boolean onEvent(int code, String raw, String[] cooked) {
784 StringBuilder builder = new StringBuilder();
785 builder.append("onEvent::");
786 builder.append(" raw= " + raw);
787 if (cooked != null) {
788 builder.append(" cooked = " );
789 for (String str : cooked) {
790 builder.append(" " + str);
793 Slog.i(TAG, builder.toString());
795 if (code == VoldResponseCode.VolumeStateChange) {
797 * One of the volumes we're managing has changed state.
798 * Format: "NNN Volume <label> <path> state changed
799 * from <old_#> (<old_str>) to <new_#> (<new_str>)"
801 notifyVolumeStateChange(
802 cooked[2], cooked[3], Integer.parseInt(cooked[7]),
803 Integer.parseInt(cooked[10]));
804 } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
805 (code == VoldResponseCode.VolumeDiskRemoved) ||
806 (code == VoldResponseCode.VolumeBadRemoval)) {
807 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
808 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
809 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
810 String action = null;
811 final String label = cooked[2];
812 final String path = cooked[3];
817 String devComp = cooked[6].substring(1, cooked[6].length() -1);
818 String[] devTok = devComp.split(":");
819 major = Integer.parseInt(devTok[0]);
820 minor = Integer.parseInt(devTok[1]);
821 } catch (Exception ex) {
822 Slog.e(TAG, "Failed to parse major/minor", ex);
825 final StorageVolume volume;
827 synchronized (mVolumesLock) {
828 volume = mVolumesByPath.get(path);
829 state = mVolumeStates.get(path);
832 if (code == VoldResponseCode.VolumeDiskInserted) {
833 new Thread("MountService#VolumeDiskInserted") {
838 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
839 Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
841 } catch (Exception ex) {
842 Slog.w(TAG, "Failed to mount media on insertion", ex);
846 } else if (code == VoldResponseCode.VolumeDiskRemoved) {
848 * This event gets trumped if we're already in BAD_REMOVAL state
850 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
853 /* Send the media unmounted event first */
854 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
855 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
856 sendStorageIntent(Environment.MEDIA_UNMOUNTED, volume, UserHandle.ALL);
858 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
859 updatePublicVolumeState(volume, Environment.MEDIA_REMOVED);
860 action = Intent.ACTION_MEDIA_REMOVED;
861 } else if (code == VoldResponseCode.VolumeBadRemoval) {
862 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
863 /* Send the media unmounted event first */
864 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
865 sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
867 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
868 updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL);
869 action = Intent.ACTION_MEDIA_BAD_REMOVAL;
870 } else if (code == VoldResponseCode.FstrimCompleted) {
871 EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
873 Slog.e(TAG, String.format("Unknown code {%d}", code));
876 if (action != null) {
877 sendStorageIntent(action, volume, UserHandle.ALL);
886 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
887 final StorageVolume volume;
889 synchronized (mVolumesLock) {
890 volume = mVolumesByPath.get(path);
891 state = getVolumeState(path);
894 if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state);
896 String action = null;
898 if (oldState == VolumeState.Shared && newState != oldState) {
899 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
900 sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL);
903 if (newState == VolumeState.Init) {
904 } else if (newState == VolumeState.NoMedia) {
905 // NoMedia is handled via Disk Remove events
906 } else if (newState == VolumeState.Idle) {
908 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
909 * if we're in the process of enabling UMS
912 Environment.MEDIA_BAD_REMOVAL) && !state.equals(
913 Environment.MEDIA_NOFS) && !state.equals(
914 Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
915 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
916 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
917 action = Intent.ACTION_MEDIA_UNMOUNTED;
919 } else if (newState == VolumeState.Pending) {
920 } else if (newState == VolumeState.Checking) {
921 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
922 updatePublicVolumeState(volume, Environment.MEDIA_CHECKING);
923 action = Intent.ACTION_MEDIA_CHECKING;
924 } else if (newState == VolumeState.Mounted) {
925 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
926 updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
927 action = Intent.ACTION_MEDIA_MOUNTED;
928 } else if (newState == VolumeState.Unmounting) {
929 action = Intent.ACTION_MEDIA_EJECT;
930 } else if (newState == VolumeState.Formatting) {
931 } else if (newState == VolumeState.Shared) {
932 if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
933 /* Send the media unmounted event first */
934 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
935 sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
937 if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
938 updatePublicVolumeState(volume, Environment.MEDIA_SHARED);
939 action = Intent.ACTION_MEDIA_SHARED;
940 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
941 } else if (newState == VolumeState.SharedMnt) {
942 Slog.e(TAG, "Live shared mounts not supported yet!");
945 Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
948 if (action != null) {
949 sendStorageIntent(action, volume, UserHandle.ALL);
953 private int doMountVolume(String path) {
954 int rc = StorageResultCode.OperationSucceeded;
956 final StorageVolume volume;
957 synchronized (mVolumesLock) {
958 volume = mVolumesByPath.get(path);
961 if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
963 mConnector.execute("volume", "mount", path);
964 } catch (NativeDaemonConnectorException e) {
966 * Mount failed for some reason
968 String action = null;
969 int code = e.getCode();
970 if (code == VoldResponseCode.OpFailedNoMedia) {
972 * Attempt to mount but no media inserted
974 rc = StorageResultCode.OperationFailedNoMedia;
975 } else if (code == VoldResponseCode.OpFailedMediaBlank) {
976 if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs");
978 * Media is blank or does not contain a supported filesystem
980 updatePublicVolumeState(volume, Environment.MEDIA_NOFS);
981 action = Intent.ACTION_MEDIA_NOFS;
982 rc = StorageResultCode.OperationFailedMediaBlank;
983 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
984 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
986 * Volume consistency check failed
988 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE);
989 action = Intent.ACTION_MEDIA_UNMOUNTABLE;
990 rc = StorageResultCode.OperationFailedMediaCorrupt;
992 rc = StorageResultCode.OperationFailedInternalError;
996 * Send broadcast intent (if required for the failure)
998 if (action != null) {
999 sendStorageIntent(action, volume, UserHandle.ALL);
1007 * If force is not set, we do not unmount if there are
1008 * processes holding references to the volume about to be unmounted.
1009 * If force is set, all the processes holding references need to be
1010 * killed via the ActivityManager before actually unmounting the volume.
1011 * This might even take a while and might be retried after timed delays
1012 * to make sure we dont end up in an instable state and kill some core
1014 * If removeEncryption is set, force is implied, and the system will remove any encryption
1015 * mapping set on the volume when unmounting.
1017 private int doUnmountVolume(String path, boolean force, boolean removeEncryption) {
1018 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
1019 return VoldResponseCode.OpFailedVolNotMounted;
1023 * Force a GC to make sure AssetManagers in other threads of the
1024 * system_server are cleaned up. We have to do this since AssetManager
1025 * instances are kept as a WeakReference and it's possible we have files
1026 * open on the external storage.
1028 Runtime.getRuntime().gc();
1030 // Redundant probably. But no harm in updating state again.
1031 mPms.updateExternalMediaStatus(false, false);
1033 final Command cmd = new Command("volume", "unmount", path);
1034 if (removeEncryption) {
1035 cmd.appendArg("force_and_revert");
1037 cmd.appendArg("force");
1039 mConnector.execute(cmd);
1040 // We unmounted the volume. None of the asec containers are available now.
1041 synchronized (mAsecMountSet) {
1042 mAsecMountSet.clear();
1044 return StorageResultCode.OperationSucceeded;
1045 } catch (NativeDaemonConnectorException e) {
1046 // Don't worry about mismatch in PackageManager since the
1047 // call back will handle the status changes any way.
1048 int code = e.getCode();
1049 if (code == VoldResponseCode.OpFailedVolNotMounted) {
1050 return StorageResultCode.OperationFailedStorageNotMounted;
1051 } else if (code == VoldResponseCode.OpFailedStorageBusy) {
1052 return StorageResultCode.OperationFailedStorageBusy;
1054 return StorageResultCode.OperationFailedInternalError;
1059 private int doFormatVolume(String path) {
1061 mConnector.execute("volume", "format", path);
1062 return StorageResultCode.OperationSucceeded;
1063 } catch (NativeDaemonConnectorException e) {
1064 int code = e.getCode();
1065 if (code == VoldResponseCode.OpFailedNoMedia) {
1066 return StorageResultCode.OperationFailedNoMedia;
1067 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
1068 return StorageResultCode.OperationFailedMediaCorrupt;
1070 return StorageResultCode.OperationFailedInternalError;
1075 private boolean doGetVolumeShared(String path, String method) {
1076 final NativeDaemonEvent event;
1078 event = mConnector.execute("volume", "shared", path, method);
1079 } catch (NativeDaemonConnectorException ex) {
1080 Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method);
1084 if (event.getCode() == VoldResponseCode.ShareEnabledResult) {
1085 return event.getMessage().endsWith("enabled");
1091 private void notifyShareAvailabilityChange(final boolean avail) {
1092 synchronized (mListeners) {
1093 mUmsAvailable = avail;
1094 for (int i = mListeners.size() -1; i >= 0; i--) {
1095 MountServiceBinderListener bl = mListeners.get(i);
1097 bl.mListener.onUsbMassStorageConnectionChanged(avail);
1098 } catch (RemoteException rex) {
1099 Slog.e(TAG, "Listener dead");
1100 mListeners.remove(i);
1101 } catch (Exception ex) {
1102 Slog.e(TAG, "Listener failed", ex);
1107 if (mSystemReady == true) {
1108 sendUmsIntent(avail);
1110 mSendUmsConnectedOnBoot = avail;
1113 final StorageVolume primary = getPrimaryPhysicalVolume();
1114 if (avail == false && primary != null
1115 && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) {
1116 final String path = primary.getPath();
1118 * USB mass storage disconnected while enabled
1120 new Thread("MountService#AvailabilityChange") {
1125 Slog.w(TAG, "Disabling UMS after cable disconnect");
1126 doShareUnshareVolume(path, "ums", false);
1127 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
1128 Slog.e(TAG, String.format(
1129 "Failed to remount {%s} on UMS enabled-disconnect (%d)",
1132 } catch (Exception ex) {
1133 Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
1140 private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) {
1141 final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath()));
1142 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume);
1143 Slog.d(TAG, "sendStorageIntent " + intent + " to " + user);
1144 mContext.sendBroadcastAsUser(intent, user);
1147 private void sendUmsIntent(boolean c) {
1148 mContext.sendBroadcastAsUser(
1149 new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)),
1153 private void validatePermission(String perm) {
1154 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
1155 throw new SecurityException(String.format("Requires %s permission", perm));
1159 // Storage list XML tags
1160 private static final String TAG_STORAGE_LIST = "StorageList";
1161 private static final String TAG_STORAGE = "storage";
1163 private void readStorageListLocked() {
1165 mVolumeStates.clear();
1167 Resources resources = mContext.getResources();
1169 int id = com.android.internal.R.xml.storage_list;
1170 XmlResourceParser parser = resources.getXml(id);
1171 AttributeSet attrs = Xml.asAttributeSet(parser);
1174 XmlUtils.beginDocument(parser, TAG_STORAGE_LIST);
1176 XmlUtils.nextElement(parser);
1178 String element = parser.getName();
1179 if (element == null) break;
1181 if (TAG_STORAGE.equals(element)) {
1182 TypedArray a = resources.obtainAttributes(attrs,
1183 com.android.internal.R.styleable.Storage);
1185 String path = a.getString(
1186 com.android.internal.R.styleable.Storage_mountPoint);
1187 int descriptionId = a.getResourceId(
1188 com.android.internal.R.styleable.Storage_storageDescription, -1);
1189 CharSequence description = a.getText(
1190 com.android.internal.R.styleable.Storage_storageDescription);
1191 boolean primary = a.getBoolean(
1192 com.android.internal.R.styleable.Storage_primary, false);
1193 boolean removable = a.getBoolean(
1194 com.android.internal.R.styleable.Storage_removable, false);
1195 boolean emulated = a.getBoolean(
1196 com.android.internal.R.styleable.Storage_emulated, false);
1197 int mtpReserve = a.getInt(
1198 com.android.internal.R.styleable.Storage_mtpReserve, 0);
1199 boolean allowMassStorage = a.getBoolean(
1200 com.android.internal.R.styleable.Storage_allowMassStorage, false);
1201 // resource parser does not support longs, so XML value is in megabytes
1202 long maxFileSize = a.getInt(
1203 com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L;
1205 Slog.d(TAG, "got storage path: " + path + " description: " + description +
1206 " primary: " + primary + " removable: " + removable +
1207 " emulated: " + emulated + " mtpReserve: " + mtpReserve +
1208 " allowMassStorage: " + allowMassStorage +
1209 " maxFileSize: " + maxFileSize);
1212 // For devices with emulated storage, we create separate
1213 // volumes for each known user.
1214 mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false,
1215 true, mtpReserve, false, maxFileSize, null);
1217 final UserManagerService userManager = UserManagerService.getInstance();
1218 for (UserInfo user : userManager.getUsers(false)) {
1219 createEmulatedVolumeForUserLocked(user.getUserHandle());
1223 if (path == null || description == null) {
1224 Slog.e(TAG, "Missing storage path or description in readStorageList");
1226 final StorageVolume volume = new StorageVolume(new File(path),
1227 descriptionId, primary, removable, emulated, mtpReserve,
1228 allowMassStorage, maxFileSize, null);
1229 addVolumeLocked(volume);
1231 // Until we hear otherwise, treat as unmounted
1232 mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
1239 } catch (XmlPullParserException e) {
1240 throw new RuntimeException(e);
1241 } catch (IOException e) {
1242 throw new RuntimeException(e);
1244 // Compute storage ID for each physical volume; emulated storage is
1245 // always 0 when defined.
1246 int index = isExternalStorageEmulated() ? 1 : 0;
1247 for (StorageVolume volume : mVolumes) {
1248 if (!volume.isEmulated()) {
1249 volume.setStorageId(index++);
1257 * Create and add new {@link StorageVolume} for given {@link UserHandle}
1258 * using {@link #mEmulatedTemplate} as template.
1260 private void createEmulatedVolumeForUserLocked(UserHandle user) {
1261 if (mEmulatedTemplate == null) {
1262 throw new IllegalStateException("Missing emulated volume multi-user template");
1265 final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier());
1266 final File path = userEnv.getExternalStorageDirectory();
1267 final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user);
1268 volume.setStorageId(0);
1269 addVolumeLocked(volume);
1272 updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
1274 // Place stub status for early callers to find
1275 mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
1279 private void addVolumeLocked(StorageVolume volume) {
1280 Slog.d(TAG, "addVolumeLocked() " + volume);
1281 mVolumes.add(volume);
1282 final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume);
1283 if (existing != null) {
1284 throw new IllegalStateException(
1285 "Volume at " + volume.getPath() + " already exists: " + existing);
1289 private void removeVolumeLocked(StorageVolume volume) {
1290 Slog.d(TAG, "removeVolumeLocked() " + volume);
1291 mVolumes.remove(volume);
1292 mVolumesByPath.remove(volume.getPath());
1293 mVolumeStates.remove(volume.getPath());
1296 private StorageVolume getPrimaryPhysicalVolume() {
1297 synchronized (mVolumesLock) {
1298 for (StorageVolume volume : mVolumes) {
1299 if (volume.isPrimary() && !volume.isEmulated()) {
1308 * Constructs a new MountService instance
1310 * @param context Binder context for this service
1312 public MountService(Context context) {
1315 synchronized (mVolumesLock) {
1316 readStorageListLocked();
1319 // XXX: This will go away soon in favor of IMountServiceObserver
1320 mPms = (PackageManagerService) ServiceManager.getService("package");
1322 HandlerThread hthread = new HandlerThread(TAG);
1324 mHandler = new MountServiceHandler(hthread.getLooper());
1326 // Watch for user changes
1327 final IntentFilter userFilter = new IntentFilter();
1328 userFilter.addAction(Intent.ACTION_USER_ADDED);
1329 userFilter.addAction(Intent.ACTION_USER_REMOVED);
1330 mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
1332 // Watch for USB changes on primary volume
1333 final StorageVolume primary = getPrimaryPhysicalVolume();
1334 if (primary != null && primary.allowMassStorage()) {
1335 mContext.registerReceiver(
1336 mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
1339 // Watch for idle maintenance changes
1340 IntentFilter idleMaintenanceFilter = new IntentFilter();
1341 idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
1342 mContext.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL,
1343 idleMaintenanceFilter, null, mHandler);
1345 // Add OBB Action Handler to MountService thread.
1346 mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
1349 * Create the connection to vold with a maximum queue of twice the
1350 * amount of containers we'd ever expect to have. This keeps an
1351 * "asec list" from blocking a thread repeatedly.
1353 mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25);
1355 Thread thread = new Thread(mConnector, VOLD_TAG);
1358 // Add ourself to the Watchdog monitors if enabled.
1359 if (WATCHDOG_ENABLE) {
1360 Watchdog.getInstance().addMonitor(this);
1364 public void systemReady() {
1365 mSystemReady = true;
1366 mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
1370 * Exposed API calls below here
1373 public void registerListener(IMountServiceListener listener) {
1374 synchronized (mListeners) {
1375 MountServiceBinderListener bl = new MountServiceBinderListener(listener);
1377 listener.asBinder().linkToDeath(bl, 0);
1379 } catch (RemoteException rex) {
1380 Slog.e(TAG, "Failed to link to listener death");
1385 public void unregisterListener(IMountServiceListener listener) {
1386 synchronized (mListeners) {
1387 for(MountServiceBinderListener bl : mListeners) {
1388 if (bl.mListener == listener) {
1389 mListeners.remove(mListeners.indexOf(bl));
1390 listener.asBinder().unlinkToDeath(bl, 0);
1397 public void shutdown(final IMountShutdownObserver observer) {
1398 validatePermission(android.Manifest.permission.SHUTDOWN);
1400 Slog.i(TAG, "Shutting down");
1401 synchronized (mVolumesLock) {
1402 for (String path : mVolumeStates.keySet()) {
1403 String state = mVolumeStates.get(path);
1405 if (state.equals(Environment.MEDIA_SHARED)) {
1407 * If the media is currently shared, unshare it.
1408 * XXX: This is still dangerous!. We should not
1409 * be rebooting at *all* if UMS is enabled, since
1410 * the UMS host could have dirty FAT cache entries
1413 setUsbMassStorageEnabled(false);
1414 } else if (state.equals(Environment.MEDIA_CHECKING)) {
1416 * If the media is being checked, then we need to wait for
1417 * it to complete before being able to proceed.
1419 // XXX: @hackbod - Should we disable the ANR timer here?
1421 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
1424 } catch (InterruptedException iex) {
1425 Slog.e(TAG, "Interrupted while waiting for media", iex);
1428 state = Environment.getExternalStorageState();
1431 Slog.e(TAG, "Timed out waiting for media to check");
1435 if (state.equals(Environment.MEDIA_MOUNTED)) {
1436 // Post a unmount message.
1437 ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
1438 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
1439 } else if (observer != null) {
1441 * Observer is waiting for onShutDownComplete when we are done.
1442 * Since nothing will be done send notification directly so shutdown
1443 * sequence can continue.
1446 observer.onShutDownComplete(StorageResultCode.OperationSucceeded);
1447 } catch (RemoteException e) {
1448 Slog.w(TAG, "RemoteException when shutting down");
1455 private boolean getUmsEnabling() {
1456 synchronized (mListeners) {
1457 return mUmsEnabling;
1461 private void setUmsEnabling(boolean enable) {
1462 synchronized (mListeners) {
1463 mUmsEnabling = enable;
1467 public boolean isUsbMassStorageConnected() {
1470 if (getUmsEnabling()) {
1473 synchronized (mListeners) {
1474 return mUmsAvailable;
1478 public void setUsbMassStorageEnabled(boolean enable) {
1480 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1482 final StorageVolume primary = getPrimaryPhysicalVolume();
1483 if (primary == null) return;
1485 // TODO: Add support for multiple share methods
1488 * If the volume is mounted and we're enabling then unmount it
1490 String path = primary.getPath();
1491 String vs = getVolumeState(path);
1492 String method = "ums";
1493 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
1494 // Override for isUsbMassStorageEnabled()
1495 setUmsEnabling(enable);
1496 UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
1497 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
1499 setUmsEnabling(false);
1502 * If we disabled UMS then mount the volume
1505 doShareUnshareVolume(path, method, enable);
1506 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
1507 Slog.e(TAG, "Failed to remount " + path +
1508 " after disabling share method " + method);
1510 * Even though the mount failed, the unshare didn't so don't indicate an error.
1511 * The mountVolume() call will have set the storage state and sent the necessary
1518 public boolean isUsbMassStorageEnabled() {
1521 final StorageVolume primary = getPrimaryPhysicalVolume();
1522 if (primary != null) {
1523 return doGetVolumeShared(primary.getPath(), "ums");
1530 * @return state of the volume at the specified mount point
1532 public String getVolumeState(String mountPoint) {
1533 synchronized (mVolumesLock) {
1534 String state = mVolumeStates.get(mountPoint);
1535 if (state == null) {
1536 Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
1537 if (SystemProperties.get("vold.encrypt_progress").length() != 0) {
1538 state = Environment.MEDIA_REMOVED;
1540 throw new IllegalArgumentException();
1549 public boolean isExternalStorageEmulated() {
1550 return mEmulatedTemplate != null;
1553 public int mountVolume(String path) {
1554 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1557 return doMountVolume(path);
1560 public void unmountVolume(String path, boolean force, boolean removeEncryption) {
1561 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1564 String volState = getVolumeState(path);
1565 if (DEBUG_UNMOUNT) {
1566 Slog.i(TAG, "Unmounting " + path
1567 + " force = " + force
1568 + " removeEncryption = " + removeEncryption);
1570 if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
1571 Environment.MEDIA_REMOVED.equals(volState) ||
1572 Environment.MEDIA_SHARED.equals(volState) ||
1573 Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
1574 // Media already unmounted or cannot be unmounted.
1575 // TODO return valid return code when adding observer call back.
1578 UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption);
1579 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
1582 public int formatVolume(String path) {
1583 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
1586 return doFormatVolume(path);
1589 public int[] getStorageUsers(String path) {
1590 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1593 final String[] r = NativeDaemonEvent.filterMessageList(
1594 mConnector.executeForList("storage", "users", path),
1595 VoldResponseCode.StorageUsersListResult);
1597 // FMT: <pid> <process name>
1598 int[] data = new int[r.length];
1599 for (int i = 0; i < r.length; i++) {
1600 String[] tok = r[i].split(" ");
1602 data[i] = Integer.parseInt(tok[0]);
1603 } catch (NumberFormatException nfe) {
1604 Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
1609 } catch (NativeDaemonConnectorException e) {
1610 Slog.e(TAG, "Failed to retrieve storage users list", e);
1615 private void warnOnNotMounted() {
1616 final StorageVolume primary = getPrimaryPhysicalVolume();
1617 if (primary != null) {
1618 boolean mounted = false;
1620 mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()));
1621 } catch (IllegalArgumentException e) {
1625 Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
1630 public String[] getSecureContainerList() {
1631 validatePermission(android.Manifest.permission.ASEC_ACCESS);
1636 return NativeDaemonEvent.filterMessageList(
1637 mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
1638 } catch (NativeDaemonConnectorException e) {
1639 return new String[0];
1643 public int createSecureContainer(String id, int sizeMb, String fstype, String key,
1644 int ownerUid, boolean external) {
1645 validatePermission(android.Manifest.permission.ASEC_CREATE);
1649 int rc = StorageResultCode.OperationSucceeded;
1651 mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key),
1652 ownerUid, external ? "1" : "0");
1653 } catch (NativeDaemonConnectorException e) {
1654 rc = StorageResultCode.OperationFailedInternalError;
1657 if (rc == StorageResultCode.OperationSucceeded) {
1658 synchronized (mAsecMountSet) {
1659 mAsecMountSet.add(id);
1665 public int finalizeSecureContainer(String id) {
1666 validatePermission(android.Manifest.permission.ASEC_CREATE);
1669 int rc = StorageResultCode.OperationSucceeded;
1671 mConnector.execute("asec", "finalize", id);
1673 * Finalization does a remount, so no need
1674 * to update mAsecMountSet
1676 } catch (NativeDaemonConnectorException e) {
1677 rc = StorageResultCode.OperationFailedInternalError;
1682 public int fixPermissionsSecureContainer(String id, int gid, String filename) {
1683 validatePermission(android.Manifest.permission.ASEC_CREATE);
1686 int rc = StorageResultCode.OperationSucceeded;
1688 mConnector.execute("asec", "fixperms", id, gid, filename);
1690 * Fix permissions does a remount, so no need to update
1693 } catch (NativeDaemonConnectorException e) {
1694 rc = StorageResultCode.OperationFailedInternalError;
1699 public int destroySecureContainer(String id, boolean force) {
1700 validatePermission(android.Manifest.permission.ASEC_DESTROY);
1705 * Force a GC to make sure AssetManagers in other threads of the
1706 * system_server are cleaned up. We have to do this since AssetManager
1707 * instances are kept as a WeakReference and it's possible we have files
1708 * open on the external storage.
1710 Runtime.getRuntime().gc();
1712 int rc = StorageResultCode.OperationSucceeded;
1714 final Command cmd = new Command("asec", "destroy", id);
1716 cmd.appendArg("force");
1718 mConnector.execute(cmd);
1719 } catch (NativeDaemonConnectorException e) {
1720 int code = e.getCode();
1721 if (code == VoldResponseCode.OpFailedStorageBusy) {
1722 rc = StorageResultCode.OperationFailedStorageBusy;
1724 rc = StorageResultCode.OperationFailedInternalError;
1728 if (rc == StorageResultCode.OperationSucceeded) {
1729 synchronized (mAsecMountSet) {
1730 if (mAsecMountSet.contains(id)) {
1731 mAsecMountSet.remove(id);
1739 public int mountSecureContainer(String id, String key, int ownerUid) {
1740 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1744 synchronized (mAsecMountSet) {
1745 if (mAsecMountSet.contains(id)) {
1746 return StorageResultCode.OperationFailedStorageMounted;
1750 int rc = StorageResultCode.OperationSucceeded;
1752 mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid);
1753 } catch (NativeDaemonConnectorException e) {
1754 int code = e.getCode();
1755 if (code != VoldResponseCode.OpFailedStorageBusy) {
1756 rc = StorageResultCode.OperationFailedInternalError;
1760 if (rc == StorageResultCode.OperationSucceeded) {
1761 synchronized (mAsecMountSet) {
1762 mAsecMountSet.add(id);
1768 public int unmountSecureContainer(String id, boolean force) {
1769 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1773 synchronized (mAsecMountSet) {
1774 if (!mAsecMountSet.contains(id)) {
1775 return StorageResultCode.OperationFailedStorageNotMounted;
1780 * Force a GC to make sure AssetManagers in other threads of the
1781 * system_server are cleaned up. We have to do this since AssetManager
1782 * instances are kept as a WeakReference and it's possible we have files
1783 * open on the external storage.
1785 Runtime.getRuntime().gc();
1787 int rc = StorageResultCode.OperationSucceeded;
1789 final Command cmd = new Command("asec", "unmount", id);
1791 cmd.appendArg("force");
1793 mConnector.execute(cmd);
1794 } catch (NativeDaemonConnectorException e) {
1795 int code = e.getCode();
1796 if (code == VoldResponseCode.OpFailedStorageBusy) {
1797 rc = StorageResultCode.OperationFailedStorageBusy;
1799 rc = StorageResultCode.OperationFailedInternalError;
1803 if (rc == StorageResultCode.OperationSucceeded) {
1804 synchronized (mAsecMountSet) {
1805 mAsecMountSet.remove(id);
1811 public boolean isSecureContainerMounted(String id) {
1812 validatePermission(android.Manifest.permission.ASEC_ACCESS);
1816 synchronized (mAsecMountSet) {
1817 return mAsecMountSet.contains(id);
1821 public int renameSecureContainer(String oldId, String newId) {
1822 validatePermission(android.Manifest.permission.ASEC_RENAME);
1826 synchronized (mAsecMountSet) {
1828 * Because a mounted container has active internal state which cannot be
1829 * changed while active, we must ensure both ids are not currently mounted.
1831 if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
1832 return StorageResultCode.OperationFailedStorageMounted;
1836 int rc = StorageResultCode.OperationSucceeded;
1838 mConnector.execute("asec", "rename", oldId, newId);
1839 } catch (NativeDaemonConnectorException e) {
1840 rc = StorageResultCode.OperationFailedInternalError;
1846 public String getSecureContainerPath(String id) {
1847 validatePermission(android.Manifest.permission.ASEC_ACCESS);
1851 final NativeDaemonEvent event;
1853 event = mConnector.execute("asec", "path", id);
1854 event.checkCode(VoldResponseCode.AsecPathResult);
1855 return event.getMessage();
1856 } catch (NativeDaemonConnectorException e) {
1857 int code = e.getCode();
1858 if (code == VoldResponseCode.OpFailedStorageNotFound) {
1859 Slog.i(TAG, String.format("Container '%s' not found", id));
1862 throw new IllegalStateException(String.format("Unexpected response code %d", code));
1867 public String getSecureContainerFilesystemPath(String id) {
1868 validatePermission(android.Manifest.permission.ASEC_ACCESS);
1872 final NativeDaemonEvent event;
1874 event = mConnector.execute("asec", "fspath", id);
1875 event.checkCode(VoldResponseCode.AsecPathResult);
1876 return event.getMessage();
1877 } catch (NativeDaemonConnectorException e) {
1878 int code = e.getCode();
1879 if (code == VoldResponseCode.OpFailedStorageNotFound) {
1880 Slog.i(TAG, String.format("Container '%s' not found", id));
1883 throw new IllegalStateException(String.format("Unexpected response code %d", code));
1888 public void finishMediaUpdate() {
1889 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
1892 private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
1893 if (callerUid == android.os.Process.SYSTEM_UID) {
1897 if (packageName == null) {
1901 final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid));
1904 Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
1905 packageUid + ", callerUid = " + callerUid);
1908 return callerUid == packageUid;
1911 public String getMountedObbPath(String rawPath) {
1912 Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1917 final ObbState state;
1918 synchronized (mObbPathToStateMap) {
1919 state = mObbPathToStateMap.get(rawPath);
1921 if (state == null) {
1922 Slog.w(TAG, "Failed to find OBB mounted at " + rawPath);
1926 final NativeDaemonEvent event;
1928 event = mConnector.execute("obb", "path", state.voldPath);
1929 event.checkCode(VoldResponseCode.AsecPathResult);
1930 return event.getMessage();
1931 } catch (NativeDaemonConnectorException e) {
1932 int code = e.getCode();
1933 if (code == VoldResponseCode.OpFailedStorageNotFound) {
1936 throw new IllegalStateException(String.format("Unexpected response code %d", code));
1942 public boolean isObbMounted(String rawPath) {
1943 Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1944 synchronized (mObbMounts) {
1945 return mObbPathToStateMap.containsKey(rawPath);
1950 public void mountObb(
1951 String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) {
1952 Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1953 Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null");
1954 Preconditions.checkNotNull(token, "token cannot be null");
1956 final int callingUid = Binder.getCallingUid();
1957 final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce);
1958 final ObbAction action = new MountObbAction(obbState, key, callingUid);
1959 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1962 Slog.i(TAG, "Send to OBB handler: " + action.toString());
1966 public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) {
1967 Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1969 final ObbState existingState;
1970 synchronized (mObbPathToStateMap) {
1971 existingState = mObbPathToStateMap.get(rawPath);
1974 if (existingState != null) {
1975 // TODO: separate state object from request data
1976 final int callingUid = Binder.getCallingUid();
1977 final ObbState newState = new ObbState(
1978 rawPath, existingState.canonicalPath, callingUid, token, nonce);
1979 final ObbAction action = new UnmountObbAction(newState, force);
1980 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1983 Slog.i(TAG, "Send to OBB handler: " + action.toString());
1985 Slog.w(TAG, "Unknown OBB mount at " + rawPath);
1990 public int getEncryptionState() {
1991 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1992 "no permission to access the crypt keeper");
1996 final NativeDaemonEvent event;
1998 event = mConnector.execute("cryptfs", "cryptocomplete");
1999 return Integer.parseInt(event.getMessage());
2000 } catch (NumberFormatException e) {
2001 // Bad result - unexpected.
2002 Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
2003 return ENCRYPTION_STATE_ERROR_UNKNOWN;
2004 } catch (NativeDaemonConnectorException e) {
2005 // Something bad happened.
2006 Slog.w(TAG, "Error in communicating with cryptfs in validating");
2007 return ENCRYPTION_STATE_ERROR_UNKNOWN;
2012 public int decryptStorage(String password) {
2013 if (TextUtils.isEmpty(password)) {
2014 throw new IllegalArgumentException("password cannot be empty");
2017 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
2018 "no permission to access the crypt keeper");
2023 Slog.i(TAG, "decrypting storage...");
2026 final NativeDaemonEvent event;
2028 event = mConnector.execute("cryptfs", "checkpw", new SensitiveArg(password));
2030 final int code = Integer.parseInt(event.getMessage());
2032 // Decrypt was successful. Post a delayed message before restarting in order
2033 // to let the UI to clear itself
2034 mHandler.postDelayed(new Runnable() {
2037 mConnector.execute("cryptfs", "restart");
2038 } catch (NativeDaemonConnectorException e) {
2039 Slog.e(TAG, "problem executing in background", e);
2042 }, 1000); // 1 second
2046 } catch (NativeDaemonConnectorException e) {
2047 // Decryption failed
2052 public int encryptStorage(String password) {
2053 if (TextUtils.isEmpty(password)) {
2054 throw new IllegalArgumentException("password cannot be empty");
2057 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
2058 "no permission to access the crypt keeper");
2063 Slog.i(TAG, "encrypting storage...");
2067 mConnector.execute("cryptfs", "enablecrypto", "inplace", new SensitiveArg(password));
2068 } catch (NativeDaemonConnectorException e) {
2069 // Encryption failed
2076 public int changeEncryptionPassword(String password) {
2077 if (TextUtils.isEmpty(password)) {
2078 throw new IllegalArgumentException("password cannot be empty");
2081 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
2082 "no permission to access the crypt keeper");
2087 Slog.i(TAG, "changing encryption password...");
2090 final NativeDaemonEvent event;
2092 event = mConnector.execute("cryptfs", "changepw", new SensitiveArg(password));
2093 return Integer.parseInt(event.getMessage());
2094 } catch (NativeDaemonConnectorException e) {
2095 // Encryption failed
2101 * Validate a user-supplied password string with cryptfs
2104 public int verifyEncryptionPassword(String password) throws RemoteException {
2105 // Only the system process is permitted to validate passwords
2106 if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
2107 throw new SecurityException("no permission to access the crypt keeper");
2110 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
2111 "no permission to access the crypt keeper");
2113 if (TextUtils.isEmpty(password)) {
2114 throw new IllegalArgumentException("password cannot be empty");
2120 Slog.i(TAG, "validating encryption password...");
2123 final NativeDaemonEvent event;
2125 event = mConnector.execute("cryptfs", "verifypw", new SensitiveArg(password));
2126 Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
2127 return Integer.parseInt(event.getMessage());
2128 } catch (NativeDaemonConnectorException e) {
2129 // Encryption failed
2135 public int mkdirs(String callingPkg, String appPath) {
2136 final int userId = UserHandle.getUserId(Binder.getCallingUid());
2137 final UserEnvironment userEnv = new UserEnvironment(userId);
2139 // Validate that reported package name belongs to caller
2140 final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
2141 Context.APP_OPS_SERVICE);
2142 appOps.checkPackage(Binder.getCallingUid(), callingPkg);
2145 appPath = new File(appPath).getCanonicalPath();
2146 } catch (IOException e) {
2147 Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
2151 if (!appPath.endsWith("/")) {
2152 appPath = appPath + "/";
2155 // Try translating the app path into a vold path, but require that it
2156 // belong to the calling package.
2157 String voldPath = maybeTranslatePathForVold(appPath,
2158 userEnv.buildExternalStorageAppDataDirs(callingPkg),
2159 userEnv.buildExternalStorageAppDataDirsForVold(callingPkg));
2160 if (voldPath != null) {
2162 mConnector.execute("volume", "mkdirs", voldPath);
2164 } catch (NativeDaemonConnectorException e) {
2169 voldPath = maybeTranslatePathForVold(appPath,
2170 userEnv.buildExternalStorageAppObbDirs(callingPkg),
2171 userEnv.buildExternalStorageAppObbDirsForVold(callingPkg));
2172 if (voldPath != null) {
2174 mConnector.execute("volume", "mkdirs", voldPath);
2176 } catch (NativeDaemonConnectorException e) {
2181 throw new SecurityException("Invalid mkdirs path: " + appPath);
2185 * Translate the given path from an app-visible path to a vold-visible path,
2186 * but only if it's under the given whitelisted paths.
2188 * @param path a canonicalized app-visible path.
2189 * @param appPaths list of app-visible paths that are allowed.
2190 * @param voldPaths list of vold-visible paths directly corresponding to the
2191 * allowed app-visible paths argument.
2192 * @return a vold-visible path representing the original path, or
2193 * {@code null} if the given path didn't have an app-to-vold
2197 public static String maybeTranslatePathForVold(
2198 String path, File[] appPaths, File[] voldPaths) {
2199 if (appPaths.length != voldPaths.length) {
2200 throw new IllegalStateException("Paths must be 1:1 mapping");
2203 for (int i = 0; i < appPaths.length; i++) {
2204 final String appPath = appPaths[i].getAbsolutePath() + "/";
2205 if (path.startsWith(appPath)) {
2206 path = new File(voldPaths[i], path.substring(appPath.length()))
2208 if (!path.endsWith("/")) {
2218 public StorageVolume[] getVolumeList() {
2219 final int callingUserId = UserHandle.getCallingUserId();
2220 final boolean accessAll = (mContext.checkPermission(
2221 android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
2222 Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED);
2224 synchronized (mVolumesLock) {
2225 final ArrayList<StorageVolume> filtered = Lists.newArrayList();
2226 for (StorageVolume volume : mVolumes) {
2227 final UserHandle owner = volume.getOwner();
2228 final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId;
2229 if (accessAll || ownerMatch) {
2230 filtered.add(volume);
2233 return filtered.toArray(new StorageVolume[filtered.size()]);
2237 private void addObbStateLocked(ObbState obbState) throws RemoteException {
2238 final IBinder binder = obbState.getBinder();
2239 List<ObbState> obbStates = mObbMounts.get(binder);
2241 if (obbStates == null) {
2242 obbStates = new ArrayList<ObbState>();
2243 mObbMounts.put(binder, obbStates);
2245 for (final ObbState o : obbStates) {
2246 if (o.rawPath.equals(obbState.rawPath)) {
2247 throw new IllegalStateException("Attempt to add ObbState twice. "
2248 + "This indicates an error in the MountService logic.");
2253 obbStates.add(obbState);
2256 } catch (RemoteException e) {
2258 * The binder died before we could link it, so clean up our state
2259 * and return failure.
2261 obbStates.remove(obbState);
2262 if (obbStates.isEmpty()) {
2263 mObbMounts.remove(binder);
2266 // Rethrow the error so mountObb can get it
2270 mObbPathToStateMap.put(obbState.rawPath, obbState);
2273 private void removeObbStateLocked(ObbState obbState) {
2274 final IBinder binder = obbState.getBinder();
2275 final List<ObbState> obbStates = mObbMounts.get(binder);
2276 if (obbStates != null) {
2277 if (obbStates.remove(obbState)) {
2280 if (obbStates.isEmpty()) {
2281 mObbMounts.remove(binder);
2285 mObbPathToStateMap.remove(obbState.rawPath);
2288 private class ObbActionHandler extends Handler {
2289 private boolean mBound = false;
2290 private final List<ObbAction> mActions = new LinkedList<ObbAction>();
2292 ObbActionHandler(Looper l) {
2297 public void handleMessage(Message msg) {
2299 case OBB_RUN_ACTION: {
2300 final ObbAction action = (ObbAction) msg.obj;
2303 Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
2305 // If a bind was already initiated we don't really
2306 // need to do anything. The pending install
2307 // will be processed later on.
2309 // If this is the only one pending we might
2310 // have to bind to the service again.
2311 if (!connectToService()) {
2312 Slog.e(TAG, "Failed to bind to media container service");
2313 action.handleError();
2318 mActions.add(action);
2321 case OBB_MCS_BOUND: {
2323 Slog.i(TAG, "OBB_MCS_BOUND");
2324 if (msg.obj != null) {
2325 mContainerService = (IMediaContainerService) msg.obj;
2327 if (mContainerService == null) {
2328 // Something seriously wrong. Bail out
2329 Slog.e(TAG, "Cannot bind to media container service");
2330 for (ObbAction action : mActions) {
2331 // Indicate service bind error
2332 action.handleError();
2335 } else if (mActions.size() > 0) {
2336 final ObbAction action = mActions.get(0);
2337 if (action != null) {
2338 action.execute(this);
2341 // Should never happen ideally.
2342 Slog.w(TAG, "Empty queue");
2346 case OBB_MCS_RECONNECT: {
2348 Slog.i(TAG, "OBB_MCS_RECONNECT");
2349 if (mActions.size() > 0) {
2351 disconnectService();
2353 if (!connectToService()) {
2354 Slog.e(TAG, "Failed to bind to media container service");
2355 for (ObbAction action : mActions) {
2356 // Indicate service bind error
2357 action.handleError();
2364 case OBB_MCS_UNBIND: {
2366 Slog.i(TAG, "OBB_MCS_UNBIND");
2368 // Delete pending install
2369 if (mActions.size() > 0) {
2372 if (mActions.size() == 0) {
2374 disconnectService();
2377 // There are more pending requests in queue.
2378 // Just post MCS_BOUND message to trigger processing
2379 // of next pending install.
2380 mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
2384 case OBB_FLUSH_MOUNT_STATE: {
2385 final String path = (String) msg.obj;
2388 Slog.i(TAG, "Flushing all OBB state for path " + path);
2390 synchronized (mObbMounts) {
2391 final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
2393 final Iterator<ObbState> i = mObbPathToStateMap.values().iterator();
2394 while (i.hasNext()) {
2395 final ObbState state = i.next();
2398 * If this entry's source file is in the volume path
2399 * that got unmounted, remove it because it's no
2402 if (state.canonicalPath.startsWith(path)) {
2403 obbStatesToRemove.add(state);
2407 for (final ObbState obbState : obbStatesToRemove) {
2409 Slog.i(TAG, "Removing state for " + obbState.rawPath);
2411 removeObbStateLocked(obbState);
2414 obbState.token.onObbResult(obbState.rawPath, obbState.nonce,
2415 OnObbStateChangeListener.UNMOUNTED);
2416 } catch (RemoteException e) {
2417 Slog.i(TAG, "Couldn't send unmount notification for OBB: "
2418 + obbState.rawPath);
2427 private boolean connectToService() {
2429 Slog.i(TAG, "Trying to bind to DefaultContainerService");
2431 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
2432 if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
2439 private void disconnectService() {
2440 mContainerService = null;
2442 mContext.unbindService(mDefContainerConn);
2446 abstract class ObbAction {
2447 private static final int MAX_RETRIES = 3;
2448 private int mRetries;
2452 ObbAction(ObbState obbState) {
2453 mObbState = obbState;
2456 public void execute(ObbActionHandler handler) {
2459 Slog.i(TAG, "Starting to execute action: " + toString());
2461 if (mRetries > MAX_RETRIES) {
2462 Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
2463 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2469 Slog.i(TAG, "Posting install MCS_UNBIND");
2470 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2472 } catch (RemoteException e) {
2474 Slog.i(TAG, "Posting install MCS_RECONNECT");
2475 mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT);
2476 } catch (Exception e) {
2478 Slog.d(TAG, "Error handling OBB action", e);
2480 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2484 abstract void handleExecute() throws RemoteException, IOException;
2485 abstract void handleError();
2487 protected ObbInfo getObbInfo() throws IOException {
2490 obbInfo = mContainerService.getObbInfo(mObbState.ownerPath);
2491 } catch (RemoteException e) {
2492 Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
2493 + mObbState.ownerPath);
2496 if (obbInfo == null) {
2497 throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath);
2502 protected void sendNewStatusOrIgnore(int status) {
2503 if (mObbState == null || mObbState.token == null) {
2508 mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status);
2509 } catch (RemoteException e) {
2510 Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
2515 class MountObbAction extends ObbAction {
2516 private final String mKey;
2517 private final int mCallingUid;
2519 MountObbAction(ObbState obbState, String key, int callingUid) {
2522 mCallingUid = callingUid;
2526 public void handleExecute() throws IOException, RemoteException {
2530 final ObbInfo obbInfo = getObbInfo();
2532 if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) {
2533 Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename
2534 + " which is owned by " + obbInfo.packageName);
2535 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
2539 final boolean isMounted;
2540 synchronized (mObbMounts) {
2541 isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath);
2544 Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename);
2545 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED);
2549 final String hashedKey;
2554 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
2556 KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt,
2557 PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE);
2558 SecretKey key = factory.generateSecret(ks);
2559 BigInteger bi = new BigInteger(key.getEncoded());
2560 hashedKey = bi.toString(16);
2561 } catch (NoSuchAlgorithmException e) {
2562 Slog.e(TAG, "Could not load PBKDF2 algorithm", e);
2563 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2565 } catch (InvalidKeySpecException e) {
2566 Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e);
2567 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2572 int rc = StorageResultCode.OperationSucceeded;
2574 mConnector.execute("obb", "mount", mObbState.voldPath, new SensitiveArg(hashedKey),
2575 mObbState.ownerGid);
2576 } catch (NativeDaemonConnectorException e) {
2577 int code = e.getCode();
2578 if (code != VoldResponseCode.OpFailedStorageBusy) {
2579 rc = StorageResultCode.OperationFailedInternalError;
2583 if (rc == StorageResultCode.OperationSucceeded) {
2585 Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath);
2587 synchronized (mObbMounts) {
2588 addObbStateLocked(mObbState);
2591 sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED);
2593 Slog.e(TAG, "Couldn't mount OBB file: " + rc);
2595 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT);
2600 public void handleError() {
2601 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2605 public String toString() {
2606 StringBuilder sb = new StringBuilder();
2607 sb.append("MountObbAction{");
2608 sb.append(mObbState);
2610 return sb.toString();
2614 class UnmountObbAction extends ObbAction {
2615 private final boolean mForceUnmount;
2617 UnmountObbAction(ObbState obbState, boolean force) {
2619 mForceUnmount = force;
2623 public void handleExecute() throws IOException {
2627 final ObbInfo obbInfo = getObbInfo();
2629 final ObbState existingState;
2630 synchronized (mObbMounts) {
2631 existingState = mObbPathToStateMap.get(mObbState.rawPath);
2634 if (existingState == null) {
2635 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED);
2639 if (existingState.ownerGid != mObbState.ownerGid) {
2640 Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath
2641 + " (owned by GID " + existingState.ownerGid + ")");
2642 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
2646 int rc = StorageResultCode.OperationSucceeded;
2648 final Command cmd = new Command("obb", "unmount", mObbState.voldPath);
2649 if (mForceUnmount) {
2650 cmd.appendArg("force");
2652 mConnector.execute(cmd);
2653 } catch (NativeDaemonConnectorException e) {
2654 int code = e.getCode();
2655 if (code == VoldResponseCode.OpFailedStorageBusy) {
2656 rc = StorageResultCode.OperationFailedStorageBusy;
2657 } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
2658 // If it's not mounted then we've already won.
2659 rc = StorageResultCode.OperationSucceeded;
2661 rc = StorageResultCode.OperationFailedInternalError;
2665 if (rc == StorageResultCode.OperationSucceeded) {
2666 synchronized (mObbMounts) {
2667 removeObbStateLocked(existingState);
2670 sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED);
2672 Slog.w(TAG, "Could not unmount OBB: " + existingState);
2673 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT);
2678 public void handleError() {
2679 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2683 public String toString() {
2684 StringBuilder sb = new StringBuilder();
2685 sb.append("UnmountObbAction{");
2686 sb.append(mObbState);
2687 sb.append(",force=");
2688 sb.append(mForceUnmount);
2690 return sb.toString();
2695 public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) {
2696 // TODO: allow caller to provide Environment for full testing
2697 // TODO: extend to support OBB mounts on secondary external storage
2699 // Only adjust paths when storage is emulated
2700 if (!Environment.isExternalStorageEmulated()) {
2701 return canonicalPath;
2704 String path = canonicalPath.toString();
2706 // First trim off any external storage prefix
2707 final UserEnvironment userEnv = new UserEnvironment(userId);
2709 // /storage/emulated/0
2710 final String externalPath = userEnv.getExternalStorageDirectory().getAbsolutePath();
2711 // /storage/emulated_legacy
2712 final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory()
2715 if (path.startsWith(externalPath)) {
2716 path = path.substring(externalPath.length() + 1);
2717 } else if (path.startsWith(legacyExternalPath)) {
2718 path = path.substring(legacyExternalPath.length() + 1);
2720 return canonicalPath;
2723 // Handle special OBB paths on emulated storage
2724 final String obbPath = "Android/obb";
2725 if (path.startsWith(obbPath)) {
2726 path = path.substring(obbPath.length() + 1);
2729 return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath();
2731 final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
2732 return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
2737 // Handle normal external storage paths
2739 return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
2741 return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath();
2746 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2747 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
2748 pw.println("Permission Denial: can't dump ActivityManager from from pid="
2749 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
2750 + " without permission " + android.Manifest.permission.DUMP);
2754 synchronized (mObbMounts) {
2755 pw.println(" mObbMounts:");
2757 final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator();
2758 while (binders.hasNext()) {
2759 Entry<IBinder, List<ObbState>> e = binders.next();
2760 pw.print(" Key="); pw.println(e.getKey().toString());
2761 final List<ObbState> obbStates = e.getValue();
2762 for (final ObbState obbState : obbStates) {
2763 pw.print(" "); pw.println(obbState.toString());
2768 pw.println(" mObbPathToStateMap:");
2769 final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
2770 while (maps.hasNext()) {
2771 final Entry<String, ObbState> e = maps.next();
2772 pw.print(" "); pw.print(e.getKey());
2773 pw.print(" -> "); pw.println(e.getValue().toString());
2779 synchronized (mVolumesLock) {
2780 pw.println(" mVolumes:");
2782 final int N = mVolumes.size();
2783 for (int i = 0; i < N; i++) {
2784 final StorageVolume v = mVolumes.get(i);
2786 pw.println(v.toString());
2787 pw.println(" state=" + mVolumeStates.get(v.getPath()));
2792 pw.println(" mConnection:");
2793 mConnector.dump(fd, pw, args);
2796 /** {@inheritDoc} */
2797 public void monitor() {
2798 if (mConnector != null) {
2799 mConnector.monitor();