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