MountService.java revision 4fbbda4cecb078bd3867f416b02cc75f5455284f
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17package com.android.server; 18 19import static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 21import android.Manifest; 22import android.content.BroadcastReceiver; 23import android.content.ComponentName; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.ServiceConnection; 28import android.content.pm.PackageManager; 29import android.content.pm.UserInfo; 30import android.content.res.ObbInfo; 31import android.content.res.Resources; 32import android.content.res.TypedArray; 33import android.content.res.XmlResourceParser; 34import android.hardware.usb.UsbManager; 35import android.net.Uri; 36import android.os.Binder; 37import android.os.Environment; 38import android.os.Environment.UserEnvironment; 39import android.os.Handler; 40import android.os.HandlerThread; 41import android.os.IBinder; 42import android.os.Looper; 43import android.os.Message; 44import android.os.RemoteException; 45import android.os.ServiceManager; 46import android.os.SystemProperties; 47import android.os.UserHandle; 48import android.os.storage.IMountService; 49import android.os.storage.IMountServiceListener; 50import android.os.storage.IMountShutdownObserver; 51import android.os.storage.IObbActionListener; 52import android.os.storage.OnObbStateChangeListener; 53import android.os.storage.StorageResultCode; 54import android.os.storage.StorageVolume; 55import android.text.TextUtils; 56import android.util.AttributeSet; 57import android.util.Slog; 58import android.util.Xml; 59 60import com.android.internal.app.IMediaContainerService; 61import com.android.internal.util.Preconditions; 62import com.android.internal.util.XmlUtils; 63import com.android.server.NativeDaemonConnector.Command; 64import com.android.server.am.ActivityManagerService; 65import com.android.server.pm.PackageManagerService; 66import com.android.server.pm.UserManagerService; 67import com.google.android.collect.Lists; 68import com.google.android.collect.Maps; 69 70import org.xmlpull.v1.XmlPullParserException; 71 72import java.io.File; 73import java.io.FileDescriptor; 74import java.io.IOException; 75import java.io.PrintWriter; 76import java.math.BigInteger; 77import java.security.NoSuchAlgorithmException; 78import java.security.spec.InvalidKeySpecException; 79import java.security.spec.KeySpec; 80import java.util.ArrayList; 81import java.util.HashMap; 82import java.util.HashSet; 83import java.util.Iterator; 84import java.util.LinkedList; 85import java.util.List; 86import java.util.Map; 87import java.util.Map.Entry; 88import java.util.concurrent.CountDownLatch; 89import java.util.concurrent.TimeUnit; 90 91import javax.crypto.SecretKey; 92import javax.crypto.SecretKeyFactory; 93import javax.crypto.spec.PBEKeySpec; 94 95/** 96 * MountService implements back-end services for platform storage 97 * management. 98 * @hide - Applications should use android.os.storage.StorageManager 99 * to access the MountService. 100 */ 101class MountService extends IMountService.Stub 102 implements INativeDaemonConnectorCallbacks, Watchdog.Monitor { 103 104 // TODO: listen for user creation/deletion 105 106 private static final boolean LOCAL_LOGD = true; 107 private static final boolean DEBUG_UNMOUNT = true; 108 private static final boolean DEBUG_EVENTS = true; 109 private static final boolean DEBUG_OBB = false; 110 111 // Disable this since it messes up long-running cryptfs operations. 112 private static final boolean WATCHDOG_ENABLE = false; 113 114 private static final String TAG = "MountService"; 115 116 private static final String VOLD_TAG = "VoldConnector"; 117 118 /** Maximum number of ASEC containers allowed to be mounted. */ 119 private static final int MAX_CONTAINERS = 250; 120 121 /* 122 * Internal vold volume state constants 123 */ 124 class VolumeState { 125 public static final int Init = -1; 126 public static final int NoMedia = 0; 127 public static final int Idle = 1; 128 public static final int Pending = 2; 129 public static final int Checking = 3; 130 public static final int Mounted = 4; 131 public static final int Unmounting = 5; 132 public static final int Formatting = 6; 133 public static final int Shared = 7; 134 public static final int SharedMnt = 8; 135 } 136 137 /* 138 * Internal vold response code constants 139 */ 140 class VoldResponseCode { 141 /* 142 * 100 series - Requestion action was initiated; expect another reply 143 * before proceeding with a new command. 144 */ 145 public static final int VolumeListResult = 110; 146 public static final int AsecListResult = 111; 147 public static final int StorageUsersListResult = 112; 148 149 /* 150 * 200 series - Requestion action has been successfully completed. 151 */ 152 public static final int ShareStatusResult = 210; 153 public static final int AsecPathResult = 211; 154 public static final int ShareEnabledResult = 212; 155 156 /* 157 * 400 series - Command was accepted, but the requested action 158 * did not take place. 159 */ 160 public static final int OpFailedNoMedia = 401; 161 public static final int OpFailedMediaBlank = 402; 162 public static final int OpFailedMediaCorrupt = 403; 163 public static final int OpFailedVolNotMounted = 404; 164 public static final int OpFailedStorageBusy = 405; 165 public static final int OpFailedStorageNotFound = 406; 166 167 /* 168 * 600 series - Unsolicited broadcasts. 169 */ 170 public static final int VolumeStateChange = 605; 171 public static final int VolumeDiskInserted = 630; 172 public static final int VolumeDiskRemoved = 631; 173 public static final int VolumeBadRemoval = 632; 174 } 175 176 private Context mContext; 177 private NativeDaemonConnector mConnector; 178 179 private final Object mVolumesLock = new Object(); 180 181 /** When defined, base template for user-specific {@link StorageVolume}. */ 182 private StorageVolume mEmulatedTemplate; 183 184 // @GuardedBy("mVolumesLock") 185 private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList(); 186 /** Map from path to {@link StorageVolume} */ 187 // @GuardedBy("mVolumesLock") 188 private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap(); 189 /** Map from path to state */ 190 // @GuardedBy("mVolumesLock") 191 private final HashMap<String, String> mVolumeStates = Maps.newHashMap(); 192 193 private volatile boolean mSystemReady = false; 194 195 private PackageManagerService mPms; 196 private boolean mUmsEnabling; 197 private boolean mUmsAvailable = false; 198 // Used as a lock for methods that register/unregister listeners. 199 final private ArrayList<MountServiceBinderListener> mListeners = 200 new ArrayList<MountServiceBinderListener>(); 201 private CountDownLatch mConnectedSignal = new CountDownLatch(1); 202 private CountDownLatch mAsecsScanned = new CountDownLatch(1); 203 private boolean mSendUmsConnectedOnBoot = false; 204 205 /** 206 * Private hash of currently mounted secure containers. 207 * Used as a lock in methods to manipulate secure containers. 208 */ 209 final private HashSet<String> mAsecMountSet = new HashSet<String>(); 210 211 /** 212 * The size of the crypto algorithm key in bits for OBB files. Currently 213 * Twofish is used which takes 128-bit keys. 214 */ 215 private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128; 216 217 /** 218 * The number of times to run SHA1 in the PBKDF2 function for OBB files. 219 * 1024 is reasonably secure and not too slow. 220 */ 221 private static final int PBKDF2_HASH_ROUNDS = 1024; 222 223 /** 224 * Mounted OBB tracking information. Used to track the current state of all 225 * OBBs. 226 */ 227 final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>(); 228 229 /** Map from raw paths to {@link ObbState}. */ 230 final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>(); 231 232 class ObbState implements IBinder.DeathRecipient { 233 public ObbState(String rawPath, String canonicalPath, int callingUid, 234 IObbActionListener token, int nonce) { 235 this.rawPath = rawPath; 236 this.canonicalPath = canonicalPath.toString(); 237 238 final int userId = UserHandle.getUserId(callingUid); 239 this.ownerPath = buildObbPath(canonicalPath, userId, false); 240 this.voldPath = buildObbPath(canonicalPath, userId, true); 241 242 this.ownerGid = UserHandle.getSharedAppGid(callingUid); 243 this.token = token; 244 this.nonce = nonce; 245 } 246 247 final String rawPath; 248 final String canonicalPath; 249 final String ownerPath; 250 final String voldPath; 251 252 final int ownerGid; 253 254 // Token of remote Binder caller 255 final IObbActionListener token; 256 257 // Identifier to pass back to the token 258 final int nonce; 259 260 public IBinder getBinder() { 261 return token.asBinder(); 262 } 263 264 @Override 265 public void binderDied() { 266 ObbAction action = new UnmountObbAction(this, true); 267 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 268 } 269 270 public void link() throws RemoteException { 271 getBinder().linkToDeath(this, 0); 272 } 273 274 public void unlink() { 275 getBinder().unlinkToDeath(this, 0); 276 } 277 278 @Override 279 public String toString() { 280 StringBuilder sb = new StringBuilder("ObbState{"); 281 sb.append("rawPath=").append(rawPath); 282 sb.append(",canonicalPath=").append(canonicalPath); 283 sb.append(",ownerPath=").append(ownerPath); 284 sb.append(",voldPath=").append(voldPath); 285 sb.append(",ownerGid=").append(ownerGid); 286 sb.append(",token=").append(token); 287 sb.append(",binder=").append(getBinder()); 288 sb.append('}'); 289 return sb.toString(); 290 } 291 } 292 293 // OBB Action Handler 294 final private ObbActionHandler mObbActionHandler; 295 296 // OBB action handler messages 297 private static final int OBB_RUN_ACTION = 1; 298 private static final int OBB_MCS_BOUND = 2; 299 private static final int OBB_MCS_UNBIND = 3; 300 private static final int OBB_MCS_RECONNECT = 4; 301 private static final int OBB_FLUSH_MOUNT_STATE = 5; 302 303 /* 304 * Default Container Service information 305 */ 306 static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( 307 "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService"); 308 309 final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); 310 311 class DefaultContainerConnection implements ServiceConnection { 312 public void onServiceConnected(ComponentName name, IBinder service) { 313 if (DEBUG_OBB) 314 Slog.i(TAG, "onServiceConnected"); 315 IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); 316 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs)); 317 } 318 319 public void onServiceDisconnected(ComponentName name) { 320 if (DEBUG_OBB) 321 Slog.i(TAG, "onServiceDisconnected"); 322 } 323 }; 324 325 // Used in the ObbActionHandler 326 private IMediaContainerService mContainerService = null; 327 328 // Handler messages 329 private static final int H_UNMOUNT_PM_UPDATE = 1; 330 private static final int H_UNMOUNT_PM_DONE = 2; 331 private static final int H_UNMOUNT_MS = 3; 332 private static final int H_SYSTEM_READY = 4; 333 334 private static final int RETRY_UNMOUNT_DELAY = 30; // in ms 335 private static final int MAX_UNMOUNT_RETRIES = 4; 336 337 class UnmountCallBack { 338 final String path; 339 final boolean force; 340 final boolean removeEncryption; 341 int retries; 342 343 UnmountCallBack(String path, boolean force, boolean removeEncryption) { 344 retries = 0; 345 this.path = path; 346 this.force = force; 347 this.removeEncryption = removeEncryption; 348 } 349 350 void handleFinished() { 351 if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path); 352 doUnmountVolume(path, true, removeEncryption); 353 } 354 } 355 356 class UmsEnableCallBack extends UnmountCallBack { 357 final String method; 358 359 UmsEnableCallBack(String path, String method, boolean force) { 360 super(path, force, false); 361 this.method = method; 362 } 363 364 @Override 365 void handleFinished() { 366 super.handleFinished(); 367 doShareUnshareVolume(path, method, true); 368 } 369 } 370 371 class ShutdownCallBack extends UnmountCallBack { 372 IMountShutdownObserver observer; 373 ShutdownCallBack(String path, IMountShutdownObserver observer) { 374 super(path, true, false); 375 this.observer = observer; 376 } 377 378 @Override 379 void handleFinished() { 380 int ret = doUnmountVolume(path, true, removeEncryption); 381 if (observer != null) { 382 try { 383 observer.onShutDownComplete(ret); 384 } catch (RemoteException e) { 385 Slog.w(TAG, "RemoteException when shutting down"); 386 } 387 } 388 } 389 } 390 391 class MountServiceHandler extends Handler { 392 ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>(); 393 boolean mUpdatingStatus = false; 394 395 MountServiceHandler(Looper l) { 396 super(l); 397 } 398 399 @Override 400 public void handleMessage(Message msg) { 401 switch (msg.what) { 402 case H_UNMOUNT_PM_UPDATE: { 403 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE"); 404 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 405 mForceUnmounts.add(ucb); 406 if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus); 407 // Register only if needed. 408 if (!mUpdatingStatus) { 409 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager"); 410 mUpdatingStatus = true; 411 mPms.updateExternalMediaStatus(false, true); 412 } 413 break; 414 } 415 case H_UNMOUNT_PM_DONE: { 416 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE"); 417 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests"); 418 mUpdatingStatus = false; 419 int size = mForceUnmounts.size(); 420 int sizeArr[] = new int[size]; 421 int sizeArrN = 0; 422 // Kill processes holding references first 423 ActivityManagerService ams = (ActivityManagerService) 424 ServiceManager.getService("activity"); 425 for (int i = 0; i < size; i++) { 426 UnmountCallBack ucb = mForceUnmounts.get(i); 427 String path = ucb.path; 428 boolean done = false; 429 if (!ucb.force) { 430 done = true; 431 } else { 432 int pids[] = getStorageUsers(path); 433 if (pids == null || pids.length == 0) { 434 done = true; 435 } else { 436 // Eliminate system process here? 437 ams.killPids(pids, "unmount media", true); 438 // Confirm if file references have been freed. 439 pids = getStorageUsers(path); 440 if (pids == null || pids.length == 0) { 441 done = true; 442 } 443 } 444 } 445 if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) { 446 // Retry again 447 Slog.i(TAG, "Retrying to kill storage users again"); 448 mHandler.sendMessageDelayed( 449 mHandler.obtainMessage(H_UNMOUNT_PM_DONE, 450 ucb.retries++), 451 RETRY_UNMOUNT_DELAY); 452 } else { 453 if (ucb.retries >= MAX_UNMOUNT_RETRIES) { 454 Slog.i(TAG, "Failed to unmount media inspite of " + 455 MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now"); 456 } 457 sizeArr[sizeArrN++] = i; 458 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS, 459 ucb)); 460 } 461 } 462 // Remove already processed elements from list. 463 for (int i = (sizeArrN-1); i >= 0; i--) { 464 mForceUnmounts.remove(sizeArr[i]); 465 } 466 break; 467 } 468 case H_UNMOUNT_MS: { 469 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS"); 470 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 471 ucb.handleFinished(); 472 break; 473 } 474 case H_SYSTEM_READY: { 475 try { 476 handleSystemReady(); 477 } catch (Exception ex) { 478 Slog.e(TAG, "Boot-time mount exception", ex); 479 } 480 break; 481 } 482 } 483 } 484 }; 485 486 private final HandlerThread mHandlerThread; 487 private final Handler mHandler; 488 489 void waitForAsecScan() { 490 waitForLatch(mAsecsScanned); 491 } 492 493 private void waitForReady() { 494 waitForLatch(mConnectedSignal); 495 } 496 497 private void waitForLatch(CountDownLatch latch) { 498 if (latch == null) { 499 return; 500 } 501 502 for (;;) { 503 try { 504 if (latch.await(5000, TimeUnit.MILLISECONDS)) { 505 return; 506 } else { 507 Slog.w(TAG, "Thread " + Thread.currentThread().getName() 508 + " still waiting for MountService ready..."); 509 } 510 } catch (InterruptedException e) { 511 Slog.w(TAG, "Interrupt while waiting for MountService to be ready."); 512 } 513 } 514 } 515 516 private void handleSystemReady() { 517 // Snapshot current volume states since it's not safe to call into vold 518 // while holding locks. 519 final HashMap<String, String> snapshot; 520 synchronized (mVolumesLock) { 521 snapshot = new HashMap<String, String>(mVolumeStates); 522 } 523 524 for (Map.Entry<String, String> entry : snapshot.entrySet()) { 525 final String path = entry.getKey(); 526 final String state = entry.getValue(); 527 528 if (state.equals(Environment.MEDIA_UNMOUNTED)) { 529 int rc = doMountVolume(path); 530 if (rc != StorageResultCode.OperationSucceeded) { 531 Slog.e(TAG, String.format("Boot-time mount failed (%d)", 532 rc)); 533 } 534 } else if (state.equals(Environment.MEDIA_SHARED)) { 535 /* 536 * Bootstrap UMS enabled state since vold indicates 537 * the volume is shared (runtime restart while ums enabled) 538 */ 539 notifyVolumeStateChange(null, path, VolumeState.NoMedia, 540 VolumeState.Shared); 541 } 542 } 543 544 // Push mounted state for all emulated storage 545 synchronized (mVolumesLock) { 546 for (StorageVolume volume : mVolumes) { 547 if (volume.isEmulated()) { 548 updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); 549 } 550 } 551 } 552 553 /* 554 * If UMS was connected on boot, send the connected event 555 * now that we're up. 556 */ 557 if (mSendUmsConnectedOnBoot) { 558 sendUmsIntent(true); 559 mSendUmsConnectedOnBoot = false; 560 } 561 } 562 563 private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { 564 @Override 565 public void onReceive(Context context, Intent intent) { 566 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 567 if (userId == -1) return; 568 final UserHandle user = new UserHandle(userId); 569 570 final String action = intent.getAction(); 571 if (Intent.ACTION_USER_ADDED.equals(action)) { 572 synchronized (mVolumesLock) { 573 createEmulatedVolumeForUserLocked(user); 574 } 575 576 } else if (Intent.ACTION_USER_REMOVED.equals(action)) { 577 synchronized (mVolumesLock) { 578 final List<StorageVolume> toRemove = Lists.newArrayList(); 579 for (StorageVolume volume : mVolumes) { 580 if (user.equals(volume.getOwner())) { 581 toRemove.add(volume); 582 } 583 } 584 for (StorageVolume volume : toRemove) { 585 removeVolumeLocked(volume); 586 } 587 } 588 } 589 } 590 }; 591 592 private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { 593 @Override 594 public void onReceive(Context context, Intent intent) { 595 boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) && 596 intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false)); 597 notifyShareAvailabilityChange(available); 598 } 599 }; 600 601 private final class MountServiceBinderListener implements IBinder.DeathRecipient { 602 final IMountServiceListener mListener; 603 604 MountServiceBinderListener(IMountServiceListener listener) { 605 mListener = listener; 606 607 } 608 609 public void binderDied() { 610 if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!"); 611 synchronized (mListeners) { 612 mListeners.remove(this); 613 mListener.asBinder().unlinkToDeath(this, 0); 614 } 615 } 616 } 617 618 private void doShareUnshareVolume(String path, String method, boolean enable) { 619 // TODO: Add support for multiple share methods 620 if (!method.equals("ums")) { 621 throw new IllegalArgumentException(String.format("Method %s not supported", method)); 622 } 623 624 try { 625 mConnector.execute("volume", enable ? "share" : "unshare", path, method); 626 } catch (NativeDaemonConnectorException e) { 627 Slog.e(TAG, "Failed to share/unshare", e); 628 } 629 } 630 631 private void updatePublicVolumeState(StorageVolume volume, String state) { 632 final String path = volume.getPath(); 633 final String oldState; 634 synchronized (mVolumesLock) { 635 oldState = mVolumeStates.put(path, state); 636 } 637 638 if (state.equals(oldState)) { 639 Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s", 640 state, state, path)); 641 return; 642 } 643 644 Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")"); 645 646 // Tell PackageManager about changes to primary volume state, but only 647 // when not emulated. 648 if (volume.isPrimary() && !volume.isEmulated()) { 649 if (Environment.MEDIA_UNMOUNTED.equals(state)) { 650 mPms.updateExternalMediaStatus(false, false); 651 652 /* 653 * Some OBBs might have been unmounted when this volume was 654 * unmounted, so send a message to the handler to let it know to 655 * remove those from the list of mounted OBBS. 656 */ 657 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( 658 OBB_FLUSH_MOUNT_STATE, path)); 659 } else if (Environment.MEDIA_MOUNTED.equals(state)) { 660 mPms.updateExternalMediaStatus(true, false); 661 } 662 } 663 664 synchronized (mListeners) { 665 for (int i = mListeners.size() -1; i >= 0; i--) { 666 MountServiceBinderListener bl = mListeners.get(i); 667 try { 668 bl.mListener.onStorageStateChanged(path, oldState, state); 669 } catch (RemoteException rex) { 670 Slog.e(TAG, "Listener dead"); 671 mListeners.remove(i); 672 } catch (Exception ex) { 673 Slog.e(TAG, "Listener failed", ex); 674 } 675 } 676 } 677 } 678 679 /** 680 * Callback from NativeDaemonConnector 681 */ 682 public void onDaemonConnected() { 683 /* 684 * Since we'll be calling back into the NativeDaemonConnector, 685 * we need to do our work in a new thread. 686 */ 687 new Thread("MountService#onDaemonConnected") { 688 @Override 689 public void run() { 690 /** 691 * Determine media state and UMS detection status 692 */ 693 try { 694 final String[] vols = NativeDaemonEvent.filterMessageList( 695 mConnector.executeForList("volume", "list"), 696 VoldResponseCode.VolumeListResult); 697 for (String volstr : vols) { 698 String[] tok = volstr.split(" "); 699 // FMT: <label> <mountpoint> <state> 700 String path = tok[1]; 701 String state = Environment.MEDIA_REMOVED; 702 703 final StorageVolume volume; 704 synchronized (mVolumesLock) { 705 volume = mVolumesByPath.get(path); 706 } 707 708 int st = Integer.parseInt(tok[2]); 709 if (st == VolumeState.NoMedia) { 710 state = Environment.MEDIA_REMOVED; 711 } else if (st == VolumeState.Idle) { 712 state = Environment.MEDIA_UNMOUNTED; 713 } else if (st == VolumeState.Mounted) { 714 state = Environment.MEDIA_MOUNTED; 715 Slog.i(TAG, "Media already mounted on daemon connection"); 716 } else if (st == VolumeState.Shared) { 717 state = Environment.MEDIA_SHARED; 718 Slog.i(TAG, "Media shared on daemon connection"); 719 } else { 720 throw new Exception(String.format("Unexpected state %d", st)); 721 } 722 723 if (state != null) { 724 if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state); 725 updatePublicVolumeState(volume, state); 726 } 727 } 728 } catch (Exception e) { 729 Slog.e(TAG, "Error processing initial volume state", e); 730 final StorageVolume primary = getPrimaryPhysicalVolume(); 731 if (primary != null) { 732 updatePublicVolumeState(primary, Environment.MEDIA_REMOVED); 733 } 734 } 735 736 /* 737 * Now that we've done our initialization, release 738 * the hounds! 739 */ 740 mConnectedSignal.countDown(); 741 mConnectedSignal = null; 742 743 // Let package manager load internal ASECs. 744 mPms.scanAvailableAsecs(); 745 746 // Notify people waiting for ASECs to be scanned that it's done. 747 mAsecsScanned.countDown(); 748 mAsecsScanned = null; 749 } 750 }.start(); 751 } 752 753 /** 754 * Callback from NativeDaemonConnector 755 */ 756 public boolean onEvent(int code, String raw, String[] cooked) { 757 if (DEBUG_EVENTS) { 758 StringBuilder builder = new StringBuilder(); 759 builder.append("onEvent::"); 760 builder.append(" raw= " + raw); 761 if (cooked != null) { 762 builder.append(" cooked = " ); 763 for (String str : cooked) { 764 builder.append(" " + str); 765 } 766 } 767 Slog.i(TAG, builder.toString()); 768 } 769 if (code == VoldResponseCode.VolumeStateChange) { 770 /* 771 * One of the volumes we're managing has changed state. 772 * Format: "NNN Volume <label> <path> state changed 773 * from <old_#> (<old_str>) to <new_#> (<new_str>)" 774 */ 775 notifyVolumeStateChange( 776 cooked[2], cooked[3], Integer.parseInt(cooked[7]), 777 Integer.parseInt(cooked[10])); 778 } else if ((code == VoldResponseCode.VolumeDiskInserted) || 779 (code == VoldResponseCode.VolumeDiskRemoved) || 780 (code == VoldResponseCode.VolumeBadRemoval)) { 781 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>) 782 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>) 783 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>) 784 String action = null; 785 final String label = cooked[2]; 786 final String path = cooked[3]; 787 int major = -1; 788 int minor = -1; 789 790 try { 791 String devComp = cooked[6].substring(1, cooked[6].length() -1); 792 String[] devTok = devComp.split(":"); 793 major = Integer.parseInt(devTok[0]); 794 minor = Integer.parseInt(devTok[1]); 795 } catch (Exception ex) { 796 Slog.e(TAG, "Failed to parse major/minor", ex); 797 } 798 799 final StorageVolume volume; 800 final String state; 801 synchronized (mVolumesLock) { 802 volume = mVolumesByPath.get(path); 803 state = mVolumeStates.get(path); 804 } 805 806 if (code == VoldResponseCode.VolumeDiskInserted) { 807 new Thread() { 808 @Override 809 public void run() { 810 try { 811 int rc; 812 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 813 Slog.w(TAG, String.format("Insertion mount failed (%d)", rc)); 814 } 815 } catch (Exception ex) { 816 Slog.w(TAG, "Failed to mount media on insertion", ex); 817 } 818 } 819 }.start(); 820 } else if (code == VoldResponseCode.VolumeDiskRemoved) { 821 /* 822 * This event gets trumped if we're already in BAD_REMOVAL state 823 */ 824 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) { 825 return true; 826 } 827 /* Send the media unmounted event first */ 828 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 829 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); 830 sendStorageIntent(Environment.MEDIA_UNMOUNTED, volume, UserHandle.ALL); 831 832 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed"); 833 updatePublicVolumeState(volume, Environment.MEDIA_REMOVED); 834 action = Intent.ACTION_MEDIA_REMOVED; 835 } else if (code == VoldResponseCode.VolumeBadRemoval) { 836 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 837 /* Send the media unmounted event first */ 838 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); 839 action = Intent.ACTION_MEDIA_UNMOUNTED; 840 841 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal"); 842 updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL); 843 action = Intent.ACTION_MEDIA_BAD_REMOVAL; 844 } else { 845 Slog.e(TAG, String.format("Unknown code {%d}", code)); 846 } 847 848 if (action != null) { 849 sendStorageIntent(action, volume, UserHandle.ALL); 850 } 851 } else { 852 return false; 853 } 854 855 return true; 856 } 857 858 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) { 859 final StorageVolume volume; 860 final String state; 861 synchronized (mVolumesLock) { 862 volume = mVolumesByPath.get(path); 863 state = getVolumeState(path); 864 } 865 866 if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state); 867 868 String action = null; 869 870 if (oldState == VolumeState.Shared && newState != oldState) { 871 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent"); 872 sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL); 873 } 874 875 if (newState == VolumeState.Init) { 876 } else if (newState == VolumeState.NoMedia) { 877 // NoMedia is handled via Disk Remove events 878 } else if (newState == VolumeState.Idle) { 879 /* 880 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or 881 * if we're in the process of enabling UMS 882 */ 883 if (!state.equals( 884 Environment.MEDIA_BAD_REMOVAL) && !state.equals( 885 Environment.MEDIA_NOFS) && !state.equals( 886 Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) { 887 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable"); 888 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); 889 action = Intent.ACTION_MEDIA_UNMOUNTED; 890 } 891 } else if (newState == VolumeState.Pending) { 892 } else if (newState == VolumeState.Checking) { 893 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking"); 894 updatePublicVolumeState(volume, Environment.MEDIA_CHECKING); 895 action = Intent.ACTION_MEDIA_CHECKING; 896 } else if (newState == VolumeState.Mounted) { 897 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted"); 898 updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); 899 action = Intent.ACTION_MEDIA_MOUNTED; 900 } else if (newState == VolumeState.Unmounting) { 901 action = Intent.ACTION_MEDIA_EJECT; 902 } else if (newState == VolumeState.Formatting) { 903 } else if (newState == VolumeState.Shared) { 904 if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted"); 905 /* Send the media unmounted event first */ 906 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); 907 sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL); 908 909 if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared"); 910 updatePublicVolumeState(volume, Environment.MEDIA_SHARED); 911 action = Intent.ACTION_MEDIA_SHARED; 912 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent"); 913 } else if (newState == VolumeState.SharedMnt) { 914 Slog.e(TAG, "Live shared mounts not supported yet!"); 915 return; 916 } else { 917 Slog.e(TAG, "Unhandled VolumeState {" + newState + "}"); 918 } 919 920 if (action != null) { 921 sendStorageIntent(action, volume, UserHandle.ALL); 922 } 923 } 924 925 private int doMountVolume(String path) { 926 int rc = StorageResultCode.OperationSucceeded; 927 928 final StorageVolume volume; 929 synchronized (mVolumesLock) { 930 volume = mVolumesByPath.get(path); 931 } 932 933 if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); 934 try { 935 mConnector.execute("volume", "mount", path); 936 } catch (NativeDaemonConnectorException e) { 937 /* 938 * Mount failed for some reason 939 */ 940 String action = null; 941 int code = e.getCode(); 942 if (code == VoldResponseCode.OpFailedNoMedia) { 943 /* 944 * Attempt to mount but no media inserted 945 */ 946 rc = StorageResultCode.OperationFailedNoMedia; 947 } else if (code == VoldResponseCode.OpFailedMediaBlank) { 948 if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs"); 949 /* 950 * Media is blank or does not contain a supported filesystem 951 */ 952 updatePublicVolumeState(volume, Environment.MEDIA_NOFS); 953 action = Intent.ACTION_MEDIA_NOFS; 954 rc = StorageResultCode.OperationFailedMediaBlank; 955 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 956 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt"); 957 /* 958 * Volume consistency check failed 959 */ 960 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE); 961 action = Intent.ACTION_MEDIA_UNMOUNTABLE; 962 rc = StorageResultCode.OperationFailedMediaCorrupt; 963 } else { 964 rc = StorageResultCode.OperationFailedInternalError; 965 } 966 967 /* 968 * Send broadcast intent (if required for the failure) 969 */ 970 if (action != null) { 971 sendStorageIntent(action, volume, UserHandle.ALL); 972 } 973 } 974 975 return rc; 976 } 977 978 /* 979 * If force is not set, we do not unmount if there are 980 * processes holding references to the volume about to be unmounted. 981 * If force is set, all the processes holding references need to be 982 * killed via the ActivityManager before actually unmounting the volume. 983 * This might even take a while and might be retried after timed delays 984 * to make sure we dont end up in an instable state and kill some core 985 * processes. 986 * If removeEncryption is set, force is implied, and the system will remove any encryption 987 * mapping set on the volume when unmounting. 988 */ 989 private int doUnmountVolume(String path, boolean force, boolean removeEncryption) { 990 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) { 991 return VoldResponseCode.OpFailedVolNotMounted; 992 } 993 994 /* 995 * Force a GC to make sure AssetManagers in other threads of the 996 * system_server are cleaned up. We have to do this since AssetManager 997 * instances are kept as a WeakReference and it's possible we have files 998 * open on the external storage. 999 */ 1000 Runtime.getRuntime().gc(); 1001 1002 // Redundant probably. But no harm in updating state again. 1003 mPms.updateExternalMediaStatus(false, false); 1004 try { 1005 final Command cmd = new Command("volume", "unmount", path); 1006 if (removeEncryption) { 1007 cmd.appendArg("force_and_revert"); 1008 } else if (force) { 1009 cmd.appendArg("force"); 1010 } 1011 mConnector.execute(cmd); 1012 // We unmounted the volume. None of the asec containers are available now. 1013 synchronized (mAsecMountSet) { 1014 mAsecMountSet.clear(); 1015 } 1016 return StorageResultCode.OperationSucceeded; 1017 } catch (NativeDaemonConnectorException e) { 1018 // Don't worry about mismatch in PackageManager since the 1019 // call back will handle the status changes any way. 1020 int code = e.getCode(); 1021 if (code == VoldResponseCode.OpFailedVolNotMounted) { 1022 return StorageResultCode.OperationFailedStorageNotMounted; 1023 } else if (code == VoldResponseCode.OpFailedStorageBusy) { 1024 return StorageResultCode.OperationFailedStorageBusy; 1025 } else { 1026 return StorageResultCode.OperationFailedInternalError; 1027 } 1028 } 1029 } 1030 1031 private int doFormatVolume(String path) { 1032 try { 1033 mConnector.execute("volume", "format", path); 1034 return StorageResultCode.OperationSucceeded; 1035 } catch (NativeDaemonConnectorException e) { 1036 int code = e.getCode(); 1037 if (code == VoldResponseCode.OpFailedNoMedia) { 1038 return StorageResultCode.OperationFailedNoMedia; 1039 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 1040 return StorageResultCode.OperationFailedMediaCorrupt; 1041 } else { 1042 return StorageResultCode.OperationFailedInternalError; 1043 } 1044 } 1045 } 1046 1047 private boolean doGetVolumeShared(String path, String method) { 1048 final NativeDaemonEvent event; 1049 try { 1050 event = mConnector.execute("volume", "shared", path, method); 1051 } catch (NativeDaemonConnectorException ex) { 1052 Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); 1053 return false; 1054 } 1055 1056 if (event.getCode() == VoldResponseCode.ShareEnabledResult) { 1057 return event.getMessage().endsWith("enabled"); 1058 } else { 1059 return false; 1060 } 1061 } 1062 1063 private void notifyShareAvailabilityChange(final boolean avail) { 1064 synchronized (mListeners) { 1065 mUmsAvailable = avail; 1066 for (int i = mListeners.size() -1; i >= 0; i--) { 1067 MountServiceBinderListener bl = mListeners.get(i); 1068 try { 1069 bl.mListener.onUsbMassStorageConnectionChanged(avail); 1070 } catch (RemoteException rex) { 1071 Slog.e(TAG, "Listener dead"); 1072 mListeners.remove(i); 1073 } catch (Exception ex) { 1074 Slog.e(TAG, "Listener failed", ex); 1075 } 1076 } 1077 } 1078 1079 if (mSystemReady == true) { 1080 sendUmsIntent(avail); 1081 } else { 1082 mSendUmsConnectedOnBoot = avail; 1083 } 1084 1085 final StorageVolume primary = getPrimaryPhysicalVolume(); 1086 if (avail == false && primary != null 1087 && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) { 1088 final String path = primary.getPath(); 1089 /* 1090 * USB mass storage disconnected while enabled 1091 */ 1092 new Thread() { 1093 @Override 1094 public void run() { 1095 try { 1096 int rc; 1097 Slog.w(TAG, "Disabling UMS after cable disconnect"); 1098 doShareUnshareVolume(path, "ums", false); 1099 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 1100 Slog.e(TAG, String.format( 1101 "Failed to remount {%s} on UMS enabled-disconnect (%d)", 1102 path, rc)); 1103 } 1104 } catch (Exception ex) { 1105 Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex); 1106 } 1107 } 1108 }.start(); 1109 } 1110 } 1111 1112 private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) { 1113 final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath())); 1114 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume); 1115 Slog.d(TAG, "sendStorageIntent " + intent + " to " + user); 1116 mContext.sendBroadcastAsUser(intent, user); 1117 } 1118 1119 private void sendUmsIntent(boolean c) { 1120 mContext.sendBroadcastAsUser( 1121 new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)), 1122 UserHandle.ALL); 1123 } 1124 1125 private void validatePermission(String perm) { 1126 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { 1127 throw new SecurityException(String.format("Requires %s permission", perm)); 1128 } 1129 } 1130 1131 // Storage list XML tags 1132 private static final String TAG_STORAGE_LIST = "StorageList"; 1133 private static final String TAG_STORAGE = "storage"; 1134 1135 private void readStorageListLocked() { 1136 mVolumes.clear(); 1137 mVolumeStates.clear(); 1138 1139 Resources resources = mContext.getResources(); 1140 1141 int id = com.android.internal.R.xml.storage_list; 1142 XmlResourceParser parser = resources.getXml(id); 1143 AttributeSet attrs = Xml.asAttributeSet(parser); 1144 1145 try { 1146 XmlUtils.beginDocument(parser, TAG_STORAGE_LIST); 1147 while (true) { 1148 XmlUtils.nextElement(parser); 1149 1150 String element = parser.getName(); 1151 if (element == null) break; 1152 1153 if (TAG_STORAGE.equals(element)) { 1154 TypedArray a = resources.obtainAttributes(attrs, 1155 com.android.internal.R.styleable.Storage); 1156 1157 String path = a.getString( 1158 com.android.internal.R.styleable.Storage_mountPoint); 1159 int descriptionId = a.getResourceId( 1160 com.android.internal.R.styleable.Storage_storageDescription, -1); 1161 CharSequence description = a.getText( 1162 com.android.internal.R.styleable.Storage_storageDescription); 1163 boolean primary = a.getBoolean( 1164 com.android.internal.R.styleable.Storage_primary, false); 1165 boolean removable = a.getBoolean( 1166 com.android.internal.R.styleable.Storage_removable, false); 1167 boolean emulated = a.getBoolean( 1168 com.android.internal.R.styleable.Storage_emulated, false); 1169 int mtpReserve = a.getInt( 1170 com.android.internal.R.styleable.Storage_mtpReserve, 0); 1171 boolean allowMassStorage = a.getBoolean( 1172 com.android.internal.R.styleable.Storage_allowMassStorage, false); 1173 // resource parser does not support longs, so XML value is in megabytes 1174 long maxFileSize = a.getInt( 1175 com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L; 1176 1177 Slog.d(TAG, "got storage path: " + path + " description: " + description + 1178 " primary: " + primary + " removable: " + removable + 1179 " emulated: " + emulated + " mtpReserve: " + mtpReserve + 1180 " allowMassStorage: " + allowMassStorage + 1181 " maxFileSize: " + maxFileSize); 1182 1183 if (emulated) { 1184 // For devices with emulated storage, we create separate 1185 // volumes for each known user. 1186 mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false, 1187 true, mtpReserve, false, maxFileSize, null); 1188 1189 final UserManagerService userManager = UserManagerService.getInstance(); 1190 for (UserInfo user : userManager.getUsers(false)) { 1191 createEmulatedVolumeForUserLocked(user.getUserHandle()); 1192 } 1193 1194 } else { 1195 if (path == null || description == null) { 1196 Slog.e(TAG, "Missing storage path or description in readStorageList"); 1197 } else { 1198 final StorageVolume volume = new StorageVolume(new File(path), 1199 descriptionId, primary, removable, emulated, mtpReserve, 1200 allowMassStorage, maxFileSize, null); 1201 addVolumeLocked(volume); 1202 } 1203 } 1204 1205 a.recycle(); 1206 } 1207 } 1208 } catch (XmlPullParserException e) { 1209 throw new RuntimeException(e); 1210 } catch (IOException e) { 1211 throw new RuntimeException(e); 1212 } finally { 1213 // Compute storage ID for each physical volume; emulated storage is 1214 // always 0 when defined. 1215 int index = isExternalStorageEmulated() ? 1 : 0; 1216 for (StorageVolume volume : mVolumes) { 1217 if (!volume.isEmulated()) { 1218 volume.setStorageId(index++); 1219 } 1220 } 1221 parser.close(); 1222 } 1223 } 1224 1225 /** 1226 * Create and add new {@link StorageVolume} for given {@link UserHandle} 1227 * using {@link #mEmulatedTemplate} as template. 1228 */ 1229 private void createEmulatedVolumeForUserLocked(UserHandle user) { 1230 if (mEmulatedTemplate == null) { 1231 throw new IllegalStateException("Missing emulated volume multi-user template"); 1232 } 1233 1234 final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier()); 1235 final File path = userEnv.getExternalStorageDirectory(); 1236 final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user); 1237 volume.setStorageId(0); 1238 addVolumeLocked(volume); 1239 1240 if (mSystemReady) { 1241 updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); 1242 } else { 1243 // Place stub status for early callers to find 1244 mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED); 1245 } 1246 } 1247 1248 private void addVolumeLocked(StorageVolume volume) { 1249 Slog.d(TAG, "addVolumeLocked() " + volume); 1250 mVolumes.add(volume); 1251 final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume); 1252 if (existing != null) { 1253 throw new IllegalStateException( 1254 "Volume at " + volume.getPath() + " already exists: " + existing); 1255 } 1256 } 1257 1258 private void removeVolumeLocked(StorageVolume volume) { 1259 Slog.d(TAG, "removeVolumeLocked() " + volume); 1260 mVolumes.remove(volume); 1261 mVolumesByPath.remove(volume.getPath()); 1262 mVolumeStates.remove(volume.getPath()); 1263 } 1264 1265 private StorageVolume getPrimaryPhysicalVolume() { 1266 synchronized (mVolumesLock) { 1267 for (StorageVolume volume : mVolumes) { 1268 if (volume.isPrimary() && !volume.isEmulated()) { 1269 return volume; 1270 } 1271 } 1272 } 1273 return null; 1274 } 1275 1276 /** 1277 * Constructs a new MountService instance 1278 * 1279 * @param context Binder context for this service 1280 */ 1281 public MountService(Context context) { 1282 mContext = context; 1283 1284 synchronized (mVolumesLock) { 1285 readStorageListLocked(); 1286 } 1287 1288 // XXX: This will go away soon in favor of IMountServiceObserver 1289 mPms = (PackageManagerService) ServiceManager.getService("package"); 1290 1291 mHandlerThread = new HandlerThread("MountService"); 1292 mHandlerThread.start(); 1293 mHandler = new MountServiceHandler(mHandlerThread.getLooper()); 1294 1295 // Watch for user changes 1296 final IntentFilter userFilter = new IntentFilter(); 1297 userFilter.addAction(Intent.ACTION_USER_ADDED); 1298 userFilter.addAction(Intent.ACTION_USER_REMOVED); 1299 mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler); 1300 1301 // Watch for USB changes on primary volume 1302 final StorageVolume primary = getPrimaryPhysicalVolume(); 1303 if (primary != null && primary.allowMassStorage()) { 1304 mContext.registerReceiver( 1305 mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler); 1306 } 1307 1308 // Add OBB Action Handler to MountService thread. 1309 mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper()); 1310 1311 /* 1312 * Create the connection to vold with a maximum queue of twice the 1313 * amount of containers we'd ever expect to have. This keeps an 1314 * "asec list" from blocking a thread repeatedly. 1315 */ 1316 mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25); 1317 1318 Thread thread = new Thread(mConnector, VOLD_TAG); 1319 thread.start(); 1320 1321 // Add ourself to the Watchdog monitors if enabled. 1322 if (WATCHDOG_ENABLE) { 1323 Watchdog.getInstance().addMonitor(this); 1324 } 1325 } 1326 1327 public void systemReady() { 1328 mSystemReady = true; 1329 mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); 1330 } 1331 1332 /** 1333 * Exposed API calls below here 1334 */ 1335 1336 public void registerListener(IMountServiceListener listener) { 1337 synchronized (mListeners) { 1338 MountServiceBinderListener bl = new MountServiceBinderListener(listener); 1339 try { 1340 listener.asBinder().linkToDeath(bl, 0); 1341 mListeners.add(bl); 1342 } catch (RemoteException rex) { 1343 Slog.e(TAG, "Failed to link to listener death"); 1344 } 1345 } 1346 } 1347 1348 public void unregisterListener(IMountServiceListener listener) { 1349 synchronized (mListeners) { 1350 for(MountServiceBinderListener bl : mListeners) { 1351 if (bl.mListener == listener) { 1352 mListeners.remove(mListeners.indexOf(bl)); 1353 listener.asBinder().unlinkToDeath(bl, 0); 1354 return; 1355 } 1356 } 1357 } 1358 } 1359 1360 public void shutdown(final IMountShutdownObserver observer) { 1361 validatePermission(android.Manifest.permission.SHUTDOWN); 1362 1363 Slog.i(TAG, "Shutting down"); 1364 synchronized (mVolumesLock) { 1365 for (String path : mVolumeStates.keySet()) { 1366 String state = mVolumeStates.get(path); 1367 1368 if (state.equals(Environment.MEDIA_SHARED)) { 1369 /* 1370 * If the media is currently shared, unshare it. 1371 * XXX: This is still dangerous!. We should not 1372 * be rebooting at *all* if UMS is enabled, since 1373 * the UMS host could have dirty FAT cache entries 1374 * yet to flush. 1375 */ 1376 setUsbMassStorageEnabled(false); 1377 } else if (state.equals(Environment.MEDIA_CHECKING)) { 1378 /* 1379 * If the media is being checked, then we need to wait for 1380 * it to complete before being able to proceed. 1381 */ 1382 // XXX: @hackbod - Should we disable the ANR timer here? 1383 int retries = 30; 1384 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) { 1385 try { 1386 Thread.sleep(1000); 1387 } catch (InterruptedException iex) { 1388 Slog.e(TAG, "Interrupted while waiting for media", iex); 1389 break; 1390 } 1391 state = Environment.getExternalStorageState(); 1392 } 1393 if (retries == 0) { 1394 Slog.e(TAG, "Timed out waiting for media to check"); 1395 } 1396 } 1397 1398 if (state.equals(Environment.MEDIA_MOUNTED)) { 1399 // Post a unmount message. 1400 ShutdownCallBack ucb = new ShutdownCallBack(path, observer); 1401 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 1402 } else if (observer != null) { 1403 /* 1404 * Observer is waiting for onShutDownComplete when we are done. 1405 * Since nothing will be done send notification directly so shutdown 1406 * sequence can continue. 1407 */ 1408 try { 1409 observer.onShutDownComplete(StorageResultCode.OperationSucceeded); 1410 } catch (RemoteException e) { 1411 Slog.w(TAG, "RemoteException when shutting down"); 1412 } 1413 } 1414 } 1415 } 1416 } 1417 1418 private boolean getUmsEnabling() { 1419 synchronized (mListeners) { 1420 return mUmsEnabling; 1421 } 1422 } 1423 1424 private void setUmsEnabling(boolean enable) { 1425 synchronized (mListeners) { 1426 mUmsEnabling = enable; 1427 } 1428 } 1429 1430 public boolean isUsbMassStorageConnected() { 1431 waitForReady(); 1432 1433 if (getUmsEnabling()) { 1434 return true; 1435 } 1436 synchronized (mListeners) { 1437 return mUmsAvailable; 1438 } 1439 } 1440 1441 public void setUsbMassStorageEnabled(boolean enable) { 1442 waitForReady(); 1443 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1444 1445 final StorageVolume primary = getPrimaryPhysicalVolume(); 1446 if (primary == null) return; 1447 1448 // TODO: Add support for multiple share methods 1449 1450 /* 1451 * If the volume is mounted and we're enabling then unmount it 1452 */ 1453 String path = primary.getPath(); 1454 String vs = getVolumeState(path); 1455 String method = "ums"; 1456 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { 1457 // Override for isUsbMassStorageEnabled() 1458 setUmsEnabling(enable); 1459 UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true); 1460 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb)); 1461 // Clear override 1462 setUmsEnabling(false); 1463 } 1464 /* 1465 * If we disabled UMS then mount the volume 1466 */ 1467 if (!enable) { 1468 doShareUnshareVolume(path, method, enable); 1469 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) { 1470 Slog.e(TAG, "Failed to remount " + path + 1471 " after disabling share method " + method); 1472 /* 1473 * Even though the mount failed, the unshare didn't so don't indicate an error. 1474 * The mountVolume() call will have set the storage state and sent the necessary 1475 * broadcasts. 1476 */ 1477 } 1478 } 1479 } 1480 1481 public boolean isUsbMassStorageEnabled() { 1482 waitForReady(); 1483 1484 final StorageVolume primary = getPrimaryPhysicalVolume(); 1485 if (primary != null) { 1486 return doGetVolumeShared(primary.getPath(), "ums"); 1487 } else { 1488 return false; 1489 } 1490 } 1491 1492 /** 1493 * @return state of the volume at the specified mount point 1494 */ 1495 public String getVolumeState(String mountPoint) { 1496 synchronized (mVolumesLock) { 1497 String state = mVolumeStates.get(mountPoint); 1498 if (state == null) { 1499 Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); 1500 if (SystemProperties.get("vold.encrypt_progress").length() != 0) { 1501 state = Environment.MEDIA_REMOVED; 1502 } else { 1503 throw new IllegalArgumentException(); 1504 } 1505 } 1506 1507 return state; 1508 } 1509 } 1510 1511 @Override 1512 public boolean isExternalStorageEmulated() { 1513 return mEmulatedTemplate != null; 1514 } 1515 1516 public int mountVolume(String path) { 1517 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1518 1519 waitForReady(); 1520 return doMountVolume(path); 1521 } 1522 1523 public void unmountVolume(String path, boolean force, boolean removeEncryption) { 1524 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1525 waitForReady(); 1526 1527 String volState = getVolumeState(path); 1528 if (DEBUG_UNMOUNT) { 1529 Slog.i(TAG, "Unmounting " + path 1530 + " force = " + force 1531 + " removeEncryption = " + removeEncryption); 1532 } 1533 if (Environment.MEDIA_UNMOUNTED.equals(volState) || 1534 Environment.MEDIA_REMOVED.equals(volState) || 1535 Environment.MEDIA_SHARED.equals(volState) || 1536 Environment.MEDIA_UNMOUNTABLE.equals(volState)) { 1537 // Media already unmounted or cannot be unmounted. 1538 // TODO return valid return code when adding observer call back. 1539 return; 1540 } 1541 UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption); 1542 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 1543 } 1544 1545 public int formatVolume(String path) { 1546 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); 1547 waitForReady(); 1548 1549 return doFormatVolume(path); 1550 } 1551 1552 public int[] getStorageUsers(String path) { 1553 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1554 waitForReady(); 1555 try { 1556 final String[] r = NativeDaemonEvent.filterMessageList( 1557 mConnector.executeForList("storage", "users", path), 1558 VoldResponseCode.StorageUsersListResult); 1559 1560 // FMT: <pid> <process name> 1561 int[] data = new int[r.length]; 1562 for (int i = 0; i < r.length; i++) { 1563 String[] tok = r[i].split(" "); 1564 try { 1565 data[i] = Integer.parseInt(tok[0]); 1566 } catch (NumberFormatException nfe) { 1567 Slog.e(TAG, String.format("Error parsing pid %s", tok[0])); 1568 return new int[0]; 1569 } 1570 } 1571 return data; 1572 } catch (NativeDaemonConnectorException e) { 1573 Slog.e(TAG, "Failed to retrieve storage users list", e); 1574 return new int[0]; 1575 } 1576 } 1577 1578 private void warnOnNotMounted() { 1579 final StorageVolume primary = getPrimaryPhysicalVolume(); 1580 if (primary != null 1581 && Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()))) { 1582 Slog.w(TAG, "getSecureContainerList() called when storage not mounted"); 1583 } 1584 } 1585 1586 public String[] getSecureContainerList() { 1587 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1588 waitForReady(); 1589 warnOnNotMounted(); 1590 1591 try { 1592 return NativeDaemonEvent.filterMessageList( 1593 mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult); 1594 } catch (NativeDaemonConnectorException e) { 1595 return new String[0]; 1596 } 1597 } 1598 1599 public int createSecureContainer(String id, int sizeMb, String fstype, String key, 1600 int ownerUid, boolean external) { 1601 validatePermission(android.Manifest.permission.ASEC_CREATE); 1602 waitForReady(); 1603 warnOnNotMounted(); 1604 1605 int rc = StorageResultCode.OperationSucceeded; 1606 try { 1607 mConnector.execute("asec", "create", id, sizeMb, fstype, key, ownerUid, 1608 external ? "1" : "0"); 1609 } catch (NativeDaemonConnectorException e) { 1610 rc = StorageResultCode.OperationFailedInternalError; 1611 } 1612 1613 if (rc == StorageResultCode.OperationSucceeded) { 1614 synchronized (mAsecMountSet) { 1615 mAsecMountSet.add(id); 1616 } 1617 } 1618 return rc; 1619 } 1620 1621 public int finalizeSecureContainer(String id) { 1622 validatePermission(android.Manifest.permission.ASEC_CREATE); 1623 warnOnNotMounted(); 1624 1625 int rc = StorageResultCode.OperationSucceeded; 1626 try { 1627 mConnector.execute("asec", "finalize", id); 1628 /* 1629 * Finalization does a remount, so no need 1630 * to update mAsecMountSet 1631 */ 1632 } catch (NativeDaemonConnectorException e) { 1633 rc = StorageResultCode.OperationFailedInternalError; 1634 } 1635 return rc; 1636 } 1637 1638 public int fixPermissionsSecureContainer(String id, int gid, String filename) { 1639 validatePermission(android.Manifest.permission.ASEC_CREATE); 1640 warnOnNotMounted(); 1641 1642 int rc = StorageResultCode.OperationSucceeded; 1643 try { 1644 mConnector.execute("asec", "fixperms", id, gid, filename); 1645 /* 1646 * Fix permissions does a remount, so no need to update 1647 * mAsecMountSet 1648 */ 1649 } catch (NativeDaemonConnectorException e) { 1650 rc = StorageResultCode.OperationFailedInternalError; 1651 } 1652 return rc; 1653 } 1654 1655 public int destroySecureContainer(String id, boolean force) { 1656 validatePermission(android.Manifest.permission.ASEC_DESTROY); 1657 waitForReady(); 1658 warnOnNotMounted(); 1659 1660 /* 1661 * Force a GC to make sure AssetManagers in other threads of the 1662 * system_server are cleaned up. We have to do this since AssetManager 1663 * instances are kept as a WeakReference and it's possible we have files 1664 * open on the external storage. 1665 */ 1666 Runtime.getRuntime().gc(); 1667 1668 int rc = StorageResultCode.OperationSucceeded; 1669 try { 1670 final Command cmd = new Command("asec", "destroy", id); 1671 if (force) { 1672 cmd.appendArg("force"); 1673 } 1674 mConnector.execute(cmd); 1675 } catch (NativeDaemonConnectorException e) { 1676 int code = e.getCode(); 1677 if (code == VoldResponseCode.OpFailedStorageBusy) { 1678 rc = StorageResultCode.OperationFailedStorageBusy; 1679 } else { 1680 rc = StorageResultCode.OperationFailedInternalError; 1681 } 1682 } 1683 1684 if (rc == StorageResultCode.OperationSucceeded) { 1685 synchronized (mAsecMountSet) { 1686 if (mAsecMountSet.contains(id)) { 1687 mAsecMountSet.remove(id); 1688 } 1689 } 1690 } 1691 1692 return rc; 1693 } 1694 1695 public int mountSecureContainer(String id, String key, int ownerUid) { 1696 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1697 waitForReady(); 1698 warnOnNotMounted(); 1699 1700 synchronized (mAsecMountSet) { 1701 if (mAsecMountSet.contains(id)) { 1702 return StorageResultCode.OperationFailedStorageMounted; 1703 } 1704 } 1705 1706 int rc = StorageResultCode.OperationSucceeded; 1707 try { 1708 mConnector.execute("asec", "mount", id, key, ownerUid); 1709 } catch (NativeDaemonConnectorException e) { 1710 int code = e.getCode(); 1711 if (code != VoldResponseCode.OpFailedStorageBusy) { 1712 rc = StorageResultCode.OperationFailedInternalError; 1713 } 1714 } 1715 1716 if (rc == StorageResultCode.OperationSucceeded) { 1717 synchronized (mAsecMountSet) { 1718 mAsecMountSet.add(id); 1719 } 1720 } 1721 return rc; 1722 } 1723 1724 public int unmountSecureContainer(String id, boolean force) { 1725 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1726 waitForReady(); 1727 warnOnNotMounted(); 1728 1729 synchronized (mAsecMountSet) { 1730 if (!mAsecMountSet.contains(id)) { 1731 return StorageResultCode.OperationFailedStorageNotMounted; 1732 } 1733 } 1734 1735 /* 1736 * Force a GC to make sure AssetManagers in other threads of the 1737 * system_server are cleaned up. We have to do this since AssetManager 1738 * instances are kept as a WeakReference and it's possible we have files 1739 * open on the external storage. 1740 */ 1741 Runtime.getRuntime().gc(); 1742 1743 int rc = StorageResultCode.OperationSucceeded; 1744 try { 1745 final Command cmd = new Command("asec", "unmount", id); 1746 if (force) { 1747 cmd.appendArg("force"); 1748 } 1749 mConnector.execute(cmd); 1750 } catch (NativeDaemonConnectorException e) { 1751 int code = e.getCode(); 1752 if (code == VoldResponseCode.OpFailedStorageBusy) { 1753 rc = StorageResultCode.OperationFailedStorageBusy; 1754 } else { 1755 rc = StorageResultCode.OperationFailedInternalError; 1756 } 1757 } 1758 1759 if (rc == StorageResultCode.OperationSucceeded) { 1760 synchronized (mAsecMountSet) { 1761 mAsecMountSet.remove(id); 1762 } 1763 } 1764 return rc; 1765 } 1766 1767 public boolean isSecureContainerMounted(String id) { 1768 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1769 waitForReady(); 1770 warnOnNotMounted(); 1771 1772 synchronized (mAsecMountSet) { 1773 return mAsecMountSet.contains(id); 1774 } 1775 } 1776 1777 public int renameSecureContainer(String oldId, String newId) { 1778 validatePermission(android.Manifest.permission.ASEC_RENAME); 1779 waitForReady(); 1780 warnOnNotMounted(); 1781 1782 synchronized (mAsecMountSet) { 1783 /* 1784 * Because a mounted container has active internal state which cannot be 1785 * changed while active, we must ensure both ids are not currently mounted. 1786 */ 1787 if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) { 1788 return StorageResultCode.OperationFailedStorageMounted; 1789 } 1790 } 1791 1792 int rc = StorageResultCode.OperationSucceeded; 1793 try { 1794 mConnector.execute("asec", "rename", oldId, newId); 1795 } catch (NativeDaemonConnectorException e) { 1796 rc = StorageResultCode.OperationFailedInternalError; 1797 } 1798 1799 return rc; 1800 } 1801 1802 public String getSecureContainerPath(String id) { 1803 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1804 waitForReady(); 1805 warnOnNotMounted(); 1806 1807 final NativeDaemonEvent event; 1808 try { 1809 event = mConnector.execute("asec", "path", id); 1810 event.checkCode(VoldResponseCode.AsecPathResult); 1811 return event.getMessage(); 1812 } catch (NativeDaemonConnectorException e) { 1813 int code = e.getCode(); 1814 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1815 Slog.i(TAG, String.format("Container '%s' not found", id)); 1816 return null; 1817 } else { 1818 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1819 } 1820 } 1821 } 1822 1823 public String getSecureContainerFilesystemPath(String id) { 1824 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1825 waitForReady(); 1826 warnOnNotMounted(); 1827 1828 final NativeDaemonEvent event; 1829 try { 1830 event = mConnector.execute("asec", "fspath", id); 1831 event.checkCode(VoldResponseCode.AsecPathResult); 1832 return event.getMessage(); 1833 } catch (NativeDaemonConnectorException e) { 1834 int code = e.getCode(); 1835 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1836 Slog.i(TAG, String.format("Container '%s' not found", id)); 1837 return null; 1838 } else { 1839 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1840 } 1841 } 1842 } 1843 1844 public void finishMediaUpdate() { 1845 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); 1846 } 1847 1848 private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) { 1849 if (callerUid == android.os.Process.SYSTEM_UID) { 1850 return true; 1851 } 1852 1853 if (packageName == null) { 1854 return false; 1855 } 1856 1857 final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid)); 1858 1859 if (DEBUG_OBB) { 1860 Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + 1861 packageUid + ", callerUid = " + callerUid); 1862 } 1863 1864 return callerUid == packageUid; 1865 } 1866 1867 public String getMountedObbPath(String rawPath) { 1868 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 1869 1870 waitForReady(); 1871 warnOnNotMounted(); 1872 1873 final ObbState state; 1874 synchronized (mObbPathToStateMap) { 1875 state = mObbPathToStateMap.get(rawPath); 1876 } 1877 if (state == null) { 1878 Slog.w(TAG, "Failed to find OBB mounted at " + rawPath); 1879 return null; 1880 } 1881 1882 final NativeDaemonEvent event; 1883 try { 1884 event = mConnector.execute("obb", "path", state.voldPath); 1885 event.checkCode(VoldResponseCode.AsecPathResult); 1886 return event.getMessage(); 1887 } catch (NativeDaemonConnectorException e) { 1888 int code = e.getCode(); 1889 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1890 return null; 1891 } else { 1892 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1893 } 1894 } 1895 } 1896 1897 @Override 1898 public boolean isObbMounted(String rawPath) { 1899 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 1900 synchronized (mObbMounts) { 1901 return mObbPathToStateMap.containsKey(rawPath); 1902 } 1903 } 1904 1905 @Override 1906 public void mountObb( 1907 String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) { 1908 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 1909 Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null"); 1910 Preconditions.checkNotNull(token, "token cannot be null"); 1911 1912 final int callingUid = Binder.getCallingUid(); 1913 final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce); 1914 final ObbAction action = new MountObbAction(obbState, key, callingUid); 1915 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 1916 1917 if (DEBUG_OBB) 1918 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 1919 } 1920 1921 @Override 1922 public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) { 1923 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 1924 1925 final ObbState existingState; 1926 synchronized (mObbPathToStateMap) { 1927 existingState = mObbPathToStateMap.get(rawPath); 1928 } 1929 1930 if (existingState != null) { 1931 // TODO: separate state object from request data 1932 final int callingUid = Binder.getCallingUid(); 1933 final ObbState newState = new ObbState( 1934 rawPath, existingState.canonicalPath, callingUid, token, nonce); 1935 final ObbAction action = new UnmountObbAction(newState, force); 1936 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 1937 1938 if (DEBUG_OBB) 1939 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 1940 } else { 1941 Slog.w(TAG, "Unknown OBB mount at " + rawPath); 1942 } 1943 } 1944 1945 @Override 1946 public int getEncryptionState() { 1947 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 1948 "no permission to access the crypt keeper"); 1949 1950 waitForReady(); 1951 1952 final NativeDaemonEvent event; 1953 try { 1954 event = mConnector.execute("cryptfs", "cryptocomplete"); 1955 return Integer.parseInt(event.getMessage()); 1956 } catch (NumberFormatException e) { 1957 // Bad result - unexpected. 1958 Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete"); 1959 return ENCRYPTION_STATE_ERROR_UNKNOWN; 1960 } catch (NativeDaemonConnectorException e) { 1961 // Something bad happened. 1962 Slog.w(TAG, "Error in communicating with cryptfs in validating"); 1963 return ENCRYPTION_STATE_ERROR_UNKNOWN; 1964 } 1965 } 1966 1967 @Override 1968 public int decryptStorage(String password) { 1969 if (TextUtils.isEmpty(password)) { 1970 throw new IllegalArgumentException("password cannot be empty"); 1971 } 1972 1973 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 1974 "no permission to access the crypt keeper"); 1975 1976 waitForReady(); 1977 1978 if (DEBUG_EVENTS) { 1979 Slog.i(TAG, "decrypting storage..."); 1980 } 1981 1982 final NativeDaemonEvent event; 1983 try { 1984 event = mConnector.execute("cryptfs", "checkpw", password); 1985 1986 final int code = Integer.parseInt(event.getMessage()); 1987 if (code == 0) { 1988 // Decrypt was successful. Post a delayed message before restarting in order 1989 // to let the UI to clear itself 1990 mHandler.postDelayed(new Runnable() { 1991 public void run() { 1992 try { 1993 mConnector.execute("cryptfs", "restart"); 1994 } catch (NativeDaemonConnectorException e) { 1995 Slog.e(TAG, "problem executing in background", e); 1996 } 1997 } 1998 }, 1000); // 1 second 1999 } 2000 2001 return code; 2002 } catch (NativeDaemonConnectorException e) { 2003 // Decryption failed 2004 return e.getCode(); 2005 } 2006 } 2007 2008 public int encryptStorage(String password) { 2009 if (TextUtils.isEmpty(password)) { 2010 throw new IllegalArgumentException("password cannot be empty"); 2011 } 2012 2013 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2014 "no permission to access the crypt keeper"); 2015 2016 waitForReady(); 2017 2018 if (DEBUG_EVENTS) { 2019 Slog.i(TAG, "encrypting storage..."); 2020 } 2021 2022 try { 2023 mConnector.execute("cryptfs", "enablecrypto", "inplace", password); 2024 } catch (NativeDaemonConnectorException e) { 2025 // Encryption failed 2026 return e.getCode(); 2027 } 2028 2029 return 0; 2030 } 2031 2032 public int changeEncryptionPassword(String password) { 2033 if (TextUtils.isEmpty(password)) { 2034 throw new IllegalArgumentException("password cannot be empty"); 2035 } 2036 2037 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2038 "no permission to access the crypt keeper"); 2039 2040 waitForReady(); 2041 2042 if (DEBUG_EVENTS) { 2043 Slog.i(TAG, "changing encryption password..."); 2044 } 2045 2046 final NativeDaemonEvent event; 2047 try { 2048 event = mConnector.execute("cryptfs", "changepw", password); 2049 return Integer.parseInt(event.getMessage()); 2050 } catch (NativeDaemonConnectorException e) { 2051 // Encryption failed 2052 return e.getCode(); 2053 } 2054 } 2055 2056 /** 2057 * Validate a user-supplied password string with cryptfs 2058 */ 2059 @Override 2060 public int verifyEncryptionPassword(String password) throws RemoteException { 2061 // Only the system process is permitted to validate passwords 2062 if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) { 2063 throw new SecurityException("no permission to access the crypt keeper"); 2064 } 2065 2066 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2067 "no permission to access the crypt keeper"); 2068 2069 if (TextUtils.isEmpty(password)) { 2070 throw new IllegalArgumentException("password cannot be empty"); 2071 } 2072 2073 waitForReady(); 2074 2075 if (DEBUG_EVENTS) { 2076 Slog.i(TAG, "validating encryption password..."); 2077 } 2078 2079 final NativeDaemonEvent event; 2080 try { 2081 event = mConnector.execute("cryptfs", "verifypw", password); 2082 Slog.i(TAG, "cryptfs verifypw => " + event.getMessage()); 2083 return Integer.parseInt(event.getMessage()); 2084 } catch (NativeDaemonConnectorException e) { 2085 // Encryption failed 2086 return e.getCode(); 2087 } 2088 } 2089 2090 @Override 2091 public StorageVolume[] getVolumeList() { 2092 final int callingUserId = UserHandle.getCallingUserId(); 2093 final boolean accessAll = (mContext.checkPermission( 2094 android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, 2095 Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED); 2096 2097 synchronized (mVolumesLock) { 2098 final ArrayList<StorageVolume> filtered = Lists.newArrayList(); 2099 for (StorageVolume volume : mVolumes) { 2100 final UserHandle owner = volume.getOwner(); 2101 final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId; 2102 if (accessAll || ownerMatch) { 2103 filtered.add(volume); 2104 } 2105 } 2106 return filtered.toArray(new StorageVolume[filtered.size()]); 2107 } 2108 } 2109 2110 private void addObbStateLocked(ObbState obbState) throws RemoteException { 2111 final IBinder binder = obbState.getBinder(); 2112 List<ObbState> obbStates = mObbMounts.get(binder); 2113 2114 if (obbStates == null) { 2115 obbStates = new ArrayList<ObbState>(); 2116 mObbMounts.put(binder, obbStates); 2117 } else { 2118 for (final ObbState o : obbStates) { 2119 if (o.rawPath.equals(obbState.rawPath)) { 2120 throw new IllegalStateException("Attempt to add ObbState twice. " 2121 + "This indicates an error in the MountService logic."); 2122 } 2123 } 2124 } 2125 2126 obbStates.add(obbState); 2127 try { 2128 obbState.link(); 2129 } catch (RemoteException e) { 2130 /* 2131 * The binder died before we could link it, so clean up our state 2132 * and return failure. 2133 */ 2134 obbStates.remove(obbState); 2135 if (obbStates.isEmpty()) { 2136 mObbMounts.remove(binder); 2137 } 2138 2139 // Rethrow the error so mountObb can get it 2140 throw e; 2141 } 2142 2143 mObbPathToStateMap.put(obbState.rawPath, obbState); 2144 } 2145 2146 private void removeObbStateLocked(ObbState obbState) { 2147 final IBinder binder = obbState.getBinder(); 2148 final List<ObbState> obbStates = mObbMounts.get(binder); 2149 if (obbStates != null) { 2150 if (obbStates.remove(obbState)) { 2151 obbState.unlink(); 2152 } 2153 if (obbStates.isEmpty()) { 2154 mObbMounts.remove(binder); 2155 } 2156 } 2157 2158 mObbPathToStateMap.remove(obbState.rawPath); 2159 } 2160 2161 private class ObbActionHandler extends Handler { 2162 private boolean mBound = false; 2163 private final List<ObbAction> mActions = new LinkedList<ObbAction>(); 2164 2165 ObbActionHandler(Looper l) { 2166 super(l); 2167 } 2168 2169 @Override 2170 public void handleMessage(Message msg) { 2171 switch (msg.what) { 2172 case OBB_RUN_ACTION: { 2173 final ObbAction action = (ObbAction) msg.obj; 2174 2175 if (DEBUG_OBB) 2176 Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString()); 2177 2178 // If a bind was already initiated we don't really 2179 // need to do anything. The pending install 2180 // will be processed later on. 2181 if (!mBound) { 2182 // If this is the only one pending we might 2183 // have to bind to the service again. 2184 if (!connectToService()) { 2185 Slog.e(TAG, "Failed to bind to media container service"); 2186 action.handleError(); 2187 return; 2188 } 2189 } 2190 2191 mActions.add(action); 2192 break; 2193 } 2194 case OBB_MCS_BOUND: { 2195 if (DEBUG_OBB) 2196 Slog.i(TAG, "OBB_MCS_BOUND"); 2197 if (msg.obj != null) { 2198 mContainerService = (IMediaContainerService) msg.obj; 2199 } 2200 if (mContainerService == null) { 2201 // Something seriously wrong. Bail out 2202 Slog.e(TAG, "Cannot bind to media container service"); 2203 for (ObbAction action : mActions) { 2204 // Indicate service bind error 2205 action.handleError(); 2206 } 2207 mActions.clear(); 2208 } else if (mActions.size() > 0) { 2209 final ObbAction action = mActions.get(0); 2210 if (action != null) { 2211 action.execute(this); 2212 } 2213 } else { 2214 // Should never happen ideally. 2215 Slog.w(TAG, "Empty queue"); 2216 } 2217 break; 2218 } 2219 case OBB_MCS_RECONNECT: { 2220 if (DEBUG_OBB) 2221 Slog.i(TAG, "OBB_MCS_RECONNECT"); 2222 if (mActions.size() > 0) { 2223 if (mBound) { 2224 disconnectService(); 2225 } 2226 if (!connectToService()) { 2227 Slog.e(TAG, "Failed to bind to media container service"); 2228 for (ObbAction action : mActions) { 2229 // Indicate service bind error 2230 action.handleError(); 2231 } 2232 mActions.clear(); 2233 } 2234 } 2235 break; 2236 } 2237 case OBB_MCS_UNBIND: { 2238 if (DEBUG_OBB) 2239 Slog.i(TAG, "OBB_MCS_UNBIND"); 2240 2241 // Delete pending install 2242 if (mActions.size() > 0) { 2243 mActions.remove(0); 2244 } 2245 if (mActions.size() == 0) { 2246 if (mBound) { 2247 disconnectService(); 2248 } 2249 } else { 2250 // There are more pending requests in queue. 2251 // Just post MCS_BOUND message to trigger processing 2252 // of next pending install. 2253 mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND); 2254 } 2255 break; 2256 } 2257 case OBB_FLUSH_MOUNT_STATE: { 2258 final String path = (String) msg.obj; 2259 2260 if (DEBUG_OBB) 2261 Slog.i(TAG, "Flushing all OBB state for path " + path); 2262 2263 synchronized (mObbMounts) { 2264 final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>(); 2265 2266 final Iterator<ObbState> i = mObbPathToStateMap.values().iterator(); 2267 while (i.hasNext()) { 2268 final ObbState state = i.next(); 2269 2270 /* 2271 * If this entry's source file is in the volume path 2272 * that got unmounted, remove it because it's no 2273 * longer valid. 2274 */ 2275 if (state.canonicalPath.startsWith(path)) { 2276 obbStatesToRemove.add(state); 2277 } 2278 } 2279 2280 for (final ObbState obbState : obbStatesToRemove) { 2281 if (DEBUG_OBB) 2282 Slog.i(TAG, "Removing state for " + obbState.rawPath); 2283 2284 removeObbStateLocked(obbState); 2285 2286 try { 2287 obbState.token.onObbResult(obbState.rawPath, obbState.nonce, 2288 OnObbStateChangeListener.UNMOUNTED); 2289 } catch (RemoteException e) { 2290 Slog.i(TAG, "Couldn't send unmount notification for OBB: " 2291 + obbState.rawPath); 2292 } 2293 } 2294 } 2295 break; 2296 } 2297 } 2298 } 2299 2300 private boolean connectToService() { 2301 if (DEBUG_OBB) 2302 Slog.i(TAG, "Trying to bind to DefaultContainerService"); 2303 2304 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); 2305 if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) { 2306 mBound = true; 2307 return true; 2308 } 2309 return false; 2310 } 2311 2312 private void disconnectService() { 2313 mContainerService = null; 2314 mBound = false; 2315 mContext.unbindService(mDefContainerConn); 2316 } 2317 } 2318 2319 abstract class ObbAction { 2320 private static final int MAX_RETRIES = 3; 2321 private int mRetries; 2322 2323 ObbState mObbState; 2324 2325 ObbAction(ObbState obbState) { 2326 mObbState = obbState; 2327 } 2328 2329 public void execute(ObbActionHandler handler) { 2330 try { 2331 if (DEBUG_OBB) 2332 Slog.i(TAG, "Starting to execute action: " + toString()); 2333 mRetries++; 2334 if (mRetries > MAX_RETRIES) { 2335 Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); 2336 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2337 handleError(); 2338 return; 2339 } else { 2340 handleExecute(); 2341 if (DEBUG_OBB) 2342 Slog.i(TAG, "Posting install MCS_UNBIND"); 2343 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2344 } 2345 } catch (RemoteException e) { 2346 if (DEBUG_OBB) 2347 Slog.i(TAG, "Posting install MCS_RECONNECT"); 2348 mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT); 2349 } catch (Exception e) { 2350 if (DEBUG_OBB) 2351 Slog.d(TAG, "Error handling OBB action", e); 2352 handleError(); 2353 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2354 } 2355 } 2356 2357 abstract void handleExecute() throws RemoteException, IOException; 2358 abstract void handleError(); 2359 2360 protected ObbInfo getObbInfo() throws IOException { 2361 ObbInfo obbInfo; 2362 try { 2363 obbInfo = mContainerService.getObbInfo(mObbState.ownerPath); 2364 } catch (RemoteException e) { 2365 Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for " 2366 + mObbState.ownerPath); 2367 obbInfo = null; 2368 } 2369 if (obbInfo == null) { 2370 throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath); 2371 } 2372 return obbInfo; 2373 } 2374 2375 protected void sendNewStatusOrIgnore(int status) { 2376 if (mObbState == null || mObbState.token == null) { 2377 return; 2378 } 2379 2380 try { 2381 mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status); 2382 } catch (RemoteException e) { 2383 Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); 2384 } 2385 } 2386 } 2387 2388 class MountObbAction extends ObbAction { 2389 private final String mKey; 2390 private final int mCallingUid; 2391 2392 MountObbAction(ObbState obbState, String key, int callingUid) { 2393 super(obbState); 2394 mKey = key; 2395 mCallingUid = callingUid; 2396 } 2397 2398 @Override 2399 public void handleExecute() throws IOException, RemoteException { 2400 waitForReady(); 2401 warnOnNotMounted(); 2402 2403 final ObbInfo obbInfo = getObbInfo(); 2404 2405 if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) { 2406 Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename 2407 + " which is owned by " + obbInfo.packageName); 2408 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 2409 return; 2410 } 2411 2412 final boolean isMounted; 2413 synchronized (mObbMounts) { 2414 isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath); 2415 } 2416 if (isMounted) { 2417 Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename); 2418 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED); 2419 return; 2420 } 2421 2422 final String hashedKey; 2423 if (mKey == null) { 2424 hashedKey = "none"; 2425 } else { 2426 try { 2427 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 2428 2429 KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt, 2430 PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE); 2431 SecretKey key = factory.generateSecret(ks); 2432 BigInteger bi = new BigInteger(key.getEncoded()); 2433 hashedKey = bi.toString(16); 2434 } catch (NoSuchAlgorithmException e) { 2435 Slog.e(TAG, "Could not load PBKDF2 algorithm", e); 2436 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2437 return; 2438 } catch (InvalidKeySpecException e) { 2439 Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e); 2440 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2441 return; 2442 } 2443 } 2444 2445 int rc = StorageResultCode.OperationSucceeded; 2446 try { 2447 mConnector.execute( 2448 "obb", "mount", mObbState.voldPath, hashedKey, mObbState.ownerGid); 2449 } catch (NativeDaemonConnectorException e) { 2450 int code = e.getCode(); 2451 if (code != VoldResponseCode.OpFailedStorageBusy) { 2452 rc = StorageResultCode.OperationFailedInternalError; 2453 } 2454 } 2455 2456 if (rc == StorageResultCode.OperationSucceeded) { 2457 if (DEBUG_OBB) 2458 Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath); 2459 2460 synchronized (mObbMounts) { 2461 addObbStateLocked(mObbState); 2462 } 2463 2464 sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED); 2465 } else { 2466 Slog.e(TAG, "Couldn't mount OBB file: " + rc); 2467 2468 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT); 2469 } 2470 } 2471 2472 @Override 2473 public void handleError() { 2474 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2475 } 2476 2477 @Override 2478 public String toString() { 2479 StringBuilder sb = new StringBuilder(); 2480 sb.append("MountObbAction{"); 2481 sb.append(mObbState); 2482 sb.append('}'); 2483 return sb.toString(); 2484 } 2485 } 2486 2487 class UnmountObbAction extends ObbAction { 2488 private final boolean mForceUnmount; 2489 2490 UnmountObbAction(ObbState obbState, boolean force) { 2491 super(obbState); 2492 mForceUnmount = force; 2493 } 2494 2495 @Override 2496 public void handleExecute() throws IOException { 2497 waitForReady(); 2498 warnOnNotMounted(); 2499 2500 final ObbInfo obbInfo = getObbInfo(); 2501 2502 final ObbState existingState; 2503 synchronized (mObbMounts) { 2504 existingState = mObbPathToStateMap.get(mObbState.rawPath); 2505 } 2506 2507 if (existingState == null) { 2508 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED); 2509 return; 2510 } 2511 2512 if (existingState.ownerGid != mObbState.ownerGid) { 2513 Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath 2514 + " (owned by GID " + existingState.ownerGid + ")"); 2515 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 2516 return; 2517 } 2518 2519 int rc = StorageResultCode.OperationSucceeded; 2520 try { 2521 final Command cmd = new Command("obb", "unmount", mObbState.voldPath); 2522 if (mForceUnmount) { 2523 cmd.appendArg("force"); 2524 } 2525 mConnector.execute(cmd); 2526 } catch (NativeDaemonConnectorException e) { 2527 int code = e.getCode(); 2528 if (code == VoldResponseCode.OpFailedStorageBusy) { 2529 rc = StorageResultCode.OperationFailedStorageBusy; 2530 } else if (code == VoldResponseCode.OpFailedStorageNotFound) { 2531 // If it's not mounted then we've already won. 2532 rc = StorageResultCode.OperationSucceeded; 2533 } else { 2534 rc = StorageResultCode.OperationFailedInternalError; 2535 } 2536 } 2537 2538 if (rc == StorageResultCode.OperationSucceeded) { 2539 synchronized (mObbMounts) { 2540 removeObbStateLocked(existingState); 2541 } 2542 2543 sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED); 2544 } else { 2545 Slog.w(TAG, "Could not unmount OBB: " + existingState); 2546 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT); 2547 } 2548 } 2549 2550 @Override 2551 public void handleError() { 2552 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2553 } 2554 2555 @Override 2556 public String toString() { 2557 StringBuilder sb = new StringBuilder(); 2558 sb.append("UnmountObbAction{"); 2559 sb.append(mObbState); 2560 sb.append(",force="); 2561 sb.append(mForceUnmount); 2562 sb.append('}'); 2563 return sb.toString(); 2564 } 2565 } 2566 2567 // @VisibleForTesting 2568 public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) { 2569 // TODO: allow caller to provide Environment for full testing 2570 2571 // Only adjust paths when storage is emulated 2572 if (!Environment.isExternalStorageEmulated()) { 2573 return canonicalPath; 2574 } 2575 2576 String path = canonicalPath.toString(); 2577 2578 // First trim off any external storage prefix 2579 final UserEnvironment userEnv = new UserEnvironment(userId); 2580 2581 // /storage/emulated/0 2582 final String externalPath = userEnv.getExternalStorageDirectory().toString(); 2583 // /storage/emulated_legacy 2584 final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory() 2585 .toString(); 2586 2587 if (path.startsWith(externalPath)) { 2588 path = path.substring(externalPath.length() + 1); 2589 } else if (path.startsWith(legacyExternalPath)) { 2590 path = path.substring(legacyExternalPath.length() + 1); 2591 } else { 2592 return canonicalPath; 2593 } 2594 2595 // Handle special OBB paths on emulated storage 2596 final String obbPath = "Android/obb"; 2597 if (path.startsWith(obbPath)) { 2598 path = path.substring(obbPath.length() + 1); 2599 2600 if (forVold) { 2601 return new File(Environment.getEmulatedStorageObbSource(), path).toString(); 2602 } else { 2603 final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER); 2604 return new File(ownerEnv.getExternalStorageObbDirectory(), path).toString(); 2605 } 2606 } 2607 2608 // Handle normal external storage paths 2609 if (forVold) { 2610 return new File(Environment.getEmulatedStorageSource(userId), path).toString(); 2611 } else { 2612 return new File(userEnv.getExternalStorageDirectory(), path).toString(); 2613 } 2614 } 2615 2616 @Override 2617 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2618 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { 2619 pw.println("Permission Denial: can't dump ActivityManager from from pid=" 2620 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 2621 + " without permission " + android.Manifest.permission.DUMP); 2622 return; 2623 } 2624 2625 synchronized (mObbMounts) { 2626 pw.println(" mObbMounts:"); 2627 2628 final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator(); 2629 while (binders.hasNext()) { 2630 Entry<IBinder, List<ObbState>> e = binders.next(); 2631 pw.print(" Key="); pw.println(e.getKey().toString()); 2632 final List<ObbState> obbStates = e.getValue(); 2633 for (final ObbState obbState : obbStates) { 2634 pw.print(" "); pw.println(obbState.toString()); 2635 } 2636 } 2637 2638 pw.println(""); 2639 pw.println(" mObbPathToStateMap:"); 2640 final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator(); 2641 while (maps.hasNext()) { 2642 final Entry<String, ObbState> e = maps.next(); 2643 pw.print(" "); pw.print(e.getKey()); 2644 pw.print(" -> "); pw.println(e.getValue().toString()); 2645 } 2646 } 2647 2648 pw.println(""); 2649 2650 synchronized (mVolumesLock) { 2651 pw.println(" mVolumes:"); 2652 2653 final int N = mVolumes.size(); 2654 for (int i = 0; i < N; i++) { 2655 final StorageVolume v = mVolumes.get(i); 2656 pw.print(" "); 2657 pw.println(v.toString()); 2658 } 2659 } 2660 2661 pw.println(); 2662 pw.println(" mConnection:"); 2663 mConnector.dump(fd, pw, args); 2664 } 2665 2666 /** {@inheritDoc} */ 2667 public void monitor() { 2668 if (mConnector != null) { 2669 mConnector.monitor(); 2670 } 2671 } 2672} 2673