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