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