MountService.java revision 02c8730c1bf19daf48bec8c6995df676a00a73b1
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.server.am.ActivityManagerService; 20 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.pm.PackageManager; 26import android.content.res.ObbInfo; 27import android.content.res.ObbScanner; 28import android.net.Uri; 29import android.os.storage.IMountService; 30import android.os.storage.IMountServiceListener; 31import android.os.storage.IMountShutdownObserver; 32import android.os.storage.StorageResultCode; 33import android.os.Binder; 34import android.os.Handler; 35import android.os.HandlerThread; 36import android.os.Looper; 37import android.os.Message; 38import android.os.RemoteException; 39import android.os.IBinder; 40import android.os.Environment; 41import android.os.ServiceManager; 42import android.os.SystemClock; 43import android.os.SystemProperties; 44import android.util.Slog; 45import java.util.ArrayList; 46import java.util.HashSet; 47 48/** 49 * MountService implements back-end services for platform storage 50 * management. 51 * @hide - Applications should use android.os.storage.StorageManager 52 * to access the MountService. 53 */ 54class MountService extends IMountService.Stub 55 implements INativeDaemonConnectorCallbacks { 56 private static final boolean LOCAL_LOGD = false; 57 private static final boolean DEBUG_UNMOUNT = false; 58 private static final boolean DEBUG_EVENTS = false; 59 private static final boolean DEBUG_OBB = true; 60 61 private static final String TAG = "MountService"; 62 63 /* 64 * Internal vold volume state constants 65 */ 66 class VolumeState { 67 public static final int Init = -1; 68 public static final int NoMedia = 0; 69 public static final int Idle = 1; 70 public static final int Pending = 2; 71 public static final int Checking = 3; 72 public static final int Mounted = 4; 73 public static final int Unmounting = 5; 74 public static final int Formatting = 6; 75 public static final int Shared = 7; 76 public static final int SharedMnt = 8; 77 } 78 79 /* 80 * Internal vold response code constants 81 */ 82 class VoldResponseCode { 83 /* 84 * 100 series - Requestion action was initiated; expect another reply 85 * before proceeding with a new command. 86 */ 87 public static final int VolumeListResult = 110; 88 public static final int AsecListResult = 111; 89 public static final int StorageUsersListResult = 112; 90 91 /* 92 * 200 series - Requestion action has been successfully completed. 93 */ 94 public static final int ShareStatusResult = 210; 95 public static final int AsecPathResult = 211; 96 public static final int ShareEnabledResult = 212; 97 98 /* 99 * 400 series - Command was accepted, but the requested action 100 * did not take place. 101 */ 102 public static final int OpFailedNoMedia = 401; 103 public static final int OpFailedMediaBlank = 402; 104 public static final int OpFailedMediaCorrupt = 403; 105 public static final int OpFailedVolNotMounted = 404; 106 public static final int OpFailedStorageBusy = 405; 107 public static final int OpFailedStorageNotFound = 406; 108 109 /* 110 * 600 series - Unsolicited broadcasts. 111 */ 112 public static final int VolumeStateChange = 605; 113 public static final int ShareAvailabilityChange = 620; 114 public static final int VolumeDiskInserted = 630; 115 public static final int VolumeDiskRemoved = 631; 116 public static final int VolumeBadRemoval = 632; 117 } 118 119 private Context mContext; 120 private NativeDaemonConnector mConnector; 121 private String mLegacyState = Environment.MEDIA_REMOVED; 122 private PackageManagerService mPms; 123 private boolean mUmsEnabling; 124 // Used as a lock for methods that register/unregister listeners. 125 final private ArrayList<MountServiceBinderListener> mListeners = 126 new ArrayList<MountServiceBinderListener>(); 127 private boolean mBooted = false; 128 private boolean mReady = false; 129 private boolean mSendUmsConnectedOnBoot = false; 130 131 /** 132 * Private hash of currently mounted secure containers. 133 * Used as a lock in methods to manipulate secure containers. 134 */ 135 final private HashSet<String> mAsecMountSet = new HashSet<String>(); 136 137 /** 138 * Private hash of currently mounted filesystem images. 139 */ 140 final private HashSet<String> mObbMountSet = new HashSet<String>(); 141 142 // Handler messages 143 private static final int H_UNMOUNT_PM_UPDATE = 1; 144 private static final int H_UNMOUNT_PM_DONE = 2; 145 private static final int H_UNMOUNT_MS = 3; 146 private static final int RETRY_UNMOUNT_DELAY = 30; // in ms 147 private static final int MAX_UNMOUNT_RETRIES = 4; 148 149 class UnmountCallBack { 150 String path; 151 int retries; 152 boolean force; 153 154 UnmountCallBack(String path, boolean force) { 155 retries = 0; 156 this.path = path; 157 this.force = force; 158 } 159 160 void handleFinished() { 161 if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path); 162 doUnmountVolume(path, true); 163 } 164 } 165 166 class UmsEnableCallBack extends UnmountCallBack { 167 String method; 168 169 UmsEnableCallBack(String path, String method, boolean force) { 170 super(path, force); 171 this.method = method; 172 } 173 174 @Override 175 void handleFinished() { 176 super.handleFinished(); 177 doShareUnshareVolume(path, method, true); 178 } 179 } 180 181 class ShutdownCallBack extends UnmountCallBack { 182 IMountShutdownObserver observer; 183 ShutdownCallBack(String path, IMountShutdownObserver observer) { 184 super(path, true); 185 this.observer = observer; 186 } 187 188 @Override 189 void handleFinished() { 190 int ret = doUnmountVolume(path, true); 191 if (observer != null) { 192 try { 193 observer.onShutDownComplete(ret); 194 } catch (RemoteException e) { 195 Slog.w(TAG, "RemoteException when shutting down"); 196 } 197 } 198 } 199 } 200 201 class MountServiceHandler extends Handler { 202 ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>(); 203 boolean mUpdatingStatus = false; 204 205 MountServiceHandler(Looper l) { 206 super(l); 207 } 208 209 public void handleMessage(Message msg) { 210 switch (msg.what) { 211 case H_UNMOUNT_PM_UPDATE: { 212 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE"); 213 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 214 mForceUnmounts.add(ucb); 215 if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus); 216 // Register only if needed. 217 if (!mUpdatingStatus) { 218 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager"); 219 mUpdatingStatus = true; 220 mPms.updateExternalMediaStatus(false, true); 221 } 222 break; 223 } 224 case H_UNMOUNT_PM_DONE: { 225 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE"); 226 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests"); 227 mUpdatingStatus = false; 228 int size = mForceUnmounts.size(); 229 int sizeArr[] = new int[size]; 230 int sizeArrN = 0; 231 // Kill processes holding references first 232 ActivityManagerService ams = (ActivityManagerService) 233 ServiceManager.getService("activity"); 234 for (int i = 0; i < size; i++) { 235 UnmountCallBack ucb = mForceUnmounts.get(i); 236 String path = ucb.path; 237 boolean done = false; 238 if (!ucb.force) { 239 done = true; 240 } else { 241 int pids[] = getStorageUsers(path); 242 if (pids == null || pids.length == 0) { 243 done = true; 244 } else { 245 // Eliminate system process here? 246 ams.killPids(pids, "unmount media"); 247 // Confirm if file references have been freed. 248 pids = getStorageUsers(path); 249 if (pids == null || pids.length == 0) { 250 done = true; 251 } 252 } 253 } 254 if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) { 255 // Retry again 256 Slog.i(TAG, "Retrying to kill storage users again"); 257 mHandler.sendMessageDelayed( 258 mHandler.obtainMessage(H_UNMOUNT_PM_DONE, 259 ucb.retries++), 260 RETRY_UNMOUNT_DELAY); 261 } else { 262 if (ucb.retries >= MAX_UNMOUNT_RETRIES) { 263 Slog.i(TAG, "Failed to unmount media inspite of " + 264 MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now"); 265 } 266 sizeArr[sizeArrN++] = i; 267 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS, 268 ucb)); 269 } 270 } 271 // Remove already processed elements from list. 272 for (int i = (sizeArrN-1); i >= 0; i--) { 273 mForceUnmounts.remove(sizeArr[i]); 274 } 275 break; 276 } 277 case H_UNMOUNT_MS : { 278 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS"); 279 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 280 ucb.handleFinished(); 281 break; 282 } 283 } 284 } 285 }; 286 final private HandlerThread mHandlerThread; 287 final private Handler mHandler; 288 289 private void waitForReady() { 290 while (mReady == false) { 291 for (int retries = 5; retries > 0; retries--) { 292 if (mReady) { 293 return; 294 } 295 SystemClock.sleep(1000); 296 } 297 Slog.w(TAG, "Waiting too long for mReady!"); 298 } 299 } 300 301 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 302 public void onReceive(Context context, Intent intent) { 303 String action = intent.getAction(); 304 305 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 306 mBooted = true; 307 308 /* 309 * In the simulator, we need to broadcast a volume mounted event 310 * to make the media scanner run. 311 */ 312 if ("simulator".equals(SystemProperties.get("ro.product.device"))) { 313 notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted); 314 return; 315 } 316 new Thread() { 317 public void run() { 318 try { 319 String path = Environment.getExternalStorageDirectory().getPath(); 320 String state = getVolumeState(path); 321 322 if (state.equals(Environment.MEDIA_UNMOUNTED)) { 323 int rc = doMountVolume(path); 324 if (rc != StorageResultCode.OperationSucceeded) { 325 Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc)); 326 } 327 } else if (state.equals(Environment.MEDIA_SHARED)) { 328 /* 329 * Bootstrap UMS enabled state since vold indicates 330 * the volume is shared (runtime restart while ums enabled) 331 */ 332 notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared); 333 } 334 335 /* 336 * If UMS was connected on boot, send the connected event 337 * now that we're up. 338 */ 339 if (mSendUmsConnectedOnBoot) { 340 sendUmsIntent(true); 341 mSendUmsConnectedOnBoot = false; 342 } 343 } catch (Exception ex) { 344 Slog.e(TAG, "Boot-time mount exception", ex); 345 } 346 } 347 }.start(); 348 } 349 } 350 }; 351 352 private final class MountServiceBinderListener implements IBinder.DeathRecipient { 353 final IMountServiceListener mListener; 354 355 MountServiceBinderListener(IMountServiceListener listener) { 356 mListener = listener; 357 358 } 359 360 public void binderDied() { 361 if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!"); 362 synchronized(mListeners) { 363 mListeners.remove(this); 364 mListener.asBinder().unlinkToDeath(this, 0); 365 } 366 } 367 } 368 369 private void doShareUnshareVolume(String path, String method, boolean enable) { 370 // TODO: Add support for multiple share methods 371 if (!method.equals("ums")) { 372 throw new IllegalArgumentException(String.format("Method %s not supported", method)); 373 } 374 375 try { 376 mConnector.doCommand(String.format( 377 "volume %sshare %s %s", (enable ? "" : "un"), path, method)); 378 } catch (NativeDaemonConnectorException e) { 379 Slog.e(TAG, "Failed to share/unshare", e); 380 } 381 } 382 383 private void updatePublicVolumeState(String path, String state) { 384 if (!path.equals(Environment.getExternalStorageDirectory().getPath())) { 385 Slog.w(TAG, "Multiple volumes not currently supported"); 386 return; 387 } 388 389 if (mLegacyState.equals(state)) { 390 Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state)); 391 return; 392 } 393 // Update state on PackageManager 394 if (Environment.MEDIA_UNMOUNTED.equals(state)) { 395 mPms.updateExternalMediaStatus(false, false); 396 } else if (Environment.MEDIA_MOUNTED.equals(state)) { 397 mPms.updateExternalMediaStatus(true, false); 398 } 399 String oldState = mLegacyState; 400 mLegacyState = state; 401 402 synchronized (mListeners) { 403 for (int i = mListeners.size() -1; i >= 0; i--) { 404 MountServiceBinderListener bl = mListeners.get(i); 405 try { 406 bl.mListener.onStorageStateChanged(path, oldState, state); 407 } catch (RemoteException rex) { 408 Slog.e(TAG, "Listener dead"); 409 mListeners.remove(i); 410 } catch (Exception ex) { 411 Slog.e(TAG, "Listener failed", ex); 412 } 413 } 414 } 415 } 416 417 /** 418 * 419 * Callback from NativeDaemonConnector 420 */ 421 public void onDaemonConnected() { 422 /* 423 * Since we'll be calling back into the NativeDaemonConnector, 424 * we need to do our work in a new thread. 425 */ 426 new Thread() { 427 public void run() { 428 /** 429 * Determine media state and UMS detection status 430 */ 431 String path = Environment.getExternalStorageDirectory().getPath(); 432 String state = Environment.MEDIA_REMOVED; 433 434 try { 435 String[] vols = mConnector.doListCommand( 436 "volume list", VoldResponseCode.VolumeListResult); 437 for (String volstr : vols) { 438 String[] tok = volstr.split(" "); 439 // FMT: <label> <mountpoint> <state> 440 if (!tok[1].equals(path)) { 441 Slog.w(TAG, String.format( 442 "Skipping unknown volume '%s'",tok[1])); 443 continue; 444 } 445 int st = Integer.parseInt(tok[2]); 446 if (st == VolumeState.NoMedia) { 447 state = Environment.MEDIA_REMOVED; 448 } else if (st == VolumeState.Idle) { 449 state = Environment.MEDIA_UNMOUNTED; 450 } else if (st == VolumeState.Mounted) { 451 state = Environment.MEDIA_MOUNTED; 452 Slog.i(TAG, "Media already mounted on daemon connection"); 453 } else if (st == VolumeState.Shared) { 454 state = Environment.MEDIA_SHARED; 455 Slog.i(TAG, "Media shared on daemon connection"); 456 } else { 457 throw new Exception(String.format("Unexpected state %d", st)); 458 } 459 } 460 if (state != null) { 461 if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state); 462 updatePublicVolumeState(path, state); 463 } 464 } catch (Exception e) { 465 Slog.e(TAG, "Error processing initial volume state", e); 466 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 467 } 468 469 try { 470 boolean avail = doGetShareMethodAvailable("ums"); 471 notifyShareAvailabilityChange("ums", avail); 472 } catch (Exception ex) { 473 Slog.w(TAG, "Failed to get share availability"); 474 } 475 /* 476 * Now that we've done our initialization, release 477 * the hounds! 478 */ 479 mReady = true; 480 } 481 }.start(); 482 } 483 484 /** 485 * Callback from NativeDaemonConnector 486 */ 487 public boolean onEvent(int code, String raw, String[] cooked) { 488 Intent in = null; 489 490 if (DEBUG_EVENTS) { 491 StringBuilder builder = new StringBuilder(); 492 builder.append("onEvent::"); 493 builder.append(" raw= " + raw); 494 if (cooked != null) { 495 builder.append(" cooked = " ); 496 for (String str : cooked) { 497 builder.append(" " + str); 498 } 499 } 500 Slog.i(TAG, builder.toString()); 501 } 502 if (code == VoldResponseCode.VolumeStateChange) { 503 /* 504 * One of the volumes we're managing has changed state. 505 * Format: "NNN Volume <label> <path> state changed 506 * from <old_#> (<old_str>) to <new_#> (<new_str>)" 507 */ 508 notifyVolumeStateChange( 509 cooked[2], cooked[3], Integer.parseInt(cooked[7]), 510 Integer.parseInt(cooked[10])); 511 } else if (code == VoldResponseCode.ShareAvailabilityChange) { 512 // FMT: NNN Share method <method> now <available|unavailable> 513 boolean avail = false; 514 if (cooked[5].equals("available")) { 515 avail = true; 516 } 517 notifyShareAvailabilityChange(cooked[3], avail); 518 } else if ((code == VoldResponseCode.VolumeDiskInserted) || 519 (code == VoldResponseCode.VolumeDiskRemoved) || 520 (code == VoldResponseCode.VolumeBadRemoval)) { 521 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>) 522 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>) 523 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>) 524 final String label = cooked[2]; 525 final String path = cooked[3]; 526 int major = -1; 527 int minor = -1; 528 529 try { 530 String devComp = cooked[6].substring(1, cooked[6].length() -1); 531 String[] devTok = devComp.split(":"); 532 major = Integer.parseInt(devTok[0]); 533 minor = Integer.parseInt(devTok[1]); 534 } catch (Exception ex) { 535 Slog.e(TAG, "Failed to parse major/minor", ex); 536 } 537 538 if (code == VoldResponseCode.VolumeDiskInserted) { 539 new Thread() { 540 public void run() { 541 try { 542 int rc; 543 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 544 Slog.w(TAG, String.format("Insertion mount failed (%d)", rc)); 545 } 546 } catch (Exception ex) { 547 Slog.w(TAG, "Failed to mount media on insertion", ex); 548 } 549 } 550 }.start(); 551 } else if (code == VoldResponseCode.VolumeDiskRemoved) { 552 /* 553 * This event gets trumped if we're already in BAD_REMOVAL state 554 */ 555 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) { 556 return true; 557 } 558 /* Send the media unmounted event first */ 559 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 560 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 561 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 562 mContext.sendBroadcast(in); 563 564 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed"); 565 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 566 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path)); 567 } else if (code == VoldResponseCode.VolumeBadRemoval) { 568 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 569 /* Send the media unmounted event first */ 570 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 571 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 572 mContext.sendBroadcast(in); 573 574 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal"); 575 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL); 576 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path)); 577 } else { 578 Slog.e(TAG, String.format("Unknown code {%d}", code)); 579 } 580 } else { 581 return false; 582 } 583 584 if (in != null) { 585 mContext.sendBroadcast(in); 586 } 587 return true; 588 } 589 590 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) { 591 String vs = getVolumeState(path); 592 if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs); 593 594 Intent in = null; 595 596 if (oldState == VolumeState.Shared && newState != oldState) { 597 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent"); 598 mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_UNSHARED, 599 Uri.parse("file://" + path))); 600 } 601 602 if (newState == VolumeState.Init) { 603 } else if (newState == VolumeState.NoMedia) { 604 // NoMedia is handled via Disk Remove events 605 } else if (newState == VolumeState.Idle) { 606 /* 607 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or 608 * if we're in the process of enabling UMS 609 */ 610 if (!vs.equals( 611 Environment.MEDIA_BAD_REMOVAL) && !vs.equals( 612 Environment.MEDIA_NOFS) && !vs.equals( 613 Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) { 614 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable"); 615 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 616 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 617 } 618 } else if (newState == VolumeState.Pending) { 619 } else if (newState == VolumeState.Checking) { 620 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking"); 621 updatePublicVolumeState(path, Environment.MEDIA_CHECKING); 622 in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path)); 623 } else if (newState == VolumeState.Mounted) { 624 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted"); 625 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED); 626 in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path)); 627 in.putExtra("read-only", false); 628 } else if (newState == VolumeState.Unmounting) { 629 in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path)); 630 } else if (newState == VolumeState.Formatting) { 631 } else if (newState == VolumeState.Shared) { 632 if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted"); 633 /* Send the media unmounted event first */ 634 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 635 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 636 mContext.sendBroadcast(in); 637 638 if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared"); 639 updatePublicVolumeState(path, Environment.MEDIA_SHARED); 640 in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path)); 641 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent"); 642 } else if (newState == VolumeState.SharedMnt) { 643 Slog.e(TAG, "Live shared mounts not supported yet!"); 644 return; 645 } else { 646 Slog.e(TAG, "Unhandled VolumeState {" + newState + "}"); 647 } 648 649 if (in != null) { 650 mContext.sendBroadcast(in); 651 } 652 } 653 654 private boolean doGetShareMethodAvailable(String method) { 655 ArrayList<String> rsp; 656 try { 657 rsp = mConnector.doCommand("share status " + method); 658 } catch (NativeDaemonConnectorException ex) { 659 Slog.e(TAG, "Failed to determine whether share method " + method + " is available."); 660 return false; 661 } 662 663 for (String line : rsp) { 664 String[] tok = line.split(" "); 665 if (tok.length < 3) { 666 Slog.e(TAG, "Malformed response to share status " + method); 667 return false; 668 } 669 670 int code; 671 try { 672 code = Integer.parseInt(tok[0]); 673 } catch (NumberFormatException nfe) { 674 Slog.e(TAG, String.format("Error parsing code %s", tok[0])); 675 return false; 676 } 677 if (code == VoldResponseCode.ShareStatusResult) { 678 if (tok[2].equals("available")) 679 return true; 680 return false; 681 } else { 682 Slog.e(TAG, String.format("Unexpected response code %d", code)); 683 return false; 684 } 685 } 686 Slog.e(TAG, "Got an empty response"); 687 return false; 688 } 689 690 private int doMountVolume(String path) { 691 int rc = StorageResultCode.OperationSucceeded; 692 693 if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); 694 try { 695 mConnector.doCommand(String.format("volume mount %s", path)); 696 } catch (NativeDaemonConnectorException e) { 697 /* 698 * Mount failed for some reason 699 */ 700 Intent in = null; 701 int code = e.getCode(); 702 if (code == VoldResponseCode.OpFailedNoMedia) { 703 /* 704 * Attempt to mount but no media inserted 705 */ 706 rc = StorageResultCode.OperationFailedNoMedia; 707 } else if (code == VoldResponseCode.OpFailedMediaBlank) { 708 if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs"); 709 /* 710 * Media is blank or does not contain a supported filesystem 711 */ 712 updatePublicVolumeState(path, Environment.MEDIA_NOFS); 713 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path)); 714 rc = StorageResultCode.OperationFailedMediaBlank; 715 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 716 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt"); 717 /* 718 * Volume consistency check failed 719 */ 720 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE); 721 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path)); 722 rc = StorageResultCode.OperationFailedMediaCorrupt; 723 } else { 724 rc = StorageResultCode.OperationFailedInternalError; 725 } 726 727 /* 728 * Send broadcast intent (if required for the failure) 729 */ 730 if (in != null) { 731 mContext.sendBroadcast(in); 732 } 733 } 734 735 return rc; 736 } 737 738 /* 739 * If force is not set, we do not unmount if there are 740 * processes holding references to the volume about to be unmounted. 741 * If force is set, all the processes holding references need to be 742 * killed via the ActivityManager before actually unmounting the volume. 743 * This might even take a while and might be retried after timed delays 744 * to make sure we dont end up in an instable state and kill some core 745 * processes. 746 */ 747 private int doUnmountVolume(String path, boolean force) { 748 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) { 749 return VoldResponseCode.OpFailedVolNotMounted; 750 } 751 // Redundant probably. But no harm in updating state again. 752 mPms.updateExternalMediaStatus(false, false); 753 try { 754 mConnector.doCommand(String.format( 755 "volume unmount %s%s", path, (force ? " force" : ""))); 756 // We unmounted the volume. None of the asec containers are available now. 757 synchronized (mAsecMountSet) { 758 mAsecMountSet.clear(); 759 } 760 return StorageResultCode.OperationSucceeded; 761 } catch (NativeDaemonConnectorException e) { 762 // Don't worry about mismatch in PackageManager since the 763 // call back will handle the status changes any way. 764 int code = e.getCode(); 765 if (code == VoldResponseCode.OpFailedVolNotMounted) { 766 return StorageResultCode.OperationFailedStorageNotMounted; 767 } else if (code == VoldResponseCode.OpFailedStorageBusy) { 768 return StorageResultCode.OperationFailedStorageBusy; 769 } else { 770 return StorageResultCode.OperationFailedInternalError; 771 } 772 } 773 } 774 775 private int doFormatVolume(String path) { 776 try { 777 String cmd = String.format("volume format %s", path); 778 mConnector.doCommand(cmd); 779 return StorageResultCode.OperationSucceeded; 780 } catch (NativeDaemonConnectorException e) { 781 int code = e.getCode(); 782 if (code == VoldResponseCode.OpFailedNoMedia) { 783 return StorageResultCode.OperationFailedNoMedia; 784 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 785 return StorageResultCode.OperationFailedMediaCorrupt; 786 } else { 787 return StorageResultCode.OperationFailedInternalError; 788 } 789 } 790 } 791 792 private boolean doGetVolumeShared(String path, String method) { 793 String cmd = String.format("volume shared %s %s", path, method); 794 ArrayList<String> rsp; 795 796 try { 797 rsp = mConnector.doCommand(cmd); 798 } catch (NativeDaemonConnectorException ex) { 799 Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); 800 return false; 801 } 802 803 for (String line : rsp) { 804 String[] tok = line.split(" "); 805 if (tok.length < 3) { 806 Slog.e(TAG, "Malformed response to volume shared " + path + " " + method + " command"); 807 return false; 808 } 809 810 int code; 811 try { 812 code = Integer.parseInt(tok[0]); 813 } catch (NumberFormatException nfe) { 814 Slog.e(TAG, String.format("Error parsing code %s", tok[0])); 815 return false; 816 } 817 if (code == VoldResponseCode.ShareEnabledResult) { 818 return "enabled".equals(tok[2]); 819 } else { 820 Slog.e(TAG, String.format("Unexpected response code %d", code)); 821 return false; 822 } 823 } 824 Slog.e(TAG, "Got an empty response"); 825 return false; 826 } 827 828 private void notifyShareAvailabilityChange(String method, final boolean avail) { 829 if (!method.equals("ums")) { 830 Slog.w(TAG, "Ignoring unsupported share method {" + method + "}"); 831 return; 832 } 833 834 synchronized (mListeners) { 835 for (int i = mListeners.size() -1; i >= 0; i--) { 836 MountServiceBinderListener bl = mListeners.get(i); 837 try { 838 bl.mListener.onUsbMassStorageConnectionChanged(avail); 839 } catch (RemoteException rex) { 840 Slog.e(TAG, "Listener dead"); 841 mListeners.remove(i); 842 } catch (Exception ex) { 843 Slog.e(TAG, "Listener failed", ex); 844 } 845 } 846 } 847 848 if (mBooted == true) { 849 sendUmsIntent(avail); 850 } else { 851 mSendUmsConnectedOnBoot = avail; 852 } 853 854 final String path = Environment.getExternalStorageDirectory().getPath(); 855 if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) { 856 /* 857 * USB mass storage disconnected while enabled 858 */ 859 new Thread() { 860 public void run() { 861 try { 862 int rc; 863 Slog.w(TAG, "Disabling UMS after cable disconnect"); 864 doShareUnshareVolume(path, "ums", false); 865 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 866 Slog.e(TAG, String.format( 867 "Failed to remount {%s} on UMS enabled-disconnect (%d)", 868 path, rc)); 869 } 870 } catch (Exception ex) { 871 Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex); 872 } 873 } 874 }.start(); 875 } 876 } 877 878 private void sendUmsIntent(boolean c) { 879 mContext.sendBroadcast( 880 new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED))); 881 } 882 883 private void validatePermission(String perm) { 884 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { 885 throw new SecurityException(String.format("Requires %s permission", perm)); 886 } 887 } 888 889 /** 890 * Constructs a new MountService instance 891 * 892 * @param context Binder context for this service 893 */ 894 public MountService(Context context) { 895 mContext = context; 896 897 // XXX: This will go away soon in favor of IMountServiceObserver 898 mPms = (PackageManagerService) ServiceManager.getService("package"); 899 900 mContext.registerReceiver(mBroadcastReceiver, 901 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); 902 903 mHandlerThread = new HandlerThread("MountService"); 904 mHandlerThread.start(); 905 mHandler = new MountServiceHandler(mHandlerThread.getLooper()); 906 907 /* 908 * Vold does not run in the simulator, so pretend the connector thread 909 * ran and did its thing. 910 */ 911 if ("simulator".equals(SystemProperties.get("ro.product.device"))) { 912 mReady = true; 913 mUmsEnabling = true; 914 return; 915 } 916 917 mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector"); 918 mReady = false; 919 Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName()); 920 thread.start(); 921 } 922 923 /** 924 * Exposed API calls below here 925 */ 926 927 public void registerListener(IMountServiceListener listener) { 928 synchronized (mListeners) { 929 MountServiceBinderListener bl = new MountServiceBinderListener(listener); 930 try { 931 listener.asBinder().linkToDeath(bl, 0); 932 mListeners.add(bl); 933 } catch (RemoteException rex) { 934 Slog.e(TAG, "Failed to link to listener death"); 935 } 936 } 937 } 938 939 public void unregisterListener(IMountServiceListener listener) { 940 synchronized (mListeners) { 941 for(MountServiceBinderListener bl : mListeners) { 942 if (bl.mListener == listener) { 943 mListeners.remove(mListeners.indexOf(bl)); 944 return; 945 } 946 } 947 } 948 } 949 950 public void shutdown(final IMountShutdownObserver observer) { 951 validatePermission(android.Manifest.permission.SHUTDOWN); 952 953 Slog.i(TAG, "Shutting down"); 954 955 String path = Environment.getExternalStorageDirectory().getPath(); 956 String state = getVolumeState(path); 957 958 if (state.equals(Environment.MEDIA_SHARED)) { 959 /* 960 * If the media is currently shared, unshare it. 961 * XXX: This is still dangerous!. We should not 962 * be rebooting at *all* if UMS is enabled, since 963 * the UMS host could have dirty FAT cache entries 964 * yet to flush. 965 */ 966 setUsbMassStorageEnabled(false); 967 } else if (state.equals(Environment.MEDIA_CHECKING)) { 968 /* 969 * If the media is being checked, then we need to wait for 970 * it to complete before being able to proceed. 971 */ 972 // XXX: @hackbod - Should we disable the ANR timer here? 973 int retries = 30; 974 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) { 975 try { 976 Thread.sleep(1000); 977 } catch (InterruptedException iex) { 978 Slog.e(TAG, "Interrupted while waiting for media", iex); 979 break; 980 } 981 state = Environment.getExternalStorageState(); 982 } 983 if (retries == 0) { 984 Slog.e(TAG, "Timed out waiting for media to check"); 985 } 986 } 987 988 if (state.equals(Environment.MEDIA_MOUNTED)) { 989 // Post a unmount message. 990 ShutdownCallBack ucb = new ShutdownCallBack(path, observer); 991 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 992 } 993 } 994 995 private boolean getUmsEnabling() { 996 synchronized (mListeners) { 997 return mUmsEnabling; 998 } 999 } 1000 1001 private void setUmsEnabling(boolean enable) { 1002 synchronized (mListeners) { 1003 mUmsEnabling = true; 1004 } 1005 } 1006 1007 public boolean isUsbMassStorageConnected() { 1008 waitForReady(); 1009 1010 if (getUmsEnabling()) { 1011 return true; 1012 } 1013 return doGetShareMethodAvailable("ums"); 1014 } 1015 1016 public void setUsbMassStorageEnabled(boolean enable) { 1017 waitForReady(); 1018 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1019 1020 // TODO: Add support for multiple share methods 1021 1022 /* 1023 * If the volume is mounted and we're enabling then unmount it 1024 */ 1025 String path = Environment.getExternalStorageDirectory().getPath(); 1026 String vs = getVolumeState(path); 1027 String method = "ums"; 1028 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { 1029 // Override for isUsbMassStorageEnabled() 1030 setUmsEnabling(enable); 1031 UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true); 1032 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb)); 1033 // Clear override 1034 setUmsEnabling(false); 1035 } 1036 /* 1037 * If we disabled UMS then mount the volume 1038 */ 1039 if (!enable) { 1040 doShareUnshareVolume(path, method, enable); 1041 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) { 1042 Slog.e(TAG, "Failed to remount " + path + 1043 " after disabling share method " + method); 1044 /* 1045 * Even though the mount failed, the unshare didn't so don't indicate an error. 1046 * The mountVolume() call will have set the storage state and sent the necessary 1047 * broadcasts. 1048 */ 1049 } 1050 } 1051 } 1052 1053 public boolean isUsbMassStorageEnabled() { 1054 waitForReady(); 1055 return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums"); 1056 } 1057 1058 /** 1059 * @return state of the volume at the specified mount point 1060 */ 1061 public String getVolumeState(String mountPoint) { 1062 /* 1063 * XXX: Until we have multiple volume discovery, just hardwire 1064 * this to /sdcard 1065 */ 1066 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) { 1067 Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); 1068 throw new IllegalArgumentException(); 1069 } 1070 1071 return mLegacyState; 1072 } 1073 1074 public int mountVolume(String path) { 1075 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1076 1077 waitForReady(); 1078 return doMountVolume(path); 1079 } 1080 1081 public void unmountVolume(String path, boolean force) { 1082 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1083 waitForReady(); 1084 1085 String volState = getVolumeState(path); 1086 if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path + " force = " + force); 1087 if (Environment.MEDIA_UNMOUNTED.equals(volState) || 1088 Environment.MEDIA_REMOVED.equals(volState) || 1089 Environment.MEDIA_SHARED.equals(volState) || 1090 Environment.MEDIA_UNMOUNTABLE.equals(volState)) { 1091 // Media already unmounted or cannot be unmounted. 1092 // TODO return valid return code when adding observer call back. 1093 return; 1094 } 1095 UnmountCallBack ucb = new UnmountCallBack(path, force); 1096 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 1097 } 1098 1099 public int formatVolume(String path) { 1100 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); 1101 waitForReady(); 1102 1103 return doFormatVolume(path); 1104 } 1105 1106 public int []getStorageUsers(String path) { 1107 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1108 waitForReady(); 1109 try { 1110 String[] r = mConnector.doListCommand( 1111 String.format("storage users %s", path), 1112 VoldResponseCode.StorageUsersListResult); 1113 // FMT: <pid> <process name> 1114 int[] data = new int[r.length]; 1115 for (int i = 0; i < r.length; i++) { 1116 String []tok = r[i].split(" "); 1117 try { 1118 data[i] = Integer.parseInt(tok[0]); 1119 } catch (NumberFormatException nfe) { 1120 Slog.e(TAG, String.format("Error parsing pid %s", tok[0])); 1121 return new int[0]; 1122 } 1123 } 1124 return data; 1125 } catch (NativeDaemonConnectorException e) { 1126 Slog.e(TAG, "Failed to retrieve storage users list", e); 1127 return new int[0]; 1128 } 1129 } 1130 1131 private void warnOnNotMounted() { 1132 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 1133 Slog.w(TAG, "getSecureContainerList() called when storage not mounted"); 1134 } 1135 } 1136 1137 public String[] getSecureContainerList() { 1138 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1139 waitForReady(); 1140 warnOnNotMounted(); 1141 1142 try { 1143 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult); 1144 } catch (NativeDaemonConnectorException e) { 1145 return new String[0]; 1146 } 1147 } 1148 1149 public int createSecureContainer(String id, int sizeMb, String fstype, 1150 String key, int ownerUid) { 1151 validatePermission(android.Manifest.permission.ASEC_CREATE); 1152 waitForReady(); 1153 warnOnNotMounted(); 1154 1155 int rc = StorageResultCode.OperationSucceeded; 1156 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid); 1157 try { 1158 mConnector.doCommand(cmd); 1159 } catch (NativeDaemonConnectorException e) { 1160 rc = StorageResultCode.OperationFailedInternalError; 1161 } 1162 1163 if (rc == StorageResultCode.OperationSucceeded) { 1164 synchronized (mAsecMountSet) { 1165 mAsecMountSet.add(id); 1166 } 1167 } 1168 return rc; 1169 } 1170 1171 public int finalizeSecureContainer(String id) { 1172 validatePermission(android.Manifest.permission.ASEC_CREATE); 1173 warnOnNotMounted(); 1174 1175 int rc = StorageResultCode.OperationSucceeded; 1176 try { 1177 mConnector.doCommand(String.format("asec finalize %s", id)); 1178 /* 1179 * Finalization does a remount, so no need 1180 * to update mAsecMountSet 1181 */ 1182 } catch (NativeDaemonConnectorException e) { 1183 rc = StorageResultCode.OperationFailedInternalError; 1184 } 1185 return rc; 1186 } 1187 1188 public int destroySecureContainer(String id, boolean force) { 1189 validatePermission(android.Manifest.permission.ASEC_DESTROY); 1190 waitForReady(); 1191 warnOnNotMounted(); 1192 1193 int rc = StorageResultCode.OperationSucceeded; 1194 try { 1195 mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : ""))); 1196 } catch (NativeDaemonConnectorException e) { 1197 int code = e.getCode(); 1198 if (code == VoldResponseCode.OpFailedStorageBusy) { 1199 rc = StorageResultCode.OperationFailedStorageBusy; 1200 } else { 1201 rc = StorageResultCode.OperationFailedInternalError; 1202 } 1203 } 1204 1205 if (rc == StorageResultCode.OperationSucceeded) { 1206 synchronized (mAsecMountSet) { 1207 if (mAsecMountSet.contains(id)) { 1208 mAsecMountSet.remove(id); 1209 } 1210 } 1211 } 1212 1213 return rc; 1214 } 1215 1216 public int mountSecureContainer(String id, String key, int ownerUid) { 1217 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1218 waitForReady(); 1219 warnOnNotMounted(); 1220 1221 synchronized (mAsecMountSet) { 1222 if (mAsecMountSet.contains(id)) { 1223 return StorageResultCode.OperationFailedStorageMounted; 1224 } 1225 } 1226 1227 int rc = StorageResultCode.OperationSucceeded; 1228 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid); 1229 try { 1230 mConnector.doCommand(cmd); 1231 } catch (NativeDaemonConnectorException e) { 1232 int code = e.getCode(); 1233 if (code != VoldResponseCode.OpFailedStorageBusy) { 1234 rc = StorageResultCode.OperationFailedInternalError; 1235 } 1236 } 1237 1238 if (rc == StorageResultCode.OperationSucceeded) { 1239 synchronized (mAsecMountSet) { 1240 mAsecMountSet.add(id); 1241 } 1242 } 1243 return rc; 1244 } 1245 1246 public int unmountSecureContainer(String id, boolean force) { 1247 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1248 waitForReady(); 1249 warnOnNotMounted(); 1250 1251 synchronized (mAsecMountSet) { 1252 if (!mAsecMountSet.contains(id)) { 1253 return StorageResultCode.OperationFailedStorageNotMounted; 1254 } 1255 } 1256 1257 int rc = StorageResultCode.OperationSucceeded; 1258 String cmd = String.format("asec unmount %s%s", id, (force ? " force" : "")); 1259 try { 1260 mConnector.doCommand(cmd); 1261 } catch (NativeDaemonConnectorException e) { 1262 int code = e.getCode(); 1263 if (code == VoldResponseCode.OpFailedStorageBusy) { 1264 rc = StorageResultCode.OperationFailedStorageBusy; 1265 } else { 1266 rc = StorageResultCode.OperationFailedInternalError; 1267 } 1268 } 1269 1270 if (rc == StorageResultCode.OperationSucceeded) { 1271 synchronized (mAsecMountSet) { 1272 mAsecMountSet.remove(id); 1273 } 1274 } 1275 return rc; 1276 } 1277 1278 public boolean isSecureContainerMounted(String id) { 1279 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1280 waitForReady(); 1281 warnOnNotMounted(); 1282 1283 synchronized (mAsecMountSet) { 1284 return mAsecMountSet.contains(id); 1285 } 1286 } 1287 1288 public int renameSecureContainer(String oldId, String newId) { 1289 validatePermission(android.Manifest.permission.ASEC_RENAME); 1290 waitForReady(); 1291 warnOnNotMounted(); 1292 1293 synchronized (mAsecMountSet) { 1294 /* 1295 * Because a mounted container has active internal state which cannot be 1296 * changed while active, we must ensure both ids are not currently mounted. 1297 */ 1298 if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) { 1299 return StorageResultCode.OperationFailedStorageMounted; 1300 } 1301 } 1302 1303 int rc = StorageResultCode.OperationSucceeded; 1304 String cmd = String.format("asec rename %s %s", oldId, newId); 1305 try { 1306 mConnector.doCommand(cmd); 1307 } catch (NativeDaemonConnectorException e) { 1308 rc = StorageResultCode.OperationFailedInternalError; 1309 } 1310 1311 return rc; 1312 } 1313 1314 public String getSecureContainerPath(String id) { 1315 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1316 waitForReady(); 1317 warnOnNotMounted(); 1318 1319 try { 1320 ArrayList<String> rsp = mConnector.doCommand(String.format("asec path %s", id)); 1321 String []tok = rsp.get(0).split(" "); 1322 int code = Integer.parseInt(tok[0]); 1323 if (code != VoldResponseCode.AsecPathResult) { 1324 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1325 } 1326 return tok[1]; 1327 } catch (NativeDaemonConnectorException e) { 1328 int code = e.getCode(); 1329 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1330 throw new IllegalArgumentException(String.format("Container '%s' not found", id)); 1331 } else { 1332 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1333 } 1334 } 1335 } 1336 1337 public void finishMediaUpdate() { 1338 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); 1339 } 1340 1341 private boolean isCallerOwnerOfPackage(String packageName) { 1342 final int callerUid = Binder.getCallingUid(); 1343 return isUidOwnerOfPackage(packageName, callerUid); 1344 } 1345 1346 private boolean isUidOwnerOfPackage(String packageName, int callerUid) { 1347 if (packageName == null) { 1348 return false; 1349 } 1350 1351 final int packageUid = mPms.getPackageUid(packageName); 1352 1353 if (DEBUG_OBB) { 1354 Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + 1355 packageUid + ", callerUid = " + callerUid); 1356 } 1357 1358 return callerUid == packageUid; 1359 } 1360 1361 public String getMountedObbPath(String filename) { 1362 waitForReady(); 1363 warnOnNotMounted(); 1364 1365 // XXX replace with call to IMediaContainerService 1366 ObbInfo obbInfo = ObbScanner.getObbInfo(filename); 1367 if (!isCallerOwnerOfPackage(obbInfo.packageName)) { 1368 throw new IllegalArgumentException("Caller package does not match OBB file"); 1369 } 1370 1371 try { 1372 ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename)); 1373 String []tok = rsp.get(0).split(" "); 1374 int code = Integer.parseInt(tok[0]); 1375 if (code != VoldResponseCode.AsecPathResult) { 1376 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1377 } 1378 return tok[1]; 1379 } catch (NativeDaemonConnectorException e) { 1380 int code = e.getCode(); 1381 if (code == VoldResponseCode.OpFailedStorageNotFound) { 1382 throw new IllegalArgumentException(String.format("OBB '%s' not found", filename)); 1383 } else { 1384 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1385 } 1386 } 1387 } 1388 1389 public boolean isObbMounted(String filename) { 1390 waitForReady(); 1391 warnOnNotMounted(); 1392 1393 // XXX replace with call to IMediaContainerService 1394 ObbInfo obbInfo = ObbScanner.getObbInfo(filename); 1395 if (!isCallerOwnerOfPackage(obbInfo.packageName)) { 1396 throw new IllegalArgumentException("Caller package does not match OBB file"); 1397 } 1398 1399 synchronized (mObbMountSet) { 1400 return mObbMountSet.contains(filename); 1401 } 1402 } 1403 1404 public int mountObb(String filename, String key) { 1405 waitForReady(); 1406 warnOnNotMounted(); 1407 1408 synchronized (mObbMountSet) { 1409 if (mObbMountSet.contains(filename)) { 1410 return StorageResultCode.OperationFailedStorageMounted; 1411 } 1412 } 1413 1414 final int callerUid = Binder.getCallingUid(); 1415 1416 // XXX replace with call to IMediaContainerService 1417 ObbInfo obbInfo = ObbScanner.getObbInfo(filename); 1418 if (!isUidOwnerOfPackage(obbInfo.packageName, callerUid)) { 1419 throw new IllegalArgumentException("Caller package does not match OBB file"); 1420 } 1421 1422 if (key == null) { 1423 key = "none"; 1424 } 1425 1426 int rc = StorageResultCode.OperationSucceeded; 1427 String cmd = String.format("obb mount %s %s %d", filename, key, callerUid); 1428 try { 1429 mConnector.doCommand(cmd); 1430 } catch (NativeDaemonConnectorException e) { 1431 int code = e.getCode(); 1432 if (code != VoldResponseCode.OpFailedStorageBusy) { 1433 rc = StorageResultCode.OperationFailedInternalError; 1434 } 1435 } 1436 1437 if (rc == StorageResultCode.OperationSucceeded) { 1438 synchronized (mObbMountSet) { 1439 mObbMountSet.add(filename); 1440 } 1441 } 1442 return rc; 1443 } 1444 1445 public int unmountObb(String filename, boolean force) { 1446 waitForReady(); 1447 warnOnNotMounted(); 1448 1449 ObbInfo obbInfo = ObbScanner.getObbInfo(filename); 1450 if (!isCallerOwnerOfPackage(obbInfo.packageName)) { 1451 throw new IllegalArgumentException("Caller package does not match OBB file"); 1452 } 1453 1454 synchronized (mObbMountSet) { 1455 if (!mObbMountSet.contains(filename)) { 1456 return StorageResultCode.OperationFailedStorageNotMounted; 1457 } 1458 } 1459 1460 int rc = StorageResultCode.OperationSucceeded; 1461 String cmd = String.format("obb unmount %s%s", filename, (force ? " force" : "")); 1462 try { 1463 mConnector.doCommand(cmd); 1464 } catch (NativeDaemonConnectorException e) { 1465 int code = e.getCode(); 1466 if (code == VoldResponseCode.OpFailedStorageBusy) { 1467 rc = StorageResultCode.OperationFailedStorageBusy; 1468 } else { 1469 rc = StorageResultCode.OperationFailedInternalError; 1470 } 1471 } 1472 1473 if (rc == StorageResultCode.OperationSucceeded) { 1474 synchronized (mObbMountSet) { 1475 mObbMountSet.remove(filename); 1476 } 1477 } 1478 return rc; 1479 } 1480} 1481 1482