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