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