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