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