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 17 package com.android.server; 18 19 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 21 import android.Manifest; 22 import android.content.BroadcastReceiver; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.ServiceConnection; 28 import android.content.pm.PackageManager; 29 import android.content.pm.UserInfo; 30 import android.content.res.ObbInfo; 31 import android.content.res.Resources; 32 import android.content.res.TypedArray; 33 import android.content.res.XmlResourceParser; 34 import android.hardware.usb.UsbManager; 35 import android.net.Uri; 36 import android.os.Binder; 37 import android.os.Environment; 38 import android.os.Environment.UserEnvironment; 39 import android.os.Handler; 40 import android.os.HandlerThread; 41 import android.os.IBinder; 42 import android.os.Looper; 43 import android.os.Message; 44 import android.os.RemoteException; 45 import android.os.ServiceManager; 46 import android.os.SystemProperties; 47 import android.os.UserHandle; 48 import android.os.storage.IMountService; 49 import android.os.storage.IMountServiceListener; 50 import android.os.storage.IMountShutdownObserver; 51 import android.os.storage.IObbActionListener; 52 import android.os.storage.OnObbStateChangeListener; 53 import android.os.storage.StorageResultCode; 54 import android.os.storage.StorageVolume; 55 import android.text.TextUtils; 56 import android.util.AttributeSet; 57 import android.util.Slog; 58 import android.util.Xml; 59 60 import com.android.internal.app.IMediaContainerService; 61 import com.android.internal.util.Preconditions; 62 import com.android.internal.util.XmlUtils; 63 import com.android.server.NativeDaemonConnector.Command; 64 import com.android.server.am.ActivityManagerService; 65 import com.android.server.pm.PackageManagerService; 66 import com.android.server.pm.UserManagerService; 67 import com.google.android.collect.Lists; 68 import com.google.android.collect.Maps; 69 70 import org.xmlpull.v1.XmlPullParserException; 71 72 import java.io.File; 73 import java.io.FileDescriptor; 74 import java.io.IOException; 75 import java.io.PrintWriter; 76 import java.math.BigInteger; 77 import java.security.NoSuchAlgorithmException; 78 import java.security.spec.InvalidKeySpecException; 79 import java.security.spec.KeySpec; 80 import java.util.ArrayList; 81 import java.util.HashMap; 82 import java.util.HashSet; 83 import java.util.Iterator; 84 import java.util.LinkedList; 85 import java.util.List; 86 import java.util.Map; 87 import java.util.Map.Entry; 88 import java.util.concurrent.CountDownLatch; 89 import java.util.concurrent.TimeUnit; 90 91 import javax.crypto.SecretKey; 92 import javax.crypto.SecretKeyFactory; 93 import 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 */ 101 class 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 boolean mounted = false; 1582 try { 1583 mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath())); 1584 } catch (IllegalStateException e) { 1585 } 1586 1587 if (!mounted) { 1588 Slog.w(TAG, "getSecureContainerList() called when storage not mounted"); 1589 } 1590 } 1591 } 1592 1593 public String[] getSecureContainerList() { 1594 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1595 waitForReady(); 1596 warnOnNotMounted(); 1597 1598 try { 1599 return NativeDaemonEvent.filterMessageList( 1600 mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult); 1601 } catch (NativeDaemonConnectorException e) { 1602 return new String[0]; 1603 } 1604 } 1605 1606 public int createSecureContainer(String id, int sizeMb, String fstype, String key, 1607 int ownerUid, boolean external) { 1608 validatePermission(android.Manifest.permission.ASEC_CREATE); 1609 waitForReady(); 1610 warnOnNotMounted(); 1611 1612 int rc = StorageResultCode.OperationSucceeded; 1613 try { 1614 mConnector.execute("asec", "create", id, sizeMb, fstype, key, ownerUid, 1615 external ? "1" : "0"); 1616 } catch (NativeDaemonConnectorException e) { 1617 rc = StorageResultCode.OperationFailedInternalError; 1618 } 1619 1620 if (rc == StorageResultCode.OperationSucceeded) { 1621 synchronized (mAsecMountSet) { 1622 mAsecMountSet.add(id); 1623 } 1624 } 1625 return rc; 1626 } 1627 1628 public int finalizeSecureContainer(String id) { 1629 validatePermission(android.Manifest.permission.ASEC_CREATE); 1630 warnOnNotMounted(); 1631 1632 int rc = StorageResultCode.OperationSucceeded; 1633 try { 1634 mConnector.execute("asec", "finalize", id); 1635 /* 1636 * Finalization does a remount, so no need 1637 * to update mAsecMountSet 1638 */ 1639 } catch (NativeDaemonConnectorException e) { 1640 rc = StorageResultCode.OperationFailedInternalError; 1641 } 1642 return rc; 1643 } 1644 1645 public int fixPermissionsSecureContainer(String id, int gid, String filename) { 1646 validatePermission(android.Manifest.permission.ASEC_CREATE); 1647 warnOnNotMounted(); 1648 1649 int rc = StorageResultCode.OperationSucceeded; 1650 try { 1651 mConnector.execute("asec", "fixperms", id, gid, filename); 1652 /* 1653 * Fix permissions does a remount, so no need to update 1654 * mAsecMountSet 1655 */ 1656 } catch (NativeDaemonConnectorException e) { 1657 rc = StorageResultCode.OperationFailedInternalError; 1658 } 1659 return rc; 1660 } 1661 1662 public int destroySecureContainer(String id, boolean force) { 1663 validatePermission(android.Manifest.permission.ASEC_DESTROY); 1664 waitForReady(); 1665 warnOnNotMounted(); 1666 1667 /* 1668 * Force a GC to make sure AssetManagers in other threads of the 1669 * system_server are cleaned up. We have to do this since AssetManager 1670 * instances are kept as a WeakReference and it's possible we have files 1671 * open on the external storage. 1672 */ 1673 Runtime.getRuntime().gc(); 1674 1675 int rc = StorageResultCode.OperationSucceeded; 1676 try { 1677 final Command cmd = new Command("asec", "destroy", id); 1678 if (force) { 1679 cmd.appendArg("force"); 1680 } 1681 mConnector.execute(cmd); 1682 } catch (NativeDaemonConnectorException e) { 1683 int code = e.getCode(); 1684 if (code == VoldResponseCode.OpFailedStorageBusy) { 1685 rc = StorageResultCode.OperationFailedStorageBusy; 1686 } else { 1687 rc = StorageResultCode.OperationFailedInternalError; 1688 } 1689 } 1690 1691 if (rc == StorageResultCode.OperationSucceeded) { 1692 synchronized (mAsecMountSet) { 1693 if (mAsecMountSet.contains(id)) { 1694 mAsecMountSet.remove(id); 1695 } 1696 } 1697 } 1698 1699 return rc; 1700 } 1701 1702 public int mountSecureContainer(String id, String key, int ownerUid) { 1703 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1704 waitForReady(); 1705 warnOnNotMounted(); 1706 1707 synchronized (mAsecMountSet) { 1708 if (mAsecMountSet.contains(id)) { 1709 return StorageResultCode.OperationFailedStorageMounted; 1710 } 1711 } 1712 1713 int rc = StorageResultCode.OperationSucceeded; 1714 try { 1715 mConnector.execute("asec", "mount", id, key, ownerUid); 1716 } catch (NativeDaemonConnectorException e) { 1717 int code = e.getCode(); 1718 if (code != VoldResponseCode.OpFailedStorageBusy) { 1719 rc = StorageResultCode.OperationFailedInternalError; 1720 } 1721 } 1722 1723 if (rc == StorageResultCode.OperationSucceeded) { 1724 synchronized (mAsecMountSet) { 1725 mAsecMountSet.add(id); 1726 } 1727 } 1728 return rc; 1729 } 1730 1731 public int unmountSecureContainer(String id, boolean force) { 1732 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1733 waitForReady(); 1734 warnOnNotMounted(); 1735 1736 synchronized (mAsecMountSet) { 1737 if (!mAsecMountSet.contains(id)) { 1738 return StorageResultCode.OperationFailedStorageNotMounted; 1739 } 1740 } 1741 1742 /* 1743 * Force a GC to make sure AssetManagers in other threads of the 1744 * system_server are cleaned up. We have to do this since AssetManager 1745 * instances are kept as a WeakReference and it's possible we have files 1746 * open on the external storage. 1747 */ 1748 Runtime.getRuntime().gc(); 1749 1750 int rc = StorageResultCode.OperationSucceeded; 1751 try { 1752 final Command cmd = new Command("asec", "unmount", id); 1753 if (force) { 1754 cmd.appendArg("force"); 1755 } 1756 mConnector.execute(cmd); 1757 } catch (NativeDaemonConnectorException e) { 1758 int code = e.getCode(); 1759 if (code == VoldResponseCode.OpFailedStorageBusy) { 1760 rc = StorageResultCode.OperationFailedStorageBusy; 1761 } else { 1762 rc = StorageResultCode.OperationFailedInternalError; 1763 } 1764 } 1765 1766 if (rc == StorageResultCode.OperationSucceeded) { 1767 synchronized (mAsecMountSet) { 1768 mAsecMountSet.remove(id); 1769 } 1770 } 1771 return rc; 1772 } 1773 1774 public boolean isSecureContainerMounted(String id) { 1775 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1776 waitForReady(); 1777 warnOnNotMounted(); 1778 1779 synchronized (mAsecMountSet) { 1780 return mAsecMountSet.contains(id); 1781 } 1782 } 1783 1784 public int renameSecureContainer(String oldId, String newId) { 1785 validatePermission(android.Manifest.permission.ASEC_RENAME); 1786 waitForReady(); 1787 warnOnNotMounted(); 1788 1789 synchronized (mAsecMountSet) { 1790 /* 1791 * Because a mounted container has active internal state which cannot be 1792 * changed while active, we must ensure both ids are not currently mounted. 1793 */ 1794 if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) { 1795 return StorageResultCode.OperationFailedStorageMounted; 1796 } 1797 } 1798 1799 int rc = StorageResultCode.OperationSucceeded; 1800 try { 1801 mConnector.execute("asec", "rename", oldId, newId); 1802 } catch (NativeDaemonConnectorException e) { 1803 rc = StorageResultCode.OperationFailedInternalError; 1804 } 1805 1806 return rc; 1807 } 1808 1809 public String getSecureContainerPath(String id) { 1810 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1811 waitForReady(); 1812 warnOnNotMounted(); 1813 1814 final NativeDaemonEvent event; 1815 try { 1816 event = mConnector.execute("asec", "path", id); 1817 event.checkCode(VoldResponseCode.AsecPathResult); 1818 return event.getMessage(); 1819 } catch (NativeDaemonConnectorException e) { 1820 int code = e.getCode(); 1821 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1822 Slog.i(TAG, String.format("Container '%s' not found", id)); 1823 return null; 1824 } else { 1825 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1826 } 1827 } 1828 } 1829 1830 public String getSecureContainerFilesystemPath(String id) { 1831 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1832 waitForReady(); 1833 warnOnNotMounted(); 1834 1835 final NativeDaemonEvent event; 1836 try { 1837 event = mConnector.execute("asec", "fspath", id); 1838 event.checkCode(VoldResponseCode.AsecPathResult); 1839 return event.getMessage(); 1840 } catch (NativeDaemonConnectorException e) { 1841 int code = e.getCode(); 1842 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1843 Slog.i(TAG, String.format("Container '%s' not found", id)); 1844 return null; 1845 } else { 1846 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1847 } 1848 } 1849 } 1850 1851 public void finishMediaUpdate() { 1852 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); 1853 } 1854 1855 private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) { 1856 if (callerUid == android.os.Process.SYSTEM_UID) { 1857 return true; 1858 } 1859 1860 if (packageName == null) { 1861 return false; 1862 } 1863 1864 final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid)); 1865 1866 if (DEBUG_OBB) { 1867 Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + 1868 packageUid + ", callerUid = " + callerUid); 1869 } 1870 1871 return callerUid == packageUid; 1872 } 1873 1874 public String getMountedObbPath(String rawPath) { 1875 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 1876 1877 waitForReady(); 1878 warnOnNotMounted(); 1879 1880 final ObbState state; 1881 synchronized (mObbPathToStateMap) { 1882 state = mObbPathToStateMap.get(rawPath); 1883 } 1884 if (state == null) { 1885 Slog.w(TAG, "Failed to find OBB mounted at " + rawPath); 1886 return null; 1887 } 1888 1889 final NativeDaemonEvent event; 1890 try { 1891 event = mConnector.execute("obb", "path", state.voldPath); 1892 event.checkCode(VoldResponseCode.AsecPathResult); 1893 return event.getMessage(); 1894 } catch (NativeDaemonConnectorException e) { 1895 int code = e.getCode(); 1896 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1897 return null; 1898 } else { 1899 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1900 } 1901 } 1902 } 1903 1904 @Override 1905 public boolean isObbMounted(String rawPath) { 1906 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 1907 synchronized (mObbMounts) { 1908 return mObbPathToStateMap.containsKey(rawPath); 1909 } 1910 } 1911 1912 @Override 1913 public void mountObb( 1914 String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) { 1915 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 1916 Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null"); 1917 Preconditions.checkNotNull(token, "token cannot be null"); 1918 1919 final int callingUid = Binder.getCallingUid(); 1920 final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce); 1921 final ObbAction action = new MountObbAction(obbState, key, callingUid); 1922 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 1923 1924 if (DEBUG_OBB) 1925 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 1926 } 1927 1928 @Override 1929 public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) { 1930 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 1931 1932 final ObbState existingState; 1933 synchronized (mObbPathToStateMap) { 1934 existingState = mObbPathToStateMap.get(rawPath); 1935 } 1936 1937 if (existingState != null) { 1938 // TODO: separate state object from request data 1939 final int callingUid = Binder.getCallingUid(); 1940 final ObbState newState = new ObbState( 1941 rawPath, existingState.canonicalPath, callingUid, token, nonce); 1942 final ObbAction action = new UnmountObbAction(newState, force); 1943 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 1944 1945 if (DEBUG_OBB) 1946 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 1947 } else { 1948 Slog.w(TAG, "Unknown OBB mount at " + rawPath); 1949 } 1950 } 1951 1952 @Override 1953 public int getEncryptionState() { 1954 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 1955 "no permission to access the crypt keeper"); 1956 1957 waitForReady(); 1958 1959 final NativeDaemonEvent event; 1960 try { 1961 event = mConnector.execute("cryptfs", "cryptocomplete"); 1962 return Integer.parseInt(event.getMessage()); 1963 } catch (NumberFormatException e) { 1964 // Bad result - unexpected. 1965 Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete"); 1966 return ENCRYPTION_STATE_ERROR_UNKNOWN; 1967 } catch (NativeDaemonConnectorException e) { 1968 // Something bad happened. 1969 Slog.w(TAG, "Error in communicating with cryptfs in validating"); 1970 return ENCRYPTION_STATE_ERROR_UNKNOWN; 1971 } 1972 } 1973 1974 @Override 1975 public int decryptStorage(String password) { 1976 if (TextUtils.isEmpty(password)) { 1977 throw new IllegalArgumentException("password cannot be empty"); 1978 } 1979 1980 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 1981 "no permission to access the crypt keeper"); 1982 1983 waitForReady(); 1984 1985 if (DEBUG_EVENTS) { 1986 Slog.i(TAG, "decrypting storage..."); 1987 } 1988 1989 final NativeDaemonEvent event; 1990 try { 1991 event = mConnector.execute("cryptfs", "checkpw", password); 1992 1993 final int code = Integer.parseInt(event.getMessage()); 1994 if (code == 0) { 1995 // Decrypt was successful. Post a delayed message before restarting in order 1996 // to let the UI to clear itself 1997 mHandler.postDelayed(new Runnable() { 1998 public void run() { 1999 try { 2000 mConnector.execute("cryptfs", "restart"); 2001 } catch (NativeDaemonConnectorException e) { 2002 Slog.e(TAG, "problem executing in background", e); 2003 } 2004 } 2005 }, 1000); // 1 second 2006 } 2007 2008 return code; 2009 } catch (NativeDaemonConnectorException e) { 2010 // Decryption failed 2011 return e.getCode(); 2012 } 2013 } 2014 2015 public int encryptStorage(String password) { 2016 if (TextUtils.isEmpty(password)) { 2017 throw new IllegalArgumentException("password cannot be empty"); 2018 } 2019 2020 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2021 "no permission to access the crypt keeper"); 2022 2023 waitForReady(); 2024 2025 if (DEBUG_EVENTS) { 2026 Slog.i(TAG, "encrypting storage..."); 2027 } 2028 2029 try { 2030 mConnector.execute("cryptfs", "enablecrypto", "inplace", password); 2031 } catch (NativeDaemonConnectorException e) { 2032 // Encryption failed 2033 return e.getCode(); 2034 } 2035 2036 return 0; 2037 } 2038 2039 public int changeEncryptionPassword(String password) { 2040 if (TextUtils.isEmpty(password)) { 2041 throw new IllegalArgumentException("password cannot be empty"); 2042 } 2043 2044 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2045 "no permission to access the crypt keeper"); 2046 2047 waitForReady(); 2048 2049 if (DEBUG_EVENTS) { 2050 Slog.i(TAG, "changing encryption password..."); 2051 } 2052 2053 final NativeDaemonEvent event; 2054 try { 2055 event = mConnector.execute("cryptfs", "changepw", password); 2056 return Integer.parseInt(event.getMessage()); 2057 } catch (NativeDaemonConnectorException e) { 2058 // Encryption failed 2059 return e.getCode(); 2060 } 2061 } 2062 2063 /** 2064 * Validate a user-supplied password string with cryptfs 2065 */ 2066 @Override 2067 public int verifyEncryptionPassword(String password) throws RemoteException { 2068 // Only the system process is permitted to validate passwords 2069 if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) { 2070 throw new SecurityException("no permission to access the crypt keeper"); 2071 } 2072 2073 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2074 "no permission to access the crypt keeper"); 2075 2076 if (TextUtils.isEmpty(password)) { 2077 throw new IllegalArgumentException("password cannot be empty"); 2078 } 2079 2080 waitForReady(); 2081 2082 if (DEBUG_EVENTS) { 2083 Slog.i(TAG, "validating encryption password..."); 2084 } 2085 2086 final NativeDaemonEvent event; 2087 try { 2088 event = mConnector.execute("cryptfs", "verifypw", password); 2089 Slog.i(TAG, "cryptfs verifypw => " + event.getMessage()); 2090 return Integer.parseInt(event.getMessage()); 2091 } catch (NativeDaemonConnectorException e) { 2092 // Encryption failed 2093 return e.getCode(); 2094 } 2095 } 2096 2097 @Override 2098 public StorageVolume[] getVolumeList() { 2099 final int callingUserId = UserHandle.getCallingUserId(); 2100 final boolean accessAll = (mContext.checkPermission( 2101 android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, 2102 Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED); 2103 2104 synchronized (mVolumesLock) { 2105 final ArrayList<StorageVolume> filtered = Lists.newArrayList(); 2106 for (StorageVolume volume : mVolumes) { 2107 final UserHandle owner = volume.getOwner(); 2108 final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId; 2109 if (accessAll || ownerMatch) { 2110 filtered.add(volume); 2111 } 2112 } 2113 return filtered.toArray(new StorageVolume[filtered.size()]); 2114 } 2115 } 2116 2117 private void addObbStateLocked(ObbState obbState) throws RemoteException { 2118 final IBinder binder = obbState.getBinder(); 2119 List<ObbState> obbStates = mObbMounts.get(binder); 2120 2121 if (obbStates == null) { 2122 obbStates = new ArrayList<ObbState>(); 2123 mObbMounts.put(binder, obbStates); 2124 } else { 2125 for (final ObbState o : obbStates) { 2126 if (o.rawPath.equals(obbState.rawPath)) { 2127 throw new IllegalStateException("Attempt to add ObbState twice. " 2128 + "This indicates an error in the MountService logic."); 2129 } 2130 } 2131 } 2132 2133 obbStates.add(obbState); 2134 try { 2135 obbState.link(); 2136 } catch (RemoteException e) { 2137 /* 2138 * The binder died before we could link it, so clean up our state 2139 * and return failure. 2140 */ 2141 obbStates.remove(obbState); 2142 if (obbStates.isEmpty()) { 2143 mObbMounts.remove(binder); 2144 } 2145 2146 // Rethrow the error so mountObb can get it 2147 throw e; 2148 } 2149 2150 mObbPathToStateMap.put(obbState.rawPath, obbState); 2151 } 2152 2153 private void removeObbStateLocked(ObbState obbState) { 2154 final IBinder binder = obbState.getBinder(); 2155 final List<ObbState> obbStates = mObbMounts.get(binder); 2156 if (obbStates != null) { 2157 if (obbStates.remove(obbState)) { 2158 obbState.unlink(); 2159 } 2160 if (obbStates.isEmpty()) { 2161 mObbMounts.remove(binder); 2162 } 2163 } 2164 2165 mObbPathToStateMap.remove(obbState.rawPath); 2166 } 2167 2168 private class ObbActionHandler extends Handler { 2169 private boolean mBound = false; 2170 private final List<ObbAction> mActions = new LinkedList<ObbAction>(); 2171 2172 ObbActionHandler(Looper l) { 2173 super(l); 2174 } 2175 2176 @Override 2177 public void handleMessage(Message msg) { 2178 switch (msg.what) { 2179 case OBB_RUN_ACTION: { 2180 final ObbAction action = (ObbAction) msg.obj; 2181 2182 if (DEBUG_OBB) 2183 Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString()); 2184 2185 // If a bind was already initiated we don't really 2186 // need to do anything. The pending install 2187 // will be processed later on. 2188 if (!mBound) { 2189 // If this is the only one pending we might 2190 // have to bind to the service again. 2191 if (!connectToService()) { 2192 Slog.e(TAG, "Failed to bind to media container service"); 2193 action.handleError(); 2194 return; 2195 } 2196 } 2197 2198 mActions.add(action); 2199 break; 2200 } 2201 case OBB_MCS_BOUND: { 2202 if (DEBUG_OBB) 2203 Slog.i(TAG, "OBB_MCS_BOUND"); 2204 if (msg.obj != null) { 2205 mContainerService = (IMediaContainerService) msg.obj; 2206 } 2207 if (mContainerService == null) { 2208 // Something seriously wrong. Bail out 2209 Slog.e(TAG, "Cannot bind to media container service"); 2210 for (ObbAction action : mActions) { 2211 // Indicate service bind error 2212 action.handleError(); 2213 } 2214 mActions.clear(); 2215 } else if (mActions.size() > 0) { 2216 final ObbAction action = mActions.get(0); 2217 if (action != null) { 2218 action.execute(this); 2219 } 2220 } else { 2221 // Should never happen ideally. 2222 Slog.w(TAG, "Empty queue"); 2223 } 2224 break; 2225 } 2226 case OBB_MCS_RECONNECT: { 2227 if (DEBUG_OBB) 2228 Slog.i(TAG, "OBB_MCS_RECONNECT"); 2229 if (mActions.size() > 0) { 2230 if (mBound) { 2231 disconnectService(); 2232 } 2233 if (!connectToService()) { 2234 Slog.e(TAG, "Failed to bind to media container service"); 2235 for (ObbAction action : mActions) { 2236 // Indicate service bind error 2237 action.handleError(); 2238 } 2239 mActions.clear(); 2240 } 2241 } 2242 break; 2243 } 2244 case OBB_MCS_UNBIND: { 2245 if (DEBUG_OBB) 2246 Slog.i(TAG, "OBB_MCS_UNBIND"); 2247 2248 // Delete pending install 2249 if (mActions.size() > 0) { 2250 mActions.remove(0); 2251 } 2252 if (mActions.size() == 0) { 2253 if (mBound) { 2254 disconnectService(); 2255 } 2256 } else { 2257 // There are more pending requests in queue. 2258 // Just post MCS_BOUND message to trigger processing 2259 // of next pending install. 2260 mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND); 2261 } 2262 break; 2263 } 2264 case OBB_FLUSH_MOUNT_STATE: { 2265 final String path = (String) msg.obj; 2266 2267 if (DEBUG_OBB) 2268 Slog.i(TAG, "Flushing all OBB state for path " + path); 2269 2270 synchronized (mObbMounts) { 2271 final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>(); 2272 2273 final Iterator<ObbState> i = mObbPathToStateMap.values().iterator(); 2274 while (i.hasNext()) { 2275 final ObbState state = i.next(); 2276 2277 /* 2278 * If this entry's source file is in the volume path 2279 * that got unmounted, remove it because it's no 2280 * longer valid. 2281 */ 2282 if (state.canonicalPath.startsWith(path)) { 2283 obbStatesToRemove.add(state); 2284 } 2285 } 2286 2287 for (final ObbState obbState : obbStatesToRemove) { 2288 if (DEBUG_OBB) 2289 Slog.i(TAG, "Removing state for " + obbState.rawPath); 2290 2291 removeObbStateLocked(obbState); 2292 2293 try { 2294 obbState.token.onObbResult(obbState.rawPath, obbState.nonce, 2295 OnObbStateChangeListener.UNMOUNTED); 2296 } catch (RemoteException e) { 2297 Slog.i(TAG, "Couldn't send unmount notification for OBB: " 2298 + obbState.rawPath); 2299 } 2300 } 2301 } 2302 break; 2303 } 2304 } 2305 } 2306 2307 private boolean connectToService() { 2308 if (DEBUG_OBB) 2309 Slog.i(TAG, "Trying to bind to DefaultContainerService"); 2310 2311 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); 2312 if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) { 2313 mBound = true; 2314 return true; 2315 } 2316 return false; 2317 } 2318 2319 private void disconnectService() { 2320 mContainerService = null; 2321 mBound = false; 2322 mContext.unbindService(mDefContainerConn); 2323 } 2324 } 2325 2326 abstract class ObbAction { 2327 private static final int MAX_RETRIES = 3; 2328 private int mRetries; 2329 2330 ObbState mObbState; 2331 2332 ObbAction(ObbState obbState) { 2333 mObbState = obbState; 2334 } 2335 2336 public void execute(ObbActionHandler handler) { 2337 try { 2338 if (DEBUG_OBB) 2339 Slog.i(TAG, "Starting to execute action: " + toString()); 2340 mRetries++; 2341 if (mRetries > MAX_RETRIES) { 2342 Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); 2343 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2344 handleError(); 2345 return; 2346 } else { 2347 handleExecute(); 2348 if (DEBUG_OBB) 2349 Slog.i(TAG, "Posting install MCS_UNBIND"); 2350 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2351 } 2352 } catch (RemoteException e) { 2353 if (DEBUG_OBB) 2354 Slog.i(TAG, "Posting install MCS_RECONNECT"); 2355 mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT); 2356 } catch (Exception e) { 2357 if (DEBUG_OBB) 2358 Slog.d(TAG, "Error handling OBB action", e); 2359 handleError(); 2360 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2361 } 2362 } 2363 2364 abstract void handleExecute() throws RemoteException, IOException; 2365 abstract void handleError(); 2366 2367 protected ObbInfo getObbInfo() throws IOException { 2368 ObbInfo obbInfo; 2369 try { 2370 obbInfo = mContainerService.getObbInfo(mObbState.ownerPath); 2371 } catch (RemoteException e) { 2372 Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for " 2373 + mObbState.ownerPath); 2374 obbInfo = null; 2375 } 2376 if (obbInfo == null) { 2377 throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath); 2378 } 2379 return obbInfo; 2380 } 2381 2382 protected void sendNewStatusOrIgnore(int status) { 2383 if (mObbState == null || mObbState.token == null) { 2384 return; 2385 } 2386 2387 try { 2388 mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status); 2389 } catch (RemoteException e) { 2390 Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); 2391 } 2392 } 2393 } 2394 2395 class MountObbAction extends ObbAction { 2396 private final String mKey; 2397 private final int mCallingUid; 2398 2399 MountObbAction(ObbState obbState, String key, int callingUid) { 2400 super(obbState); 2401 mKey = key; 2402 mCallingUid = callingUid; 2403 } 2404 2405 @Override 2406 public void handleExecute() throws IOException, RemoteException { 2407 waitForReady(); 2408 warnOnNotMounted(); 2409 2410 final ObbInfo obbInfo = getObbInfo(); 2411 2412 if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) { 2413 Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename 2414 + " which is owned by " + obbInfo.packageName); 2415 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 2416 return; 2417 } 2418 2419 final boolean isMounted; 2420 synchronized (mObbMounts) { 2421 isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath); 2422 } 2423 if (isMounted) { 2424 Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename); 2425 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED); 2426 return; 2427 } 2428 2429 final String hashedKey; 2430 if (mKey == null) { 2431 hashedKey = "none"; 2432 } else { 2433 try { 2434 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 2435 2436 KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt, 2437 PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE); 2438 SecretKey key = factory.generateSecret(ks); 2439 BigInteger bi = new BigInteger(key.getEncoded()); 2440 hashedKey = bi.toString(16); 2441 } catch (NoSuchAlgorithmException e) { 2442 Slog.e(TAG, "Could not load PBKDF2 algorithm", e); 2443 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2444 return; 2445 } catch (InvalidKeySpecException e) { 2446 Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e); 2447 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2448 return; 2449 } 2450 } 2451 2452 int rc = StorageResultCode.OperationSucceeded; 2453 try { 2454 mConnector.execute( 2455 "obb", "mount", mObbState.voldPath, hashedKey, mObbState.ownerGid); 2456 } catch (NativeDaemonConnectorException e) { 2457 int code = e.getCode(); 2458 if (code != VoldResponseCode.OpFailedStorageBusy) { 2459 rc = StorageResultCode.OperationFailedInternalError; 2460 } 2461 } 2462 2463 if (rc == StorageResultCode.OperationSucceeded) { 2464 if (DEBUG_OBB) 2465 Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath); 2466 2467 synchronized (mObbMounts) { 2468 addObbStateLocked(mObbState); 2469 } 2470 2471 sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED); 2472 } else { 2473 Slog.e(TAG, "Couldn't mount OBB file: " + rc); 2474 2475 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT); 2476 } 2477 } 2478 2479 @Override 2480 public void handleError() { 2481 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2482 } 2483 2484 @Override 2485 public String toString() { 2486 StringBuilder sb = new StringBuilder(); 2487 sb.append("MountObbAction{"); 2488 sb.append(mObbState); 2489 sb.append('}'); 2490 return sb.toString(); 2491 } 2492 } 2493 2494 class UnmountObbAction extends ObbAction { 2495 private final boolean mForceUnmount; 2496 2497 UnmountObbAction(ObbState obbState, boolean force) { 2498 super(obbState); 2499 mForceUnmount = force; 2500 } 2501 2502 @Override 2503 public void handleExecute() throws IOException { 2504 waitForReady(); 2505 warnOnNotMounted(); 2506 2507 final ObbInfo obbInfo = getObbInfo(); 2508 2509 final ObbState existingState; 2510 synchronized (mObbMounts) { 2511 existingState = mObbPathToStateMap.get(mObbState.rawPath); 2512 } 2513 2514 if (existingState == null) { 2515 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED); 2516 return; 2517 } 2518 2519 if (existingState.ownerGid != mObbState.ownerGid) { 2520 Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath 2521 + " (owned by GID " + existingState.ownerGid + ")"); 2522 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 2523 return; 2524 } 2525 2526 int rc = StorageResultCode.OperationSucceeded; 2527 try { 2528 final Command cmd = new Command("obb", "unmount", mObbState.voldPath); 2529 if (mForceUnmount) { 2530 cmd.appendArg("force"); 2531 } 2532 mConnector.execute(cmd); 2533 } catch (NativeDaemonConnectorException e) { 2534 int code = e.getCode(); 2535 if (code == VoldResponseCode.OpFailedStorageBusy) { 2536 rc = StorageResultCode.OperationFailedStorageBusy; 2537 } else if (code == VoldResponseCode.OpFailedStorageNotFound) { 2538 // If it's not mounted then we've already won. 2539 rc = StorageResultCode.OperationSucceeded; 2540 } else { 2541 rc = StorageResultCode.OperationFailedInternalError; 2542 } 2543 } 2544 2545 if (rc == StorageResultCode.OperationSucceeded) { 2546 synchronized (mObbMounts) { 2547 removeObbStateLocked(existingState); 2548 } 2549 2550 sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED); 2551 } else { 2552 Slog.w(TAG, "Could not unmount OBB: " + existingState); 2553 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT); 2554 } 2555 } 2556 2557 @Override 2558 public void handleError() { 2559 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2560 } 2561 2562 @Override 2563 public String toString() { 2564 StringBuilder sb = new StringBuilder(); 2565 sb.append("UnmountObbAction{"); 2566 sb.append(mObbState); 2567 sb.append(",force="); 2568 sb.append(mForceUnmount); 2569 sb.append('}'); 2570 return sb.toString(); 2571 } 2572 } 2573 2574 // @VisibleForTesting 2575 public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) { 2576 // TODO: allow caller to provide Environment for full testing 2577 2578 // Only adjust paths when storage is emulated 2579 if (!Environment.isExternalStorageEmulated()) { 2580 return canonicalPath; 2581 } 2582 2583 String path = canonicalPath.toString(); 2584 2585 // First trim off any external storage prefix 2586 final UserEnvironment userEnv = new UserEnvironment(userId); 2587 2588 // /storage/emulated/0 2589 final String externalPath = userEnv.getExternalStorageDirectory().toString(); 2590 // /storage/emulated_legacy 2591 final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory() 2592 .toString(); 2593 2594 if (path.startsWith(externalPath)) { 2595 path = path.substring(externalPath.length() + 1); 2596 } else if (path.startsWith(legacyExternalPath)) { 2597 path = path.substring(legacyExternalPath.length() + 1); 2598 } else { 2599 return canonicalPath; 2600 } 2601 2602 // Handle special OBB paths on emulated storage 2603 final String obbPath = "Android/obb"; 2604 if (path.startsWith(obbPath)) { 2605 path = path.substring(obbPath.length() + 1); 2606 2607 if (forVold) { 2608 return new File(Environment.getEmulatedStorageObbSource(), path).toString(); 2609 } else { 2610 final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER); 2611 return new File(ownerEnv.getExternalStorageObbDirectory(), path).toString(); 2612 } 2613 } 2614 2615 // Handle normal external storage paths 2616 if (forVold) { 2617 return new File(Environment.getEmulatedStorageSource(userId), path).toString(); 2618 } else { 2619 return new File(userEnv.getExternalStorageDirectory(), path).toString(); 2620 } 2621 } 2622 2623 @Override 2624 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2625 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { 2626 pw.println("Permission Denial: can't dump ActivityManager from from pid=" 2627 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 2628 + " without permission " + android.Manifest.permission.DUMP); 2629 return; 2630 } 2631 2632 synchronized (mObbMounts) { 2633 pw.println(" mObbMounts:"); 2634 2635 final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator(); 2636 while (binders.hasNext()) { 2637 Entry<IBinder, List<ObbState>> e = binders.next(); 2638 pw.print(" Key="); pw.println(e.getKey().toString()); 2639 final List<ObbState> obbStates = e.getValue(); 2640 for (final ObbState obbState : obbStates) { 2641 pw.print(" "); pw.println(obbState.toString()); 2642 } 2643 } 2644 2645 pw.println(""); 2646 pw.println(" mObbPathToStateMap:"); 2647 final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator(); 2648 while (maps.hasNext()) { 2649 final Entry<String, ObbState> e = maps.next(); 2650 pw.print(" "); pw.print(e.getKey()); 2651 pw.print(" -> "); pw.println(e.getValue().toString()); 2652 } 2653 } 2654 2655 pw.println(""); 2656 2657 synchronized (mVolumesLock) { 2658 pw.println(" mVolumes:"); 2659 2660 final int N = mVolumes.size(); 2661 for (int i = 0; i < N; i++) { 2662 final StorageVolume v = mVolumes.get(i); 2663 pw.print(" "); 2664 pw.println(v.toString()); 2665 } 2666 } 2667 2668 pw.println(); 2669 pw.println(" mConnection:"); 2670 mConnector.dump(fd, pw, args); 2671 } 2672 2673 /** {@inheritDoc} */ 2674 public void monitor() { 2675 if (mConnector != null) { 2676 mConnector.monitor(); 2677 } 2678 } 2679 } 2680