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