MountService.java revision 6cdd9c08565a6871ad72cd388adfdfca23532e5e
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 android.content.BroadcastReceiver; 20import android.content.Context; 21import android.content.Intent; 22import android.content.IntentFilter; 23import android.content.pm.PackageManager; 24import android.content.res.Resources; 25import android.net.Uri; 26import android.os.storage.IMountService; 27import android.os.storage.IMountServiceListener; 28import android.os.storage.StorageResultCode; 29import android.os.RemoteException; 30import android.os.IBinder; 31import android.os.Environment; 32import android.os.ServiceManager; 33import android.os.SystemClock; 34import android.os.SystemProperties; 35import android.os.UEventObserver; 36import android.os.Handler; 37import android.text.TextUtils; 38import android.util.Log; 39import java.util.ArrayList; 40import java.util.HashSet; 41 42import java.io.File; 43import java.io.FileReader; 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 55 private static final String TAG = "MountService"; 56 57 /* 58 * Internal vold volume state constants 59 */ 60 class VolumeState { 61 public static final int Init = -1; 62 public static final int NoMedia = 0; 63 public static final int Idle = 1; 64 public static final int Pending = 2; 65 public static final int Checking = 3; 66 public static final int Mounted = 4; 67 public static final int Unmounting = 5; 68 public static final int Formatting = 6; 69 public static final int Shared = 7; 70 public static final int SharedMnt = 8; 71 } 72 73 /* 74 * Internal vold response code constants 75 */ 76 class VoldResponseCode { 77 /* 78 * 100 series - Requestion action was initiated; expect another reply 79 * before proceeding with a new command. 80 */ 81 public static final int VolumeListResult = 110; 82 public static final int AsecListResult = 111; 83 84 /* 85 * 200 series - Requestion action has been successfully completed. 86 */ 87 public static final int ShareStatusResult = 210; 88 public static final int AsecPathResult = 211; 89 public static final int ShareEnabledResult = 212; 90 91 /* 92 * 400 series - Command was accepted, but the requested action 93 * did not take place. 94 */ 95 public static final int OpFailedNoMedia = 401; 96 public static final int OpFailedMediaBlank = 402; 97 public static final int OpFailedMediaCorrupt = 403; 98 public static final int OpFailedVolNotMounted = 404; 99 public static final int OpFailedVolBusy = 405; 100 101 /* 102 * 600 series - Unsolicited broadcasts. 103 */ 104 public static final int VolumeStateChange = 605; 105 public static final int ShareAvailabilityChange = 620; 106 public static final int VolumeDiskInserted = 630; 107 public static final int VolumeDiskRemoved = 631; 108 public static final int VolumeBadRemoval = 632; 109 } 110 111 private Context mContext; 112 private NativeDaemonConnector mConnector; 113 private String mLegacyState = Environment.MEDIA_REMOVED; 114 private PackageManagerService mPms; 115 private boolean mUmsEnabling; 116 private ArrayList<MountServiceBinderListener> mListeners; 117 private boolean mBooted; 118 private boolean mReady; 119 120 /** 121 * Private hash of currently mounted secure containers. 122 */ 123 private HashSet<String> mAsecMountSet = new HashSet<String>(); 124 125 private void waitForReady() { 126 while (mReady == false) { 127 for (int retries = 5; retries > 0; retries--) { 128 if (mReady) { 129 return; 130 } 131 SystemClock.sleep(1000); 132 } 133 Log.w(TAG, "Waiting too long for mReady!"); 134 } 135 } 136 137 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 138 public void onReceive(Context context, Intent intent) { 139 String action = intent.getAction(); 140 141 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 142 mBooted = true; 143 144 String path = Environment.getExternalStorageDirectory().getPath(); 145 if (getVolumeState(path).equals(Environment.MEDIA_UNMOUNTED)) { 146 int rc = doMountVolume(path); 147 if (rc != StorageResultCode.OperationSucceeded) { 148 Log.e(TAG, String.format("Boot-time mount failed (%d)", rc)); 149 } 150 } 151 } 152 } 153 }; 154 155 private final class MountServiceBinderListener implements IBinder.DeathRecipient { 156 final IMountServiceListener mListener; 157 158 MountServiceBinderListener(IMountServiceListener listener) { 159 mListener = listener; 160 161 } 162 163 public void binderDied() { 164 if (LOCAL_LOGD) Log.d(TAG, "An IMountServiceListener has died!"); 165 synchronized(mListeners) { 166 mListeners.remove(this); 167 mListener.asBinder().unlinkToDeath(this, 0); 168 } 169 } 170 } 171 172 private int doShareUnshareVolume(String path, String method, boolean enable) { 173 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 174 175 // TODO: Add support for multiple share methods 176 if (!method.equals("ums")) { 177 throw new IllegalArgumentException(String.format("Method %s not supported", method)); 178 } 179 180 /* 181 * If the volume is mounted and we're enabling then unmount it 182 */ 183 String vs = getVolumeState(path); 184 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { 185 mUmsEnabling = enable; // Override for isUsbMassStorageEnabled() 186 int rc = doUnmountVolume(path); 187 mUmsEnabling = false; // Clear override 188 if (rc != StorageResultCode.OperationSucceeded) { 189 Log.e(TAG, String.format("Failed to unmount before enabling UMS (%d)", rc)); 190 return rc; 191 } 192 } 193 194 try { 195 mConnector.doCommand(String.format( 196 "volume %sshare %s %s", (enable ? "" : "un"), path, method)); 197 } catch (NativeDaemonConnectorException e) { 198 Log.e(TAG, "Failed to share/unshare", e); 199 return StorageResultCode.OperationFailedInternalError; 200 } 201 202 /* 203 * If we disabled UMS then mount the volume 204 */ 205 if (!enable) { 206 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) { 207 Log.e(TAG, String.format( 208 "Failed to remount %s after disabling share method %s", path, method)); 209 /* 210 * Even though the mount failed, the unshare didn't so don't indicate an error. 211 * The mountVolume() call will have set the storage state and sent the necessary 212 * broadcasts. 213 */ 214 } 215 } 216 217 return StorageResultCode.OperationSucceeded; 218 } 219 220 private void updatePublicVolumeState(String path, String state) { 221 if (!path.equals(Environment.getExternalStorageDirectory().getPath())) { 222 Log.w(TAG, "Multiple volumes not currently supported"); 223 return; 224 } 225 226 if (mLegacyState.equals(state)) { 227 Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state)); 228 return; 229 } 230 231 String oldState = mLegacyState; 232 mLegacyState = state; 233 234 synchronized (mListeners) { 235 for (int i = mListeners.size() -1; i >= 0; i--) { 236 MountServiceBinderListener bl = mListeners.get(i); 237 try { 238 bl.mListener.onStorageStateChanged(path, oldState, state); 239 } catch (RemoteException rex) { 240 Log.e(TAG, "Listener dead"); 241 mListeners.remove(i); 242 } catch (Exception ex) { 243 Log.e(TAG, "Listener failed", ex); 244 } 245 } 246 } 247 } 248 249 /** 250 * 251 * Callback from NativeDaemonConnector 252 */ 253 public void onDaemonConnected() { 254 /* 255 * Since we'll be calling back into the NativeDaemonConnector, 256 * we need to do our work in a new thread. 257 */ 258 new Thread() { 259 public void run() { 260 /** 261 * Determine media state and UMS detection status 262 */ 263 String path = Environment.getExternalStorageDirectory().getPath(); 264 String state = Environment.MEDIA_REMOVED; 265 266 try { 267 String[] vols = mConnector.doListCommand( 268 "volume list", VoldResponseCode.VolumeListResult); 269 for (String volstr : vols) { 270 String[] tok = volstr.split(" "); 271 // FMT: <label> <mountpoint> <state> 272 if (!tok[1].equals(path)) { 273 Log.w(TAG, String.format( 274 "Skipping unknown volume '%s'",tok[1])); 275 continue; 276 } 277 int st = Integer.parseInt(tok[2]); 278 if (st == VolumeState.NoMedia) { 279 state = Environment.MEDIA_REMOVED; 280 } else if (st == VolumeState.Idle) { 281 state = Environment.MEDIA_UNMOUNTED; 282 } else if (st == VolumeState.Mounted) { 283 state = Environment.MEDIA_MOUNTED; 284 Log.i(TAG, "Media already mounted on daemon connection"); 285 } else if (st == VolumeState.Shared) { 286 state = Environment.MEDIA_SHARED; 287 Log.i(TAG, "Media shared on daemon connection"); 288 } else { 289 throw new Exception(String.format("Unexpected state %d", st)); 290 } 291 } 292 if (state != null) { 293 updatePublicVolumeState(path, state); 294 } 295 } catch (Exception e) { 296 Log.e(TAG, "Error processing initial volume state", e); 297 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 298 } 299 300 try { 301 boolean avail = doGetShareMethodAvailable("ums"); 302 notifyShareAvailabilityChange("ums", avail); 303 } catch (Exception ex) { 304 Log.w(TAG, "Failed to get share availability"); 305 } 306 /* 307 * Now that we've done our initialization, release 308 * the hounds! 309 */ 310 mReady = true; 311 } 312 }.start(); 313 } 314 315 /** 316 * Callback from NativeDaemonConnector 317 */ 318 public boolean onEvent(int code, String raw, String[] cooked) { 319 Intent in = null; 320 321 if (code == VoldResponseCode.VolumeStateChange) { 322 /* 323 * One of the volumes we're managing has changed state. 324 * Format: "NNN Volume <label> <path> state changed 325 * from <old_#> (<old_str>) to <new_#> (<new_str>)" 326 */ 327 notifyVolumeStateChange( 328 cooked[2], cooked[3], Integer.parseInt(cooked[7]), 329 Integer.parseInt(cooked[10])); 330 } else if (code == VoldResponseCode.ShareAvailabilityChange) { 331 // FMT: NNN Share method <method> now <available|unavailable> 332 boolean avail = false; 333 if (cooked[5].equals("available")) { 334 avail = true; 335 } 336 notifyShareAvailabilityChange(cooked[3], avail); 337 } else if ((code == VoldResponseCode.VolumeDiskInserted) || 338 (code == VoldResponseCode.VolumeDiskRemoved) || 339 (code == VoldResponseCode.VolumeBadRemoval)) { 340 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>) 341 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>) 342 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>) 343 final String label = cooked[2]; 344 final String path = cooked[3]; 345 int major = -1; 346 int minor = -1; 347 348 try { 349 String devComp = cooked[6].substring(1, cooked[6].length() -1); 350 String[] devTok = devComp.split(":"); 351 major = Integer.parseInt(devTok[0]); 352 minor = Integer.parseInt(devTok[1]); 353 } catch (Exception ex) { 354 Log.e(TAG, "Failed to parse major/minor", ex); 355 } 356 357 if (code == VoldResponseCode.VolumeDiskInserted) { 358 new Thread() { 359 public void run() { 360 try { 361 int rc; 362 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 363 Log.w(TAG, String.format("Insertion mount failed (%d)", rc)); 364 } 365 } catch (Exception ex) { 366 Log.w(TAG, "Failed to mount media on insertion", ex); 367 } 368 } 369 }.start(); 370 } else if (code == VoldResponseCode.VolumeDiskRemoved) { 371 /* 372 * This event gets trumped if we're already in BAD_REMOVAL state 373 */ 374 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) { 375 return true; 376 } 377 /* Send the media unmounted event first */ 378 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 379 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 380 mContext.sendBroadcast(in); 381 382 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 383 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path)); 384 } else if (code == VoldResponseCode.VolumeBadRemoval) { 385 /* Send the media unmounted event first */ 386 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 387 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 388 mContext.sendBroadcast(in); 389 390 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL); 391 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path)); 392 } else { 393 Log.e(TAG, String.format("Unknown code {%d}", code)); 394 } 395 } else { 396 return false; 397 } 398 399 if (in != null) { 400 mContext.sendBroadcast(in); 401 } 402 return true; 403 } 404 405 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) { 406 String vs = getVolumeState(path); 407 408 Intent in = null; 409 410 if (newState == VolumeState.Init) { 411 } else if (newState == VolumeState.NoMedia) { 412 // NoMedia is handled via Disk Remove events 413 } else if (newState == VolumeState.Idle) { 414 /* 415 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or 416 * if we're in the process of enabling UMS 417 */ 418 if (!vs.equals( 419 Environment.MEDIA_BAD_REMOVAL) && !vs.equals( 420 Environment.MEDIA_NOFS) && !vs.equals( 421 Environment.MEDIA_UNMOUNTABLE) && !mUmsEnabling) { 422 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 423 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 424 } 425 } else if (newState == VolumeState.Pending) { 426 } else if (newState == VolumeState.Checking) { 427 updatePublicVolumeState(path, Environment.MEDIA_CHECKING); 428 in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path)); 429 } else if (newState == VolumeState.Mounted) { 430 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED); 431 // Update media status on PackageManagerService to mount packages on sdcard 432 mPms.updateExternalMediaStatus(true); 433 in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path)); 434 in.putExtra("read-only", false); 435 } else if (newState == VolumeState.Unmounting) { 436 mPms.updateExternalMediaStatus(false); 437 in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path)); 438 } else if (newState == VolumeState.Formatting) { 439 } else if (newState == VolumeState.Shared) { 440 /* Send the media unmounted event first */ 441 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 442 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); 443 mContext.sendBroadcast(in); 444 445 updatePublicVolumeState(path, Environment.MEDIA_SHARED); 446 in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path)); 447 } else if (newState == VolumeState.SharedMnt) { 448 Log.e(TAG, "Live shared mounts not supported yet!"); 449 return; 450 } else { 451 Log.e(TAG, "Unhandled VolumeState {" + newState + "}"); 452 } 453 454 if (in != null) { 455 mContext.sendBroadcast(in); 456 } 457 } 458 459 private boolean doGetShareMethodAvailable(String method) { 460 ArrayList<String> rsp = mConnector.doCommand("share status " + method); 461 462 for (String line : rsp) { 463 String []tok = line.split(" "); 464 int code; 465 try { 466 code = Integer.parseInt(tok[0]); 467 } catch (NumberFormatException nfe) { 468 Log.e(TAG, String.format("Error parsing code %s", tok[0])); 469 return false; 470 } 471 if (code == VoldResponseCode.ShareStatusResult) { 472 if (tok[2].equals("available")) 473 return true; 474 return false; 475 } else { 476 Log.e(TAG, String.format("Unexpected response code %d", code)); 477 return false; 478 } 479 } 480 Log.e(TAG, "Got an empty response"); 481 return false; 482 } 483 484 private int doMountVolume(String path) { 485 int rc = StorageResultCode.OperationSucceeded; 486 487 try { 488 mConnector.doCommand(String.format("volume mount %s", path)); 489 } catch (NativeDaemonConnectorException e) { 490 /* 491 * Mount failed for some reason 492 */ 493 Intent in = null; 494 int code = e.getCode(); 495 if (code == VoldResponseCode.OpFailedNoMedia) { 496 /* 497 * Attempt to mount but no media inserted 498 */ 499 rc = StorageResultCode.OperationFailedNoMedia; 500 } else if (code == VoldResponseCode.OpFailedMediaBlank) { 501 /* 502 * Media is blank or does not contain a supported filesystem 503 */ 504 updatePublicVolumeState(path, Environment.MEDIA_NOFS); 505 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path)); 506 rc = StorageResultCode.OperationFailedMediaBlank; 507 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 508 /* 509 * Volume consistency check failed 510 */ 511 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE); 512 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path)); 513 rc = StorageResultCode.OperationFailedMediaCorrupt; 514 } else { 515 rc = StorageResultCode.OperationFailedInternalError; 516 } 517 518 /* 519 * Send broadcast intent (if required for the failure) 520 */ 521 if (in != null) { 522 mContext.sendBroadcast(in); 523 } 524 } 525 526 return rc; 527 } 528 529 private int doUnmountVolume(String path) { 530 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) { 531 return VoldResponseCode.OpFailedVolNotMounted; 532 } 533 534 // Notify PackageManager of potential media removal and deal with 535 // return code later on. The caller of this api should be aware or have been 536 // notified that the applications installed on the media will be killed. 537 mPms.updateExternalMediaStatus(false); 538 try { 539 mConnector.doCommand(String.format("volume unmount %s", path)); 540 return StorageResultCode.OperationSucceeded; 541 } catch (NativeDaemonConnectorException e) { 542 // Don't worry about mismatch in PackageManager since the 543 // call back will handle the status changes any way. 544 int code = e.getCode(); 545 if (code == VoldResponseCode.OpFailedVolNotMounted) { 546 return StorageResultCode.OperationFailedVolumeNotMounted; 547 } else { 548 return StorageResultCode.OperationFailedInternalError; 549 } 550 } 551 } 552 553 private int doFormatVolume(String path) { 554 try { 555 String cmd = String.format("volume format %s", path); 556 mConnector.doCommand(cmd); 557 return StorageResultCode.OperationSucceeded; 558 } catch (NativeDaemonConnectorException e) { 559 int code = e.getCode(); 560 if (code == VoldResponseCode.OpFailedNoMedia) { 561 return StorageResultCode.OperationFailedNoMedia; 562 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 563 return StorageResultCode.OperationFailedMediaCorrupt; 564 } else { 565 return StorageResultCode.OperationFailedInternalError; 566 } 567 } 568 } 569 570 private boolean doGetVolumeShared(String path, String method) { 571 String cmd = String.format("volume shared %s %s", path, method); 572 ArrayList<String> rsp = mConnector.doCommand(cmd); 573 574 for (String line : rsp) { 575 String []tok = line.split(" "); 576 int code; 577 try { 578 code = Integer.parseInt(tok[0]); 579 } catch (NumberFormatException nfe) { 580 Log.e(TAG, String.format("Error parsing code %s", tok[0])); 581 return false; 582 } 583 if (code == VoldResponseCode.ShareEnabledResult) { 584 if (tok[2].equals("enabled")) 585 return true; 586 return false; 587 } else { 588 Log.e(TAG, String.format("Unexpected response code %d", code)); 589 return false; 590 } 591 } 592 Log.e(TAG, "Got an empty response"); 593 return false; 594 } 595 596 private void notifyShareAvailabilityChange(String method, final boolean avail) { 597 if (!method.equals("ums")) { 598 Log.w(TAG, "Ignoring unsupported share method {" + method + "}"); 599 return; 600 } 601 602 synchronized (mListeners) { 603 for (int i = mListeners.size() -1; i >= 0; i--) { 604 MountServiceBinderListener bl = mListeners.get(i); 605 try { 606 bl.mListener.onUsbMassStorageConnectionChanged(avail); 607 } catch (RemoteException rex) { 608 Log.e(TAG, "Listener dead"); 609 mListeners.remove(i); 610 } catch (Exception ex) { 611 Log.e(TAG, "Listener failed", ex); 612 } 613 } 614 } 615 616 if (mBooted == true) { 617 Intent intent; 618 if (avail) { 619 intent = new Intent(Intent.ACTION_UMS_CONNECTED); 620 } else { 621 intent = new Intent(Intent.ACTION_UMS_DISCONNECTED); 622 } 623 mContext.sendBroadcast(intent); 624 } 625 } 626 627 private void validatePermission(String perm) { 628 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { 629 throw new SecurityException(String.format("Requires %s permission", perm)); 630 } 631 } 632 633 /** 634 * Constructs a new MountService instance 635 * 636 * @param context Binder context for this service 637 */ 638 public MountService(Context context) { 639 mContext = context; 640 641 /* 642 * Vold does not run in the simulator, so fake out a mounted 643 * event to trigger MediaScanner 644 */ 645 if ("simulator".equals(SystemProperties.get("ro.product.device"))) { 646 updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED); 647 return; 648 } 649 650 // XXX: This will go away soon in favor of IMountServiceObserver 651 mPms = (PackageManagerService) ServiceManager.getService("package"); 652 653 mContext.registerReceiver(mBroadcastReceiver, 654 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); 655 656 mListeners = new ArrayList<MountServiceBinderListener>(); 657 658 mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector"); 659 mReady = false; 660 Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName()); 661 thread.start(); 662 } 663 664 /** 665 * Exposed API calls below here 666 */ 667 668 public void registerListener(IMountServiceListener listener) { 669 synchronized (mListeners) { 670 MountServiceBinderListener bl = new MountServiceBinderListener(listener); 671 try { 672 listener.asBinder().linkToDeath(bl, 0); 673 mListeners.add(bl); 674 } catch (RemoteException rex) { 675 Log.e(TAG, "Failed to link to listener death"); 676 } 677 } 678 } 679 680 public void unregisterListener(IMountServiceListener listener) { 681 synchronized (mListeners) { 682 for(MountServiceBinderListener bl : mListeners) { 683 if (bl.mListener == listener) { 684 mListeners.remove(mListeners.indexOf(bl)); 685 return; 686 } 687 } 688 } 689 } 690 691 public void shutdown() { 692 validatePermission(android.Manifest.permission.SHUTDOWN); 693 694 Log.i(TAG, "Shutting down"); 695 696 String path = Environment.getExternalStorageDirectory().getPath(); 697 String state = getVolumeState(path); 698 699 if (state.equals(Environment.MEDIA_SHARED)) { 700 /* 701 * If the media is currently shared, unshare it. 702 * XXX: This is still dangerous!. We should not 703 * be rebooting at *all* if UMS is enabled, since 704 * the UMS host could have dirty FAT cache entries 705 * yet to flush. 706 */ 707 if (setUsbMassStorageEnabled(false) != StorageResultCode.OperationSucceeded) { 708 Log.e(TAG, "UMS disable on shutdown failed"); 709 } 710 } else if (state.equals(Environment.MEDIA_CHECKING)) { 711 /* 712 * If the media is being checked, then we need to wait for 713 * it to complete before being able to proceed. 714 */ 715 // XXX: @hackbod - Should we disable the ANR timer here? 716 int retries = 30; 717 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) { 718 try { 719 Thread.sleep(1000); 720 } catch (InterruptedException iex) { 721 Log.e(TAG, "Interrupted while waiting for media", iex); 722 break; 723 } 724 state = Environment.getExternalStorageState(); 725 } 726 if (retries == 0) { 727 Log.e(TAG, "Timed out waiting for media to check"); 728 } 729 } 730 731 if (state.equals(Environment.MEDIA_MOUNTED)) { 732 /* 733 * If the media is mounted, then gracefully unmount it. 734 */ 735 if (doUnmountVolume(path) != StorageResultCode.OperationSucceeded) { 736 Log.e(TAG, "Failed to unmount media for shutdown"); 737 } 738 } 739 } 740 741 public boolean isUsbMassStorageConnected() { 742 waitForReady(); 743 744 if (mUmsEnabling) { 745 return true; 746 } 747 return doGetShareMethodAvailable("ums"); 748 } 749 750 public int setUsbMassStorageEnabled(boolean enable) { 751 waitForReady(); 752 753 return doShareUnshareVolume(Environment.getExternalStorageDirectory().getPath(), "ums", enable); 754 } 755 756 public boolean isUsbMassStorageEnabled() { 757 waitForReady(); 758 return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums"); 759 } 760 761 /** 762 * @return state of the volume at the specified mount point 763 */ 764 public String getVolumeState(String mountPoint) { 765 /* 766 * XXX: Until we have multiple volume discovery, just hardwire 767 * this to /sdcard 768 */ 769 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) { 770 Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); 771 throw new IllegalArgumentException(); 772 } 773 774 return mLegacyState; 775 } 776 777 public int mountVolume(String path) { 778 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 779 780 waitForReady(); 781 return doMountVolume(path); 782 } 783 784 public int unmountVolume(String path) { 785 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 786 waitForReady(); 787 788 return doUnmountVolume(path); 789 } 790 791 public int formatVolume(String path) { 792 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); 793 waitForReady(); 794 795 return doFormatVolume(path); 796 } 797 798 private void warnOnNotMounted() { 799 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 800 Log.w(TAG, "getSecureContainerList() called when storage not mounted"); 801 } 802 } 803 804 public String[] getSecureContainerList() { 805 validatePermission(android.Manifest.permission.ASEC_ACCESS); 806 waitForReady(); 807 warnOnNotMounted(); 808 809 try { 810 return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult); 811 } catch (NativeDaemonConnectorException e) { 812 return new String[0]; 813 } 814 } 815 816 public int createSecureContainer(String id, int sizeMb, String fstype, 817 String key, int ownerUid) { 818 validatePermission(android.Manifest.permission.ASEC_CREATE); 819 waitForReady(); 820 warnOnNotMounted(); 821 822 int rc = StorageResultCode.OperationSucceeded; 823 String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid); 824 try { 825 mConnector.doCommand(cmd); 826 } catch (NativeDaemonConnectorException e) { 827 rc = StorageResultCode.OperationFailedInternalError; 828 } 829 return rc; 830 } 831 832 public int finalizeSecureContainer(String id) { 833 validatePermission(android.Manifest.permission.ASEC_CREATE); 834 warnOnNotMounted(); 835 836 int rc = StorageResultCode.OperationSucceeded; 837 try { 838 mConnector.doCommand(String.format("asec finalize %s", id)); 839 } catch (NativeDaemonConnectorException e) { 840 rc = StorageResultCode.OperationFailedInternalError; 841 } 842 return rc; 843 } 844 845 public int destroySecureContainer(String id) { 846 validatePermission(android.Manifest.permission.ASEC_DESTROY); 847 waitForReady(); 848 warnOnNotMounted(); 849 850 int rc = StorageResultCode.OperationSucceeded; 851 try { 852 mConnector.doCommand(String.format("asec destroy %s", id)); 853 } catch (NativeDaemonConnectorException e) { 854 rc = StorageResultCode.OperationFailedInternalError; 855 } 856 return rc; 857 } 858 859 public int mountSecureContainer(String id, String key, int ownerUid) { 860 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 861 waitForReady(); 862 warnOnNotMounted(); 863 864 int rc = StorageResultCode.OperationSucceeded; 865 String cmd = String.format("asec mount %s %s %d", id, key, ownerUid); 866 try { 867 mConnector.doCommand(cmd); 868 } catch (NativeDaemonConnectorException e) { 869 rc = StorageResultCode.OperationFailedInternalError; 870 } 871 872 if (rc == StorageResultCode.OperationSucceeded) { 873 synchronized (mAsecMountSet) { 874 mAsecMountSet.add(id); 875 } 876 } 877 return rc; 878 } 879 880 public int unmountSecureContainer(String id) { 881 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 882 waitForReady(); 883 warnOnNotMounted(); 884 885 synchronized (mAsecMountSet) { 886 if (!mAsecMountSet.contains(id)) { 887 return StorageResultCode.OperationFailedVolumeNotMounted; 888 } 889 } 890 891 int rc = StorageResultCode.OperationSucceeded; 892 String cmd = String.format("asec unmount %s", id); 893 try { 894 mConnector.doCommand(cmd); 895 } catch (NativeDaemonConnectorException e) { 896 rc = StorageResultCode.OperationFailedInternalError; 897 } 898 899 if (rc == StorageResultCode.OperationSucceeded) { 900 synchronized (mAsecMountSet) { 901 mAsecMountSet.remove(id); 902 } 903 } 904 return rc; 905 } 906 907 public boolean isSecureContainerMounted(String id) { 908 validatePermission(android.Manifest.permission.ASEC_ACCESS); 909 waitForReady(); 910 warnOnNotMounted(); 911 912 synchronized (mAsecMountSet) { 913 return mAsecMountSet.contains(id); 914 } 915 } 916 917 public int renameSecureContainer(String oldId, String newId) { 918 validatePermission(android.Manifest.permission.ASEC_RENAME); 919 waitForReady(); 920 warnOnNotMounted(); 921 922 int rc = StorageResultCode.OperationSucceeded; 923 String cmd = String.format("asec rename %s %s", oldId, newId); 924 try { 925 mConnector.doCommand(cmd); 926 } catch (NativeDaemonConnectorException e) { 927 rc = StorageResultCode.OperationFailedInternalError; 928 } 929 return rc; 930 } 931 932 public String getSecureContainerPath(String id) { 933 validatePermission(android.Manifest.permission.ASEC_ACCESS); 934 waitForReady(); 935 warnOnNotMounted(); 936 937 ArrayList<String> rsp = mConnector.doCommand("asec path " + id); 938 939 for (String line : rsp) { 940 String []tok = line.split(" "); 941 int code = Integer.parseInt(tok[0]); 942 if (code == VoldResponseCode.AsecPathResult) { 943 return tok[1]; 944 } else { 945 Log.e(TAG, String.format("Unexpected response code %d", code)); 946 return ""; 947 } 948 } 949 950 Log.e(TAG, "Got an empty response"); 951 return ""; 952 } 953} 954 955