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