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