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