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