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