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