MountService.java revision af9d667ccf3e24058214cf4cc0a8aa8bc5100e3c
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 com.android.internal.app.IMediaContainerService; 20import com.android.internal.util.HexDump; 21import com.android.server.am.ActivityManagerService; 22 23import android.content.BroadcastReceiver; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.ServiceConnection; 29import android.content.pm.PackageManager; 30import android.content.res.ObbInfo; 31import android.net.Uri; 32import android.os.Binder; 33import android.os.Environment; 34import android.os.Handler; 35import android.os.HandlerThread; 36import android.os.IBinder; 37import android.os.Looper; 38import android.os.Message; 39import android.os.RemoteException; 40import android.os.ServiceManager; 41import android.os.SystemClock; 42import android.os.SystemProperties; 43import android.os.storage.IMountService; 44import android.os.storage.IMountServiceListener; 45import android.os.storage.IMountShutdownObserver; 46import android.os.storage.IObbActionListener; 47import android.os.storage.OnObbStateChangeListener; 48import android.os.storage.StorageResultCode; 49import android.security.MessageDigest; 50import android.util.Slog; 51 52import java.io.FileDescriptor; 53import java.io.IOException; 54import java.io.PrintWriter; 55import java.security.NoSuchAlgorithmException; 56import java.util.ArrayList; 57import java.util.HashMap; 58import java.util.HashSet; 59import java.util.Iterator; 60import java.util.LinkedList; 61import java.util.List; 62import java.util.Map; 63import java.util.Map.Entry; 64 65/** 66 * MountService implements back-end services for platform storage 67 * management. 68 * @hide - Applications should use android.os.storage.StorageManager 69 * to access the MountService. 70 */ 71class MountService extends IMountService.Stub 72 implements INativeDaemonConnectorCallbacks { 73 private static final boolean LOCAL_LOGD = false; 74 private static final boolean DEBUG_UNMOUNT = false; 75 private static final boolean DEBUG_EVENTS = false; 76 private static final boolean DEBUG_OBB = true; 77 78 private static final String TAG = "MountService"; 79 80 private static final String VOLD_TAG = "VoldConnector"; 81 82 /* 83 * Internal vold volume state constants 84 */ 85 class VolumeState { 86 public static final int Init = -1; 87 public static final int NoMedia = 0; 88 public static final int Idle = 1; 89 public static final int Pending = 2; 90 public static final int Checking = 3; 91 public static final int Mounted = 4; 92 public static final int Unmounting = 5; 93 public static final int Formatting = 6; 94 public static final int Shared = 7; 95 public static final int SharedMnt = 8; 96 } 97 98 /* 99 * Internal vold response code constants 100 */ 101 class VoldResponseCode { 102 /* 103 * 100 series - Requestion action was initiated; expect another reply 104 * before proceeding with a new command. 105 */ 106 public static final int VolumeListResult = 110; 107 public static final int AsecListResult = 111; 108 public static final int StorageUsersListResult = 112; 109 110 /* 111 * 200 series - Requestion action has been successfully completed. 112 */ 113 public static final int ShareStatusResult = 210; 114 public static final int AsecPathResult = 211; 115 public static final int ShareEnabledResult = 212; 116 117 /* 118 * 400 series - Command was accepted, but the requested action 119 * did not take place. 120 */ 121 public static final int OpFailedNoMedia = 401; 122 public static final int OpFailedMediaBlank = 402; 123 public static final int OpFailedMediaCorrupt = 403; 124 public static final int OpFailedVolNotMounted = 404; 125 public static final int OpFailedStorageBusy = 405; 126 public static final int OpFailedStorageNotFound = 406; 127 128 /* 129 * 600 series - Unsolicited broadcasts. 130 */ 131 public static final int VolumeStateChange = 605; 132 public static final int ShareAvailabilityChange = 620; 133 public static final int VolumeDiskInserted = 630; 134 public static final int VolumeDiskRemoved = 631; 135 public static final int VolumeBadRemoval = 632; 136 } 137 138 private Context mContext; 139 private NativeDaemonConnector mConnector; 140 private String mLegacyState = Environment.MEDIA_REMOVED; 141 private PackageManagerService mPms; 142 private boolean mUmsEnabling; 143 // Used as a lock for methods that register/unregister listeners. 144 final private ArrayList<MountServiceBinderListener> mListeners = 145 new ArrayList<MountServiceBinderListener>(); 146 private boolean mBooted = false; 147 private boolean mReady = false; 148 private boolean mSendUmsConnectedOnBoot = false; 149 150 /** 151 * Private hash of currently mounted secure containers. 152 * Used as a lock in methods to manipulate secure containers. 153 */ 154 final private HashSet<String> mAsecMountSet = new HashSet<String>(); 155 156 /** 157 * Mounted OBB tracking information. Used to track the current state of all 158 * OBBs. 159 */ 160 final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>(); 161 final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>(); 162 163 class ObbState implements IBinder.DeathRecipient { 164 public ObbState(String filename, int callerUid, IObbActionListener token, int nonce) 165 throws RemoteException { 166 this.filename = filename; 167 this.callerUid = callerUid; 168 this.token = token; 169 this.nonce = nonce; 170 } 171 172 // OBB source filename 173 String filename; 174 175 // Binder.callingUid() 176 final public int callerUid; 177 178 // Token of remote Binder caller 179 final IObbActionListener token; 180 181 // Identifier to pass back to the token 182 final int nonce; 183 184 public IBinder getBinder() { 185 return token.asBinder(); 186 } 187 188 @Override 189 public void binderDied() { 190 ObbAction action = new UnmountObbAction(this, true); 191 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 192 } 193 194 public void link() throws RemoteException { 195 getBinder().linkToDeath(this, 0); 196 } 197 198 public void unlink() { 199 getBinder().unlinkToDeath(this, 0); 200 } 201 202 @Override 203 public String toString() { 204 StringBuilder sb = new StringBuilder("ObbState{"); 205 sb.append("filename="); 206 sb.append(filename); 207 sb.append(",token="); 208 sb.append(token.toString()); 209 sb.append(",callerUid="); 210 sb.append(callerUid); 211 sb.append('}'); 212 return sb.toString(); 213 } 214 } 215 216 // OBB Action Handler 217 final private ObbActionHandler mObbActionHandler; 218 219 // OBB action handler messages 220 private static final int OBB_RUN_ACTION = 1; 221 private static final int OBB_MCS_BOUND = 2; 222 private static final int OBB_MCS_UNBIND = 3; 223 private static final int OBB_MCS_RECONNECT = 4; 224 private static final int OBB_FLUSH_MOUNT_STATE = 5; 225 226 /* 227 * Default Container Service information 228 */ 229 static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( 230 "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService"); 231 232 final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); 233 234 class DefaultContainerConnection implements ServiceConnection { 235 public void onServiceConnected(ComponentName name, IBinder service) { 236 if (DEBUG_OBB) 237 Slog.i(TAG, "onServiceConnected"); 238 IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); 239 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs)); 240 } 241 242 public void onServiceDisconnected(ComponentName name) { 243 if (DEBUG_OBB) 244 Slog.i(TAG, "onServiceDisconnected"); 245 } 246 }; 247 248 // Used in the ObbActionHandler 249 private IMediaContainerService mContainerService = null; 250 251 // Handler messages 252 private static final int H_UNMOUNT_PM_UPDATE = 1; 253 private static final int H_UNMOUNT_PM_DONE = 2; 254 private static final int H_UNMOUNT_MS = 3; 255 private static final int RETRY_UNMOUNT_DELAY = 30; // in ms 256 private static final int MAX_UNMOUNT_RETRIES = 4; 257 258 class UnmountCallBack { 259 final String path; 260 final boolean force; 261 int retries; 262 263 UnmountCallBack(String path, boolean force) { 264 retries = 0; 265 this.path = path; 266 this.force = force; 267 } 268 269 void handleFinished() { 270 if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path); 271 doUnmountVolume(path, true); 272 } 273 } 274 275 class UmsEnableCallBack extends UnmountCallBack { 276 final String method; 277 278 UmsEnableCallBack(String path, String method, boolean force) { 279 super(path, force); 280 this.method = method; 281 } 282 283 @Override 284 void handleFinished() { 285 super.handleFinished(); 286 doShareUnshareVolume(path, method, true); 287 } 288 } 289 290 class ShutdownCallBack extends UnmountCallBack { 291 IMountShutdownObserver observer; 292 ShutdownCallBack(String path, IMountShutdownObserver observer) { 293 super(path, true); 294 this.observer = observer; 295 } 296 297 @Override 298 void handleFinished() { 299 int ret = doUnmountVolume(path, true); 300 if (observer != null) { 301 try { 302 observer.onShutDownComplete(ret); 303 } catch (RemoteException e) { 304 Slog.w(TAG, "RemoteException when shutting down"); 305 } 306 } 307 } 308 } 309 310 class MountServiceHandler extends Handler { 311 ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>(); 312 boolean mUpdatingStatus = false; 313 314 MountServiceHandler(Looper l) { 315 super(l); 316 } 317 318 public void handleMessage(Message msg) { 319 switch (msg.what) { 320 case H_UNMOUNT_PM_UPDATE: { 321 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE"); 322 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 323 mForceUnmounts.add(ucb); 324 if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus); 325 // Register only if needed. 326 if (!mUpdatingStatus) { 327 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager"); 328 mUpdatingStatus = true; 329 mPms.updateExternalMediaStatus(false, true); 330 } 331 break; 332 } 333 case H_UNMOUNT_PM_DONE: { 334 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE"); 335 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests"); 336 mUpdatingStatus = false; 337 int size = mForceUnmounts.size(); 338 int sizeArr[] = new int[size]; 339 int sizeArrN = 0; 340 // Kill processes holding references first 341 ActivityManagerService ams = (ActivityManagerService) 342 ServiceManager.getService("activity"); 343 for (int i = 0; i < size; i++) { 344 UnmountCallBack ucb = mForceUnmounts.get(i); 345 String path = ucb.path; 346 boolean done = false; 347 if (!ucb.force) { 348 done = true; 349 } else { 350 int pids[] = getStorageUsers(path); 351 if (pids == null || pids.length == 0) { 352 done = true; 353 } else { 354 // Eliminate system process here? 355 ams.killPids(pids, "unmount media"); 356 // Confirm if file references have been freed. 357 pids = getStorageUsers(path); 358 if (pids == null || pids.length == 0) { 359 done = true; 360 } 361 } 362 } 363 if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) { 364 // Retry again 365 Slog.i(TAG, "Retrying to kill storage users again"); 366 mHandler.sendMessageDelayed( 367 mHandler.obtainMessage(H_UNMOUNT_PM_DONE, 368 ucb.retries++), 369 RETRY_UNMOUNT_DELAY); 370 } else { 371 if (ucb.retries >= MAX_UNMOUNT_RETRIES) { 372 Slog.i(TAG, "Failed to unmount media inspite of " + 373 MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now"); 374 } 375 sizeArr[sizeArrN++] = i; 376 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS, 377 ucb)); 378 } 379 } 380 // Remove already processed elements from list. 381 for (int i = (sizeArrN-1); i >= 0; i--) { 382 mForceUnmounts.remove(sizeArr[i]); 383 } 384 break; 385 } 386 case H_UNMOUNT_MS : { 387 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS"); 388 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 389 ucb.handleFinished(); 390 break; 391 } 392 } 393 } 394 }; 395 final private HandlerThread mHandlerThread; 396 final private Handler mHandler; 397 398 private void waitForReady() { 399 while (mReady == false) { 400 for (int retries = 5; retries > 0; retries--) { 401 if (mReady) { 402 return; 403 } 404 SystemClock.sleep(1000); 405 } 406 Slog.w(TAG, "Waiting too long for mReady!"); 407 } 408 } 409 410 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 411 public void onReceive(Context context, Intent intent) { 412 String action = intent.getAction(); 413 414 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 415 mBooted = true; 416 417 /* 418 * In the simulator, we need to broadcast a volume mounted event 419 * to make the media scanner run. 420 */ 421 if ("simulator".equals(SystemProperties.get("ro.product.device"))) { 422 notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted); 423 return; 424 } 425 new Thread() { 426 public void run() { 427 try { 428 String path = Environment.getExternalStorageDirectory().getPath(); 429 String state = getVolumeState(path); 430 431 if (state.equals(Environment.MEDIA_UNMOUNTED)) { 432 int rc = doMountVolume(path); 433 if (rc != StorageResultCode.OperationSucceeded) { 434 Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc)); 435 } 436 } else if (state.equals(Environment.MEDIA_SHARED)) { 437 /* 438 * Bootstrap UMS enabled state since vold indicates 439 * the volume is shared (runtime restart while ums enabled) 440 */ 441 notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared); 442 } 443 444 /* 445 * If UMS was connected on boot, send the connected event 446 * now that we're up. 447 */ 448 if (mSendUmsConnectedOnBoot) { 449 sendUmsIntent(true); 450 mSendUmsConnectedOnBoot = false; 451 } 452 } catch (Exception ex) { 453 Slog.e(TAG, "Boot-time mount exception", ex); 454 } 455 } 456 }.start(); 457 } 458 } 459 }; 460 461 private final class MountServiceBinderListener implements IBinder.DeathRecipient { 462 final IMountServiceListener mListener; 463 464 MountServiceBinderListener(IMountServiceListener listener) { 465 mListener = listener; 466 467 } 468 469 public void binderDied() { 470 if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!"); 471 synchronized (mListeners) { 472 mListeners.remove(this); 473 mListener.asBinder().unlinkToDeath(this, 0); 474 } 475 } 476 } 477 478 private void doShareUnshareVolume(String path, String method, boolean enable) { 479 // TODO: Add support for multiple share methods 480 if (!method.equals("ums")) { 481 throw new IllegalArgumentException(String.format("Method %s not supported", method)); 482 } 483 484 try { 485 mConnector.doCommand(String.format( 486 "volume %sshare %s %s", (enable ? "" : "un"), path, method)); 487 } catch (NativeDaemonConnectorException e) { 488 Slog.e(TAG, "Failed to share/unshare", e); 489 } 490 } 491 492 private void updatePublicVolumeState(String path, String state) { 493 if (!path.equals(Environment.getExternalStorageDirectory().getPath())) { 494 Slog.w(TAG, "Multiple volumes not currently supported"); 495 return; 496 } 497 498 if (mLegacyState.equals(state)) { 499 Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state)); 500 return; 501 } 502 503 if (Environment.MEDIA_UNMOUNTED.equals(state)) { 504 // Tell the package manager the media is gone. 505 mPms.updateExternalMediaStatus(false, false); 506 507 /* 508 * Some OBBs might have been unmounted when this volume was 509 * unmounted, so send a message to the handler to let it know to 510 * remove those from the list of mounted OBBS. 511 */ 512 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE, 513 path)); 514 } else if (Environment.MEDIA_MOUNTED.equals(state)) { 515 // Tell the package manager the media is available for use. 516 mPms.updateExternalMediaStatus(true, false); 517 } 518 519 String oldState = mLegacyState; 520 mLegacyState = state; 521 522 synchronized (mListeners) { 523 for (int i = mListeners.size() -1; i >= 0; i--) { 524 MountServiceBinderListener bl = mListeners.get(i); 525 try { 526 bl.mListener.onStorageStateChanged(path, oldState, state); 527 } catch (RemoteException rex) { 528 Slog.e(TAG, "Listener dead"); 529 mListeners.remove(i); 530 } catch (Exception ex) { 531 Slog.e(TAG, "Listener failed", ex); 532 } 533 } 534 } 535 } 536 537 /** 538 * 539 * Callback from NativeDaemonConnector 540 */ 541 public void onDaemonConnected() { 542 /* 543 * Since we'll be calling back into the NativeDaemonConnector, 544 * we need to do our work in a new thread. 545 */ 546 new Thread() { 547 public void run() { 548 /** 549 * Determine media state and UMS detection status 550 */ 551 String path = Environment.getExternalStorageDirectory().getPath(); 552 String state = Environment.MEDIA_REMOVED; 553 554 try { 555 String[] vols = mConnector.doListCommand( 556 "volume list", VoldResponseCode.VolumeListResult); 557 for (String volstr : vols) { 558 String[] tok = volstr.split(" "); 559 // FMT: <label> <mountpoint> <state> 560 if (!tok[1].equals(path)) { 561 Slog.w(TAG, String.format( 562 "Skipping unknown volume '%s'",tok[1])); 563 continue; 564 } 565 int st = Integer.parseInt(tok[2]); 566 if (st == VolumeState.NoMedia) { 567 state = Environment.MEDIA_REMOVED; 568 } else if (st == VolumeState.Idle) { 569 state = Environment.MEDIA_UNMOUNTED; 570 } else if (st == VolumeState.Mounted) { 571 state = Environment.MEDIA_MOUNTED; 572 Slog.i(TAG, "Media already mounted on daemon connection"); 573 } else if (st == VolumeState.Shared) { 574 state = Environment.MEDIA_SHARED; 575 Slog.i(TAG, "Media shared on daemon connection"); 576 } else { 577 throw new Exception(String.format("Unexpected state %d", st)); 578 } 579 } 580 if (state != null) { 581 if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state); 582 updatePublicVolumeState(path, state); 583 } 584 } catch (Exception e) { 585 Slog.e(TAG, "Error processing initial volume state", e); 586 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 587 } 588 589 try { 590 boolean avail = doGetShareMethodAvailable("ums"); 591 notifyShareAvailabilityChange("ums", avail); 592 } catch (Exception ex) { 593 Slog.w(TAG, "Failed to get share availability"); 594 } 595 /* 596 * Now that we've done our initialization, release 597 * the hounds! 598 */ 599 mReady = true; 600 } 601 }.start(); 602 } 603 604 /** 605 * Callback from NativeDaemonConnector 606 */ 607 public boolean onEvent(int code, String raw, String[] cooked) { 608 Intent in = null; 609 610 if (DEBUG_EVENTS) { 611 StringBuilder builder = new StringBuilder(); 612 builder.append("onEvent::"); 613 builder.append(" raw= " + raw); 614 if (cooked != null) { 615 builder.append(" cooked = " ); 616 for (String str : cooked) { 617 builder.append(" " + str); 618 } 619 } 620 Slog.i(TAG, builder.toString()); 621 } 622 if (code == VoldResponseCode.VolumeStateChange) { 623 /* 624 * One of the volumes we're managing has changed state. 625 * Format: "NNN Volume <label> <path> state changed 626 * from <old_#> (<old_str>) to <new_#> (<new_str>)" 627 */ 628 notifyVolumeStateChange( 629 cooked[2], cooked[3], Integer.parseInt(cooked[7]), 630 Integer.parseInt(cooked[10])); 631 } else if (code == VoldResponseCode.ShareAvailabilityChange) { 632 // FMT: NNN Share method <method> now <available|unavailable> 633 boolean avail = false; 634 if (cooked[5].equals("available")) { 635 avail = true; 636 } 637 notifyShareAvailabilityChange(cooked[3], avail); 638 } else if ((code == VoldResponseCode.VolumeDiskInserted) || 639 (code == VoldResponseCode.VolumeDiskRemoved) || 640 (code == VoldResponseCode.VolumeBadRemoval)) { 641 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>) 642 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>) 643 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>) 644 final String label = cooked[2]; 645 final String path = cooked[3]; 646 int major = -1; 647 int minor = -1; 648 649 try { 650 String devComp = cooked[6].substring(1, cooked[6].length() -1); 651 String[] devTok = devComp.split(":"); 652 major = Integer.parseInt(devTok[0]); 653 minor = Integer.parseInt(devTok[1]); 654 } catch (Exception ex) { 655 Slog.e(TAG, "Failed to parse major/minor", ex); 656 } 657 658 if (code == VoldResponseCode.VolumeDiskInserted) { 659 new Thread() { 660 public void run() { 661 try { 662 int rc; 663 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 664 Slog.w(TAG, String.format("Insertion mount failed (%d)", rc)); 665 } 666 } catch (Exception ex) { 667 Slog.w(TAG, "Failed to mount media on insertion", ex); 668 } 669 } 670 }.start(); 671 } else if (code == VoldResponseCode.VolumeDiskRemoved) { 672 /* 673 * This event gets trumped if we're already in BAD_REMOVAL state 674 */ 675 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) { 676 return true; 677 } 678 /* Send the media unmounted event first */ 679 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 680 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 681 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 682 mContext.sendBroadcast(in); 683 684 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed"); 685 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 686 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path)); 687 } else if (code == VoldResponseCode.VolumeBadRemoval) { 688 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 689 /* Send the media unmounted event first */ 690 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 691 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 692 mContext.sendBroadcast(in); 693 694 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal"); 695 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL); 696 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path)); 697 } else { 698 Slog.e(TAG, String.format("Unknown code {%d}", code)); 699 } 700 } else { 701 return false; 702 } 703 704 if (in != null) { 705 mContext.sendBroadcast(in); 706 } 707 return true; 708 } 709 710 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) { 711 String vs = getVolumeState(path); 712 if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs); 713 714 Intent in = null; 715 716 if (oldState == VolumeState.Shared && newState != oldState) { 717 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent"); 718 mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_UNSHARED, 719 Uri.parse("file://" + path))); 720 } 721 722 if (newState == VolumeState.Init) { 723 } else if (newState == VolumeState.NoMedia) { 724 // NoMedia is handled via Disk Remove events 725 } else if (newState == VolumeState.Idle) { 726 /* 727 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or 728 * if we're in the process of enabling UMS 729 */ 730 if (!vs.equals( 731 Environment.MEDIA_BAD_REMOVAL) && !vs.equals( 732 Environment.MEDIA_NOFS) && !vs.equals( 733 Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) { 734 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable"); 735 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 736 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 737 } 738 } else if (newState == VolumeState.Pending) { 739 } else if (newState == VolumeState.Checking) { 740 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking"); 741 updatePublicVolumeState(path, Environment.MEDIA_CHECKING); 742 in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path)); 743 } else if (newState == VolumeState.Mounted) { 744 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted"); 745 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED); 746 in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path)); 747 in.putExtra("read-only", false); 748 } else if (newState == VolumeState.Unmounting) { 749 in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path)); 750 } else if (newState == VolumeState.Formatting) { 751 } else if (newState == VolumeState.Shared) { 752 if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted"); 753 /* Send the media unmounted event first */ 754 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 755 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 756 mContext.sendBroadcast(in); 757 758 if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared"); 759 updatePublicVolumeState(path, Environment.MEDIA_SHARED); 760 in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path)); 761 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent"); 762 } else if (newState == VolumeState.SharedMnt) { 763 Slog.e(TAG, "Live shared mounts not supported yet!"); 764 return; 765 } else { 766 Slog.e(TAG, "Unhandled VolumeState {" + newState + "}"); 767 } 768 769 if (in != null) { 770 mContext.sendBroadcast(in); 771 } 772 } 773 774 private boolean doGetShareMethodAvailable(String method) { 775 ArrayList<String> rsp; 776 try { 777 rsp = mConnector.doCommand("share status " + method); 778 } catch (NativeDaemonConnectorException ex) { 779 Slog.e(TAG, "Failed to determine whether share method " + method + " is available."); 780 return false; 781 } 782 783 for (String line : rsp) { 784 String[] tok = line.split(" "); 785 if (tok.length < 3) { 786 Slog.e(TAG, "Malformed response to share status " + method); 787 return false; 788 } 789 790 int code; 791 try { 792 code = Integer.parseInt(tok[0]); 793 } catch (NumberFormatException nfe) { 794 Slog.e(TAG, String.format("Error parsing code %s", tok[0])); 795 return false; 796 } 797 if (code == VoldResponseCode.ShareStatusResult) { 798 if (tok[2].equals("available")) 799 return true; 800 return false; 801 } else { 802 Slog.e(TAG, String.format("Unexpected response code %d", code)); 803 return false; 804 } 805 } 806 Slog.e(TAG, "Got an empty response"); 807 return false; 808 } 809 810 private int doMountVolume(String path) { 811 int rc = StorageResultCode.OperationSucceeded; 812 813 if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); 814 try { 815 mConnector.doCommand(String.format("volume mount %s", path)); 816 } catch (NativeDaemonConnectorException e) { 817 /* 818 * Mount failed for some reason 819 */ 820 Intent in = null; 821 int code = e.getCode(); 822 if (code == VoldResponseCode.OpFailedNoMedia) { 823 /* 824 * Attempt to mount but no media inserted 825 */ 826 rc = StorageResultCode.OperationFailedNoMedia; 827 } else if (code == VoldResponseCode.OpFailedMediaBlank) { 828 if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs"); 829 /* 830 * Media is blank or does not contain a supported filesystem 831 */ 832 updatePublicVolumeState(path, Environment.MEDIA_NOFS); 833 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path)); 834 rc = StorageResultCode.OperationFailedMediaBlank; 835 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 836 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt"); 837 /* 838 * Volume consistency check failed 839 */ 840 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE); 841 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path)); 842 rc = StorageResultCode.OperationFailedMediaCorrupt; 843 } else { 844 rc = StorageResultCode.OperationFailedInternalError; 845 } 846 847 /* 848 * Send broadcast intent (if required for the failure) 849 */ 850 if (in != null) { 851 mContext.sendBroadcast(in); 852 } 853 } 854 855 return rc; 856 } 857 858 /* 859 * If force is not set, we do not unmount if there are 860 * processes holding references to the volume about to be unmounted. 861 * If force is set, all the processes holding references need to be 862 * killed via the ActivityManager before actually unmounting the volume. 863 * This might even take a while and might be retried after timed delays 864 * to make sure we dont end up in an instable state and kill some core 865 * processes. 866 */ 867 private int doUnmountVolume(String path, boolean force) { 868 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) { 869 return VoldResponseCode.OpFailedVolNotMounted; 870 } 871 872 /* 873 * Force a GC to make sure AssetManagers in other threads of the 874 * system_server are cleaned up. We have to do this since AssetManager 875 * instances are kept as a WeakReference and it's possible we have files 876 * open on the external storage. 877 */ 878 Runtime.getRuntime().gc(); 879 880 // Redundant probably. But no harm in updating state again. 881 mPms.updateExternalMediaStatus(false, false); 882 try { 883 mConnector.doCommand(String.format( 884 "volume unmount %s%s", path, (force ? " force" : ""))); 885 // We unmounted the volume. None of the asec containers are available now. 886 synchronized (mAsecMountSet) { 887 mAsecMountSet.clear(); 888 } 889 return StorageResultCode.OperationSucceeded; 890 } catch (NativeDaemonConnectorException e) { 891 // Don't worry about mismatch in PackageManager since the 892 // call back will handle the status changes any way. 893 int code = e.getCode(); 894 if (code == VoldResponseCode.OpFailedVolNotMounted) { 895 return StorageResultCode.OperationFailedStorageNotMounted; 896 } else if (code == VoldResponseCode.OpFailedStorageBusy) { 897 return StorageResultCode.OperationFailedStorageBusy; 898 } else { 899 return StorageResultCode.OperationFailedInternalError; 900 } 901 } 902 } 903 904 private int doFormatVolume(String path) { 905 try { 906 String cmd = String.format("volume format %s", path); 907 mConnector.doCommand(cmd); 908 return StorageResultCode.OperationSucceeded; 909 } catch (NativeDaemonConnectorException e) { 910 int code = e.getCode(); 911 if (code == VoldResponseCode.OpFailedNoMedia) { 912 return StorageResultCode.OperationFailedNoMedia; 913 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 914 return StorageResultCode.OperationFailedMediaCorrupt; 915 } else { 916 return StorageResultCode.OperationFailedInternalError; 917 } 918 } 919 } 920 921 private boolean doGetVolumeShared(String path, String method) { 922 String cmd = String.format("volume shared %s %s", path, method); 923 ArrayList<String> rsp; 924 925 try { 926 rsp = mConnector.doCommand(cmd); 927 } catch (NativeDaemonConnectorException ex) { 928 Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); 929 return false; 930 } 931 932 for (String line : rsp) { 933 String[] tok = line.split(" "); 934 if (tok.length < 3) { 935 Slog.e(TAG, "Malformed response to volume shared " + path + " " + method + " command"); 936 return false; 937 } 938 939 int code; 940 try { 941 code = Integer.parseInt(tok[0]); 942 } catch (NumberFormatException nfe) { 943 Slog.e(TAG, String.format("Error parsing code %s", tok[0])); 944 return false; 945 } 946 if (code == VoldResponseCode.ShareEnabledResult) { 947 return "enabled".equals(tok[2]); 948 } else { 949 Slog.e(TAG, String.format("Unexpected response code %d", code)); 950 return false; 951 } 952 } 953 Slog.e(TAG, "Got an empty response"); 954 return false; 955 } 956 957 private void notifyShareAvailabilityChange(String method, final boolean avail) { 958 if (!method.equals("ums")) { 959 Slog.w(TAG, "Ignoring unsupported share method {" + method + "}"); 960 return; 961 } 962 963 synchronized (mListeners) { 964 for (int i = mListeners.size() -1; i >= 0; i--) { 965 MountServiceBinderListener bl = mListeners.get(i); 966 try { 967 bl.mListener.onUsbMassStorageConnectionChanged(avail); 968 } catch (RemoteException rex) { 969 Slog.e(TAG, "Listener dead"); 970 mListeners.remove(i); 971 } catch (Exception ex) { 972 Slog.e(TAG, "Listener failed", ex); 973 } 974 } 975 } 976 977 if (mBooted == true) { 978 sendUmsIntent(avail); 979 } else { 980 mSendUmsConnectedOnBoot = avail; 981 } 982 983 final String path = Environment.getExternalStorageDirectory().getPath(); 984 if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) { 985 /* 986 * USB mass storage disconnected while enabled 987 */ 988 new Thread() { 989 public void run() { 990 try { 991 int rc; 992 Slog.w(TAG, "Disabling UMS after cable disconnect"); 993 doShareUnshareVolume(path, "ums", false); 994 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 995 Slog.e(TAG, String.format( 996 "Failed to remount {%s} on UMS enabled-disconnect (%d)", 997 path, rc)); 998 } 999 } catch (Exception ex) { 1000 Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex); 1001 } 1002 } 1003 }.start(); 1004 } 1005 } 1006 1007 private void sendUmsIntent(boolean c) { 1008 mContext.sendBroadcast( 1009 new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED))); 1010 } 1011 1012 private void validatePermission(String perm) { 1013 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { 1014 throw new SecurityException(String.format("Requires %s permission", perm)); 1015 } 1016 } 1017 1018 /** 1019 * Constructs a new MountService instance 1020 * 1021 * @param context Binder context for this service 1022 */ 1023 public MountService(Context context) { 1024 mContext = context; 1025 1026 // XXX: This will go away soon in favor of IMountServiceObserver 1027 mPms = (PackageManagerService) ServiceManager.getService("package"); 1028 1029 mContext.registerReceiver(mBroadcastReceiver, 1030 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); 1031 1032 mHandlerThread = new HandlerThread("MountService"); 1033 mHandlerThread.start(); 1034 mHandler = new MountServiceHandler(mHandlerThread.getLooper()); 1035 1036 // Add OBB Action Handler to MountService thread. 1037 mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper()); 1038 1039 /* 1040 * Vold does not run in the simulator, so pretend the connector thread 1041 * ran and did its thing. 1042 */ 1043 if ("simulator".equals(SystemProperties.get("ro.product.device"))) { 1044 mReady = true; 1045 mUmsEnabling = true; 1046 return; 1047 } 1048 1049 /* 1050 * Create the connection to vold with a maximum queue of twice the 1051 * amount of containers we'd ever expect to have. This keeps an 1052 * "asec list" from blocking a thread repeatedly. 1053 */ 1054 mConnector = new NativeDaemonConnector(this, "vold", 1055 PackageManagerService.MAX_CONTAINERS * 2, VOLD_TAG); 1056 mReady = false; 1057 Thread thread = new Thread(mConnector, VOLD_TAG); 1058 thread.start(); 1059 } 1060 1061 /** 1062 * Exposed API calls below here 1063 */ 1064 1065 public void registerListener(IMountServiceListener listener) { 1066 synchronized (mListeners) { 1067 MountServiceBinderListener bl = new MountServiceBinderListener(listener); 1068 try { 1069 listener.asBinder().linkToDeath(bl, 0); 1070 mListeners.add(bl); 1071 } catch (RemoteException rex) { 1072 Slog.e(TAG, "Failed to link to listener death"); 1073 } 1074 } 1075 } 1076 1077 public void unregisterListener(IMountServiceListener listener) { 1078 synchronized (mListeners) { 1079 for(MountServiceBinderListener bl : mListeners) { 1080 if (bl.mListener == listener) { 1081 mListeners.remove(mListeners.indexOf(bl)); 1082 return; 1083 } 1084 } 1085 } 1086 } 1087 1088 public void shutdown(final IMountShutdownObserver observer) { 1089 validatePermission(android.Manifest.permission.SHUTDOWN); 1090 1091 Slog.i(TAG, "Shutting down"); 1092 1093 String path = Environment.getExternalStorageDirectory().getPath(); 1094 String state = getVolumeState(path); 1095 1096 if (state.equals(Environment.MEDIA_SHARED)) { 1097 /* 1098 * If the media is currently shared, unshare it. 1099 * XXX: This is still dangerous!. We should not 1100 * be rebooting at *all* if UMS is enabled, since 1101 * the UMS host could have dirty FAT cache entries 1102 * yet to flush. 1103 */ 1104 setUsbMassStorageEnabled(false); 1105 } else if (state.equals(Environment.MEDIA_CHECKING)) { 1106 /* 1107 * If the media is being checked, then we need to wait for 1108 * it to complete before being able to proceed. 1109 */ 1110 // XXX: @hackbod - Should we disable the ANR timer here? 1111 int retries = 30; 1112 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) { 1113 try { 1114 Thread.sleep(1000); 1115 } catch (InterruptedException iex) { 1116 Slog.e(TAG, "Interrupted while waiting for media", iex); 1117 break; 1118 } 1119 state = Environment.getExternalStorageState(); 1120 } 1121 if (retries == 0) { 1122 Slog.e(TAG, "Timed out waiting for media to check"); 1123 } 1124 } 1125 1126 if (state.equals(Environment.MEDIA_MOUNTED)) { 1127 // Post a unmount message. 1128 ShutdownCallBack ucb = new ShutdownCallBack(path, observer); 1129 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 1130 } 1131 } 1132 1133 private boolean getUmsEnabling() { 1134 synchronized (mListeners) { 1135 return mUmsEnabling; 1136 } 1137 } 1138 1139 private void setUmsEnabling(boolean enable) { 1140 synchronized (mListeners) { 1141 mUmsEnabling = true; 1142 } 1143 } 1144 1145 public boolean isUsbMassStorageConnected() { 1146 waitForReady(); 1147 1148 if (getUmsEnabling()) { 1149 return true; 1150 } 1151 return doGetShareMethodAvailable("ums"); 1152 } 1153 1154 public void setUsbMassStorageEnabled(boolean enable) { 1155 waitForReady(); 1156 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1157 1158 // TODO: Add support for multiple share methods 1159 1160 /* 1161 * If the volume is mounted and we're enabling then unmount it 1162 */ 1163 String path = Environment.getExternalStorageDirectory().getPath(); 1164 String vs = getVolumeState(path); 1165 String method = "ums"; 1166 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { 1167 // Override for isUsbMassStorageEnabled() 1168 setUmsEnabling(enable); 1169 UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true); 1170 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb)); 1171 // Clear override 1172 setUmsEnabling(false); 1173 } 1174 /* 1175 * If we disabled UMS then mount the volume 1176 */ 1177 if (!enable) { 1178 doShareUnshareVolume(path, method, enable); 1179 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) { 1180 Slog.e(TAG, "Failed to remount " + path + 1181 " after disabling share method " + method); 1182 /* 1183 * Even though the mount failed, the unshare didn't so don't indicate an error. 1184 * The mountVolume() call will have set the storage state and sent the necessary 1185 * broadcasts. 1186 */ 1187 } 1188 } 1189 } 1190 1191 public boolean isUsbMassStorageEnabled() { 1192 waitForReady(); 1193 return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums"); 1194 } 1195 1196 /** 1197 * @return state of the volume at the specified mount point 1198 */ 1199 public String getVolumeState(String mountPoint) { 1200 /* 1201 * XXX: Until we have multiple volume discovery, just hardwire 1202 * this to /sdcard 1203 */ 1204 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) { 1205 Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); 1206 throw new IllegalArgumentException(); 1207 } 1208 1209 return mLegacyState; 1210 } 1211 1212 public int mountVolume(String path) { 1213 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1214 1215 waitForReady(); 1216 return doMountVolume(path); 1217 } 1218 1219 public void unmountVolume(String path, boolean force) { 1220 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1221 waitForReady(); 1222 1223 String volState = getVolumeState(path); 1224 if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path + " force = " + force); 1225 if (Environment.MEDIA_UNMOUNTED.equals(volState) || 1226 Environment.MEDIA_REMOVED.equals(volState) || 1227 Environment.MEDIA_SHARED.equals(volState) || 1228 Environment.MEDIA_UNMOUNTABLE.equals(volState)) { 1229 // Media already unmounted or cannot be unmounted. 1230 // TODO return valid return code when adding observer call back. 1231 return; 1232 } 1233 UnmountCallBack ucb = new UnmountCallBack(path, force); 1234 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 1235 } 1236 1237 public int formatVolume(String path) { 1238 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); 1239 waitForReady(); 1240 1241 return doFormatVolume(path); 1242 } 1243 1244 public int []getStorageUsers(String path) { 1245 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1246 waitForReady(); 1247 try { 1248 String[] r = mConnector.doListCommand( 1249 String.format("storage users %s", path), 1250 VoldResponseCode.StorageUsersListResult); 1251 // FMT: <pid> <process name> 1252 int[] data = new int[r.length]; 1253 for (int i = 0; i < r.length; i++) { 1254 String []tok = r[i].split(" "); 1255 try { 1256 data[i] = Integer.parseInt(tok[0]); 1257 } catch (NumberFormatException nfe) { 1258 Slog.e(TAG, String.format("Error parsing pid %s", tok[0])); 1259 return new int[0]; 1260 } 1261 } 1262 return data; 1263 } catch (NativeDaemonConnectorException e) { 1264 Slog.e(TAG, "Failed to retrieve storage users list", e); 1265 return new int[0]; 1266 } 1267 } 1268 1269 private void warnOnNotMounted() { 1270 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 1271 Slog.w(TAG, "getSecureContainerList() called when storage not mounted"); 1272 } 1273 } 1274 1275 public String[] getSecureContainerList() { 1276 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1277 waitForReady(); 1278 warnOnNotMounted(); 1279 1280 try { 1281 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult); 1282 } catch (NativeDaemonConnectorException e) { 1283 return new String[0]; 1284 } 1285 } 1286 1287 public int createSecureContainer(String id, int sizeMb, String fstype, 1288 String key, int ownerUid) { 1289 validatePermission(android.Manifest.permission.ASEC_CREATE); 1290 waitForReady(); 1291 warnOnNotMounted(); 1292 1293 int rc = StorageResultCode.OperationSucceeded; 1294 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid); 1295 try { 1296 mConnector.doCommand(cmd); 1297 } catch (NativeDaemonConnectorException e) { 1298 rc = StorageResultCode.OperationFailedInternalError; 1299 } 1300 1301 if (rc == StorageResultCode.OperationSucceeded) { 1302 synchronized (mAsecMountSet) { 1303 mAsecMountSet.add(id); 1304 } 1305 } 1306 return rc; 1307 } 1308 1309 public int finalizeSecureContainer(String id) { 1310 validatePermission(android.Manifest.permission.ASEC_CREATE); 1311 warnOnNotMounted(); 1312 1313 int rc = StorageResultCode.OperationSucceeded; 1314 try { 1315 mConnector.doCommand(String.format("asec finalize %s", id)); 1316 /* 1317 * Finalization does a remount, so no need 1318 * to update mAsecMountSet 1319 */ 1320 } catch (NativeDaemonConnectorException e) { 1321 rc = StorageResultCode.OperationFailedInternalError; 1322 } 1323 return rc; 1324 } 1325 1326 public int destroySecureContainer(String id, boolean force) { 1327 validatePermission(android.Manifest.permission.ASEC_DESTROY); 1328 waitForReady(); 1329 warnOnNotMounted(); 1330 1331 /* 1332 * Force a GC to make sure AssetManagers in other threads of the 1333 * system_server are cleaned up. We have to do this since AssetManager 1334 * instances are kept as a WeakReference and it's possible we have files 1335 * open on the external storage. 1336 */ 1337 Runtime.getRuntime().gc(); 1338 1339 int rc = StorageResultCode.OperationSucceeded; 1340 try { 1341 mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : ""))); 1342 } catch (NativeDaemonConnectorException e) { 1343 int code = e.getCode(); 1344 if (code == VoldResponseCode.OpFailedStorageBusy) { 1345 rc = StorageResultCode.OperationFailedStorageBusy; 1346 } else { 1347 rc = StorageResultCode.OperationFailedInternalError; 1348 } 1349 } 1350 1351 if (rc == StorageResultCode.OperationSucceeded) { 1352 synchronized (mAsecMountSet) { 1353 if (mAsecMountSet.contains(id)) { 1354 mAsecMountSet.remove(id); 1355 } 1356 } 1357 } 1358 1359 return rc; 1360 } 1361 1362 public int mountSecureContainer(String id, String key, int ownerUid) { 1363 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1364 waitForReady(); 1365 warnOnNotMounted(); 1366 1367 synchronized (mAsecMountSet) { 1368 if (mAsecMountSet.contains(id)) { 1369 return StorageResultCode.OperationFailedStorageMounted; 1370 } 1371 } 1372 1373 int rc = StorageResultCode.OperationSucceeded; 1374 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid); 1375 try { 1376 mConnector.doCommand(cmd); 1377 } catch (NativeDaemonConnectorException e) { 1378 int code = e.getCode(); 1379 if (code != VoldResponseCode.OpFailedStorageBusy) { 1380 rc = StorageResultCode.OperationFailedInternalError; 1381 } 1382 } 1383 1384 if (rc == StorageResultCode.OperationSucceeded) { 1385 synchronized (mAsecMountSet) { 1386 mAsecMountSet.add(id); 1387 } 1388 } 1389 return rc; 1390 } 1391 1392 public int unmountSecureContainer(String id, boolean force) { 1393 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1394 waitForReady(); 1395 warnOnNotMounted(); 1396 1397 synchronized (mAsecMountSet) { 1398 if (!mAsecMountSet.contains(id)) { 1399 return StorageResultCode.OperationFailedStorageNotMounted; 1400 } 1401 } 1402 1403 /* 1404 * Force a GC to make sure AssetManagers in other threads of the 1405 * system_server are cleaned up. We have to do this since AssetManager 1406 * instances are kept as a WeakReference and it's possible we have files 1407 * open on the external storage. 1408 */ 1409 Runtime.getRuntime().gc(); 1410 1411 int rc = StorageResultCode.OperationSucceeded; 1412 String cmd = String.format("asec unmount %s%s", id, (force ? " force" : "")); 1413 try { 1414 mConnector.doCommand(cmd); 1415 } catch (NativeDaemonConnectorException e) { 1416 int code = e.getCode(); 1417 if (code == VoldResponseCode.OpFailedStorageBusy) { 1418 rc = StorageResultCode.OperationFailedStorageBusy; 1419 } else { 1420 rc = StorageResultCode.OperationFailedInternalError; 1421 } 1422 } 1423 1424 if (rc == StorageResultCode.OperationSucceeded) { 1425 synchronized (mAsecMountSet) { 1426 mAsecMountSet.remove(id); 1427 } 1428 } 1429 return rc; 1430 } 1431 1432 public boolean isSecureContainerMounted(String id) { 1433 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1434 waitForReady(); 1435 warnOnNotMounted(); 1436 1437 synchronized (mAsecMountSet) { 1438 return mAsecMountSet.contains(id); 1439 } 1440 } 1441 1442 public int renameSecureContainer(String oldId, String newId) { 1443 validatePermission(android.Manifest.permission.ASEC_RENAME); 1444 waitForReady(); 1445 warnOnNotMounted(); 1446 1447 synchronized (mAsecMountSet) { 1448 /* 1449 * Because a mounted container has active internal state which cannot be 1450 * changed while active, we must ensure both ids are not currently mounted. 1451 */ 1452 if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) { 1453 return StorageResultCode.OperationFailedStorageMounted; 1454 } 1455 } 1456 1457 int rc = StorageResultCode.OperationSucceeded; 1458 String cmd = String.format("asec rename %s %s", oldId, newId); 1459 try { 1460 mConnector.doCommand(cmd); 1461 } catch (NativeDaemonConnectorException e) { 1462 rc = StorageResultCode.OperationFailedInternalError; 1463 } 1464 1465 return rc; 1466 } 1467 1468 public String getSecureContainerPath(String id) { 1469 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1470 waitForReady(); 1471 warnOnNotMounted(); 1472 1473 try { 1474 ArrayList<String> rsp = mConnector.doCommand(String.format("asec path %s", id)); 1475 String []tok = rsp.get(0).split(" "); 1476 int code = Integer.parseInt(tok[0]); 1477 if (code != VoldResponseCode.AsecPathResult) { 1478 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1479 } 1480 return tok[1]; 1481 } catch (NativeDaemonConnectorException e) { 1482 int code = e.getCode(); 1483 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1484 throw new IllegalArgumentException(String.format("Container '%s' not found", id)); 1485 } else { 1486 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1487 } 1488 } 1489 } 1490 1491 public void finishMediaUpdate() { 1492 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); 1493 } 1494 1495 private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) { 1496 if (callerUid == android.os.Process.SYSTEM_UID) { 1497 return true; 1498 } 1499 1500 if (packageName == null) { 1501 return false; 1502 } 1503 1504 final int packageUid = mPms.getPackageUid(packageName); 1505 1506 if (DEBUG_OBB) { 1507 Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + 1508 packageUid + ", callerUid = " + callerUid); 1509 } 1510 1511 return callerUid == packageUid; 1512 } 1513 1514 public String getMountedObbPath(String filename) { 1515 if (filename == null) { 1516 throw new IllegalArgumentException("filename cannot be null"); 1517 } 1518 1519 waitForReady(); 1520 warnOnNotMounted(); 1521 1522 try { 1523 ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename)); 1524 String []tok = rsp.get(0).split(" "); 1525 int code = Integer.parseInt(tok[0]); 1526 if (code != VoldResponseCode.AsecPathResult) { 1527 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1528 } 1529 return tok[1]; 1530 } catch (NativeDaemonConnectorException e) { 1531 int code = e.getCode(); 1532 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1533 return null; 1534 } else { 1535 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1536 } 1537 } 1538 } 1539 1540 public boolean isObbMounted(String filename) { 1541 if (filename == null) { 1542 throw new IllegalArgumentException("filename cannot be null"); 1543 } 1544 1545 synchronized (mObbMounts) { 1546 return mObbPathToStateMap.containsKey(filename); 1547 } 1548 } 1549 1550 public void mountObb(String filename, String key, IObbActionListener token, int nonce) 1551 throws RemoteException { 1552 if (filename == null) { 1553 throw new IllegalArgumentException("filename cannot be null"); 1554 } 1555 1556 if (token == null) { 1557 throw new IllegalArgumentException("token cannot be null"); 1558 } 1559 1560 final int callerUid = Binder.getCallingUid(); 1561 final ObbState obbState = new ObbState(filename, callerUid, token, nonce); 1562 final ObbAction action = new MountObbAction(obbState, key); 1563 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 1564 1565 if (DEBUG_OBB) 1566 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 1567 } 1568 1569 public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce) 1570 throws RemoteException { 1571 if (filename == null) { 1572 throw new IllegalArgumentException("filename cannot be null"); 1573 } 1574 1575 final int callerUid = Binder.getCallingUid(); 1576 final ObbState obbState = new ObbState(filename, callerUid, token, nonce); 1577 final ObbAction action = new UnmountObbAction(obbState, force); 1578 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 1579 1580 if (DEBUG_OBB) 1581 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 1582 } 1583 1584 private void addObbStateLocked(ObbState obbState) throws RemoteException { 1585 final IBinder binder = obbState.getBinder(); 1586 List<ObbState> obbStates = mObbMounts.get(binder); 1587 1588 if (obbStates == null) { 1589 obbStates = new ArrayList<ObbState>(); 1590 mObbMounts.put(binder, obbStates); 1591 } else { 1592 for (final ObbState o : obbStates) { 1593 if (o.filename.equals(obbState.filename)) { 1594 throw new IllegalStateException("Attempt to add ObbState twice. " 1595 + "This indicates an error in the MountService logic."); 1596 } 1597 } 1598 } 1599 1600 obbStates.add(obbState); 1601 try { 1602 obbState.link(); 1603 } catch (RemoteException e) { 1604 /* 1605 * The binder died before we could link it, so clean up our state 1606 * and return failure. 1607 */ 1608 obbStates.remove(obbState); 1609 if (obbStates.isEmpty()) { 1610 mObbMounts.remove(binder); 1611 } 1612 1613 // Rethrow the error so mountObb can get it 1614 throw e; 1615 } 1616 1617 mObbPathToStateMap.put(obbState.filename, obbState); 1618 } 1619 1620 private void removeObbStateLocked(ObbState obbState) { 1621 final IBinder binder = obbState.getBinder(); 1622 final List<ObbState> obbStates = mObbMounts.get(binder); 1623 if (obbStates != null) { 1624 if (obbStates.remove(obbState)) { 1625 obbState.unlink(); 1626 } 1627 if (obbStates.isEmpty()) { 1628 mObbMounts.remove(binder); 1629 } 1630 } 1631 1632 mObbPathToStateMap.remove(obbState.filename); 1633 } 1634 1635 private class ObbActionHandler extends Handler { 1636 private boolean mBound = false; 1637 private final List<ObbAction> mActions = new LinkedList<ObbAction>(); 1638 1639 ObbActionHandler(Looper l) { 1640 super(l); 1641 } 1642 1643 @Override 1644 public void handleMessage(Message msg) { 1645 switch (msg.what) { 1646 case OBB_RUN_ACTION: { 1647 final ObbAction action = (ObbAction) msg.obj; 1648 1649 if (DEBUG_OBB) 1650 Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString()); 1651 1652 // If a bind was already initiated we don't really 1653 // need to do anything. The pending install 1654 // will be processed later on. 1655 if (!mBound) { 1656 // If this is the only one pending we might 1657 // have to bind to the service again. 1658 if (!connectToService()) { 1659 Slog.e(TAG, "Failed to bind to media container service"); 1660 action.handleError(); 1661 return; 1662 } 1663 } 1664 1665 mActions.add(action); 1666 break; 1667 } 1668 case OBB_MCS_BOUND: { 1669 if (DEBUG_OBB) 1670 Slog.i(TAG, "OBB_MCS_BOUND"); 1671 if (msg.obj != null) { 1672 mContainerService = (IMediaContainerService) msg.obj; 1673 } 1674 if (mContainerService == null) { 1675 // Something seriously wrong. Bail out 1676 Slog.e(TAG, "Cannot bind to media container service"); 1677 for (ObbAction action : mActions) { 1678 // Indicate service bind error 1679 action.handleError(); 1680 } 1681 mActions.clear(); 1682 } else if (mActions.size() > 0) { 1683 final ObbAction action = mActions.get(0); 1684 if (action != null) { 1685 action.execute(this); 1686 } 1687 } else { 1688 // Should never happen ideally. 1689 Slog.w(TAG, "Empty queue"); 1690 } 1691 break; 1692 } 1693 case OBB_MCS_RECONNECT: { 1694 if (DEBUG_OBB) 1695 Slog.i(TAG, "OBB_MCS_RECONNECT"); 1696 if (mActions.size() > 0) { 1697 if (mBound) { 1698 disconnectService(); 1699 } 1700 if (!connectToService()) { 1701 Slog.e(TAG, "Failed to bind to media container service"); 1702 for (ObbAction action : mActions) { 1703 // Indicate service bind error 1704 action.handleError(); 1705 } 1706 mActions.clear(); 1707 } 1708 } 1709 break; 1710 } 1711 case OBB_MCS_UNBIND: { 1712 if (DEBUG_OBB) 1713 Slog.i(TAG, "OBB_MCS_UNBIND"); 1714 1715 // Delete pending install 1716 if (mActions.size() > 0) { 1717 mActions.remove(0); 1718 } 1719 if (mActions.size() == 0) { 1720 if (mBound) { 1721 disconnectService(); 1722 } 1723 } else { 1724 // There are more pending requests in queue. 1725 // Just post MCS_BOUND message to trigger processing 1726 // of next pending install. 1727 mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND); 1728 } 1729 break; 1730 } 1731 case OBB_FLUSH_MOUNT_STATE: { 1732 final String path = (String) msg.obj; 1733 1734 if (DEBUG_OBB) 1735 Slog.i(TAG, "Flushing all OBB state for path " + path); 1736 1737 synchronized (mObbMounts) { 1738 final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>(); 1739 1740 final Iterator<Entry<String, ObbState>> i = 1741 mObbPathToStateMap.entrySet().iterator(); 1742 while (i.hasNext()) { 1743 final Entry<String, ObbState> obbEntry = i.next(); 1744 1745 /* 1746 * If this entry's source file is in the volume path 1747 * that got unmounted, remove it because it's no 1748 * longer valid. 1749 */ 1750 if (obbEntry.getKey().startsWith(path)) { 1751 obbStatesToRemove.add(obbEntry.getValue()); 1752 } 1753 } 1754 1755 for (final ObbState obbState : obbStatesToRemove) { 1756 if (DEBUG_OBB) 1757 Slog.i(TAG, "Removing state for " + obbState.filename); 1758 1759 removeObbStateLocked(obbState); 1760 1761 try { 1762 obbState.token.onObbResult(obbState.filename, obbState.nonce, 1763 OnObbStateChangeListener.UNMOUNTED); 1764 } catch (RemoteException e) { 1765 Slog.i(TAG, "Couldn't send unmount notification for OBB: " 1766 + obbState.filename); 1767 } 1768 } 1769 } 1770 break; 1771 } 1772 } 1773 } 1774 1775 private boolean connectToService() { 1776 if (DEBUG_OBB) 1777 Slog.i(TAG, "Trying to bind to DefaultContainerService"); 1778 1779 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); 1780 if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) { 1781 mBound = true; 1782 return true; 1783 } 1784 return false; 1785 } 1786 1787 private void disconnectService() { 1788 mContainerService = null; 1789 mBound = false; 1790 mContext.unbindService(mDefContainerConn); 1791 } 1792 } 1793 1794 abstract class ObbAction { 1795 private static final int MAX_RETRIES = 3; 1796 private int mRetries; 1797 1798 ObbState mObbState; 1799 1800 ObbAction(ObbState obbState) { 1801 mObbState = obbState; 1802 } 1803 1804 public void execute(ObbActionHandler handler) { 1805 try { 1806 if (DEBUG_OBB) 1807 Slog.i(TAG, "Starting to execute action: " + this.toString()); 1808 mRetries++; 1809 if (mRetries > MAX_RETRIES) { 1810 Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); 1811 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 1812 handleError(); 1813 return; 1814 } else { 1815 handleExecute(); 1816 if (DEBUG_OBB) 1817 Slog.i(TAG, "Posting install MCS_UNBIND"); 1818 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 1819 } 1820 } catch (RemoteException e) { 1821 if (DEBUG_OBB) 1822 Slog.i(TAG, "Posting install MCS_RECONNECT"); 1823 mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT); 1824 } catch (Exception e) { 1825 if (DEBUG_OBB) 1826 Slog.d(TAG, "Error handling OBB action", e); 1827 handleError(); 1828 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 1829 } 1830 } 1831 1832 abstract void handleExecute() throws RemoteException, IOException; 1833 abstract void handleError(); 1834 1835 protected ObbInfo getObbInfo() throws IOException { 1836 ObbInfo obbInfo; 1837 try { 1838 obbInfo = mContainerService.getObbInfo(mObbState.filename); 1839 } catch (RemoteException e) { 1840 Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for " 1841 + mObbState.filename); 1842 obbInfo = null; 1843 } 1844 if (obbInfo == null) { 1845 throw new IOException("Couldn't read OBB file: " + mObbState.filename); 1846 } 1847 return obbInfo; 1848 } 1849 1850 protected void sendNewStatusOrIgnore(int status) { 1851 if (mObbState == null || mObbState.token == null) { 1852 return; 1853 } 1854 1855 try { 1856 mObbState.token.onObbResult(mObbState.filename, mObbState.nonce, status); 1857 } catch (RemoteException e) { 1858 Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); 1859 } 1860 } 1861 } 1862 1863 class MountObbAction extends ObbAction { 1864 private String mKey; 1865 1866 MountObbAction(ObbState obbState, String key) { 1867 super(obbState); 1868 mKey = key; 1869 } 1870 1871 public void handleExecute() throws IOException, RemoteException { 1872 waitForReady(); 1873 warnOnNotMounted(); 1874 1875 final ObbInfo obbInfo = getObbInfo(); 1876 1877 if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) { 1878 Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename 1879 + " which is owned by " + obbInfo.packageName); 1880 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 1881 return; 1882 } 1883 1884 final boolean isMounted; 1885 synchronized (mObbMounts) { 1886 isMounted = mObbPathToStateMap.containsKey(obbInfo.filename); 1887 } 1888 if (isMounted) { 1889 Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename); 1890 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED); 1891 return; 1892 } 1893 1894 /* 1895 * The filename passed in might not be the canonical name, so just 1896 * set the filename to the canonicalized version. 1897 */ 1898 mObbState.filename = obbInfo.filename; 1899 1900 final String hashedKey; 1901 if (mKey == null) { 1902 hashedKey = "none"; 1903 } else { 1904 final MessageDigest md; 1905 try { 1906 md = MessageDigest.getInstance("MD5"); 1907 } catch (NoSuchAlgorithmException e) { 1908 Slog.e(TAG, "Could not load MD5 algorithm", e); 1909 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 1910 return; 1911 } 1912 1913 hashedKey = HexDump.toHexString(md.digest(mKey.getBytes())); 1914 } 1915 1916 int rc = StorageResultCode.OperationSucceeded; 1917 String cmd = String.format("obb mount %s %s %d", mObbState.filename, hashedKey, 1918 mObbState.callerUid); 1919 try { 1920 mConnector.doCommand(cmd); 1921 } catch (NativeDaemonConnectorException e) { 1922 int code = e.getCode(); 1923 if (code != VoldResponseCode.OpFailedStorageBusy) { 1924 rc = StorageResultCode.OperationFailedInternalError; 1925 } 1926 } 1927 1928 if (rc == StorageResultCode.OperationSucceeded) { 1929 if (DEBUG_OBB) 1930 Slog.d(TAG, "Successfully mounted OBB " + mObbState.filename); 1931 1932 synchronized (mObbMounts) { 1933 addObbStateLocked(mObbState); 1934 } 1935 1936 sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED); 1937 } else { 1938 Slog.e(TAG, "Couldn't mount OBB file: " + rc); 1939 1940 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT); 1941 } 1942 } 1943 1944 public void handleError() { 1945 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 1946 } 1947 1948 @Override 1949 public String toString() { 1950 StringBuilder sb = new StringBuilder(); 1951 sb.append("MountObbAction{"); 1952 sb.append("filename="); 1953 sb.append(mObbState.filename); 1954 sb.append(",callerUid="); 1955 sb.append(mObbState.callerUid); 1956 sb.append(",token="); 1957 sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL"); 1958 sb.append(",binder="); 1959 sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null"); 1960 sb.append('}'); 1961 return sb.toString(); 1962 } 1963 } 1964 1965 class UnmountObbAction extends ObbAction { 1966 private boolean mForceUnmount; 1967 1968 UnmountObbAction(ObbState obbState, boolean force) { 1969 super(obbState); 1970 mForceUnmount = force; 1971 } 1972 1973 public void handleExecute() throws IOException { 1974 waitForReady(); 1975 warnOnNotMounted(); 1976 1977 final ObbInfo obbInfo = getObbInfo(); 1978 1979 final ObbState obbState; 1980 synchronized (mObbMounts) { 1981 obbState = mObbPathToStateMap.get(obbInfo.filename); 1982 } 1983 1984 if (obbState == null) { 1985 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED); 1986 return; 1987 } 1988 1989 if (obbState.callerUid != mObbState.callerUid) { 1990 Slog.w(TAG, "Permission denied attempting to unmount OBB " + obbInfo.filename 1991 + " (owned by " + obbInfo.packageName + ")"); 1992 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 1993 return; 1994 } 1995 1996 mObbState.filename = obbInfo.filename; 1997 1998 int rc = StorageResultCode.OperationSucceeded; 1999 String cmd = String.format("obb unmount %s%s", mObbState.filename, 2000 (mForceUnmount ? " force" : "")); 2001 try { 2002 mConnector.doCommand(cmd); 2003 } catch (NativeDaemonConnectorException e) { 2004 int code = e.getCode(); 2005 if (code == VoldResponseCode.OpFailedStorageBusy) { 2006 rc = StorageResultCode.OperationFailedStorageBusy; 2007 } else if (code == VoldResponseCode.OpFailedStorageNotFound) { 2008 // If it's not mounted then we've already won. 2009 rc = StorageResultCode.OperationSucceeded; 2010 } else { 2011 rc = StorageResultCode.OperationFailedInternalError; 2012 } 2013 } 2014 2015 if (rc == StorageResultCode.OperationSucceeded) { 2016 synchronized (mObbMounts) { 2017 removeObbStateLocked(obbState); 2018 } 2019 2020 sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED); 2021 } else { 2022 Slog.w(TAG, "Could not mount OBB: " + mObbState.filename); 2023 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT); 2024 } 2025 } 2026 2027 public void handleError() { 2028 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2029 } 2030 2031 @Override 2032 public String toString() { 2033 StringBuilder sb = new StringBuilder(); 2034 sb.append("UnmountObbAction{"); 2035 sb.append("filename="); 2036 sb.append(mObbState.filename != null ? mObbState.filename : "null"); 2037 sb.append(",force="); 2038 sb.append(mForceUnmount); 2039 sb.append(",callerUid="); 2040 sb.append(mObbState.callerUid); 2041 sb.append(",token="); 2042 sb.append(mObbState.token != null ? mObbState.token.toString() : "null"); 2043 sb.append(",binder="); 2044 sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null"); 2045 sb.append('}'); 2046 return sb.toString(); 2047 } 2048 } 2049 2050 @Override 2051 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2052 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { 2053 pw.println("Permission Denial: can't dump ActivityManager from from pid=" 2054 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 2055 + " without permission " + android.Manifest.permission.DUMP); 2056 return; 2057 } 2058 2059 synchronized (mObbMounts) { 2060 pw.println(" mObbMounts:"); 2061 2062 final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator(); 2063 while (binders.hasNext()) { 2064 Entry<IBinder, List<ObbState>> e = binders.next(); 2065 pw.print(" Key="); pw.println(e.getKey().toString()); 2066 final List<ObbState> obbStates = e.getValue(); 2067 for (final ObbState obbState : obbStates) { 2068 pw.print(" "); pw.println(obbState.toString()); 2069 } 2070 } 2071 2072 pw.println(""); 2073 pw.println(" mObbPathToStateMap:"); 2074 final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator(); 2075 while (maps.hasNext()) { 2076 final Entry<String, ObbState> e = maps.next(); 2077 pw.print(" "); pw.print(e.getKey()); 2078 pw.print(" -> "); pw.println(e.getValue().toString()); 2079 } 2080 } 2081 } 2082} 2083 2084