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