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