MountService.java revision 5b77dab23469273d41f9c530d947ac055765e6ea
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.app.Notification; 20import android.app.NotificationManager; 21import android.app.PendingIntent; 22import android.content.BroadcastReceiver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.content.pm.PackageManager; 27import android.content.res.Resources; 28import android.net.Uri; 29import android.os.IMountService; 30import android.os.Environment; 31import android.os.ServiceManager; 32import android.os.SystemProperties; 33import android.os.UEventObserver; 34import android.os.Handler; 35import android.text.TextUtils; 36import android.util.Log; 37import java.util.ArrayList; 38 39import android.provider.Settings; 40import android.content.ContentResolver; 41import android.database.ContentObserver; 42 43import java.io.File; 44import java.io.FileReader; 45import java.lang.IllegalStateException; 46 47/** 48 * MountService implements an to the mount service daemon 49 * @hide 50 */ 51class MountService extends IMountService.Stub 52 implements INativeDaemonConnectorCallbacks { 53 54 private static final String TAG = "MountService"; 55 56 class VolumeState { 57 public static final int Init = -1; 58 public static final int NoMedia = 0; 59 public static final int Idle = 1; 60 public static final int Pending = 2; 61 public static final int Checking = 3; 62 public static final int Mounted = 4; 63 public static final int Unmounting = 5; 64 public static final int Formatting = 6; 65 public static final int Shared = 7; 66 public static final int SharedMnt = 8; 67 } 68 69 class VoldResponseCode { 70 public static final int VolumeListResult = 110; 71 public static final int AsecListResult = 111; 72 73 public static final int ShareAvailabilityResult = 210; 74 public static final int AsecPathResult = 211; 75 76 public static final int VolumeStateChange = 605; 77 public static final int VolumeMountFailedBlank = 610; 78 public static final int VolumeMountFailedDamaged = 611; 79 public static final int VolumeMountFailedNoMedia = 612; 80 public static final int ShareAvailabilityChange = 620; 81 public static final int VolumeDiskInserted = 630; 82 public static final int VolumeDiskRemoved = 631; 83 public static final int VolumeBadRemoval = 632; 84 } 85 86 87 /** 88 * Binder context for this service 89 */ 90 private Context mContext; 91 92 /** 93 * connectorr object for communicating with vold 94 */ 95 private NativeDaemonConnector mConnector; 96 97 /** 98 * The notification that is shown when a USB mass storage host 99 * is connected. 100 * <p> 101 * This is lazily created, so use {@link #setUsbStorageNotification()}. 102 */ 103 private Notification mUsbStorageNotification; 104 105 106 /** 107 * The notification that is shown when the following media events occur: 108 * - Media is being checked 109 * - Media is blank (or unknown filesystem) 110 * - Media is corrupt 111 * - Media is safe to unmount 112 * - Media is missing 113 * <p> 114 * This is lazily created, so use {@link #setMediaStorageNotification()}. 115 */ 116 private Notification mMediaStorageNotification; 117 118 private boolean mShowSafeUnmountNotificationWhenUnmounted; 119 120 private boolean mPlaySounds; 121 122 private boolean mMounted; 123 124 private SettingsWatcher mSettingsWatcher; 125 private boolean mAutoStartUms; 126 private boolean mPromptUms; 127 private boolean mUmsActiveNotify; 128 129 private boolean mUmsConnected = false; 130 private boolean mUmsEnabled = false; 131 private boolean mUmsEnabling = false; 132 133 private String mLegacyState = Environment.MEDIA_REMOVED; 134 135 private PackageManagerService mPms; 136 137 /** 138 * Constructs a new MountService instance 139 * 140 * @param context Binder context for this service 141 */ 142 public MountService(Context context) { 143 mContext = context; 144 145 mPms = (PackageManagerService) ServiceManager.getService("package"); 146 // Register a BOOT_COMPLETED handler so that we can start 147 // our NativeDaemonConnector. We defer the startup so that we don't 148 // start processing events before we ought-to 149 mContext.registerReceiver(mBroadcastReceiver, 150 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); 151 152 mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector"); 153 mShowSafeUnmountNotificationWhenUnmounted = false; 154 155 mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1"); 156 157 ContentResolver cr = mContext.getContentResolver(); 158 mAutoStartUms = (Settings.Secure.getInt( 159 cr, Settings.Secure.MOUNT_UMS_AUTOSTART, 0) == 1); 160 mPromptUms = (Settings.Secure.getInt( 161 cr, Settings.Secure.MOUNT_UMS_PROMPT, 1) == 1); 162 mUmsActiveNotify = (Settings.Secure.getInt( 163 cr, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED, 1) == 1); 164 165 mSettingsWatcher = new SettingsWatcher(new Handler()); 166 } 167 168 private class SettingsWatcher extends ContentObserver { 169 public SettingsWatcher(Handler handler) { 170 super(handler); 171 ContentResolver cr = mContext.getContentResolver(); 172 cr.registerContentObserver(Settings.System.getUriFor( 173 Settings.Secure.MOUNT_PLAY_NOTIFICATION_SND), false, this); 174 cr.registerContentObserver(Settings.Secure.getUriFor( 175 Settings.Secure.MOUNT_UMS_AUTOSTART), false, this); 176 cr.registerContentObserver(Settings.Secure.getUriFor( 177 Settings.Secure.MOUNT_UMS_PROMPT), false, this); 178 cr.registerContentObserver(Settings.Secure.getUriFor( 179 Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED), false, this); 180 } 181 182 public void onChange(boolean selfChange) { 183 super.onChange(selfChange); 184 ContentResolver cr = mContext.getContentResolver(); 185 186 boolean newPlayNotificationSounds = (Settings.Secure.getInt( 187 cr, Settings.Secure.MOUNT_PLAY_NOTIFICATION_SND, 1) == 1); 188 189 boolean newUmsAutostart = (Settings.Secure.getInt( 190 cr, Settings.Secure.MOUNT_UMS_AUTOSTART, 0) == 1); 191 192 if (newUmsAutostart != mAutoStartUms) { 193 mAutoStartUms = newUmsAutostart; 194 } 195 196 boolean newUmsPrompt = (Settings.Secure.getInt( 197 cr, Settings.Secure.MOUNT_UMS_PROMPT, 1) == 1); 198 199 if (newUmsPrompt != mPromptUms) { 200 mPromptUms = newUmsAutostart; 201 } 202 203 boolean newUmsNotifyEnabled = (Settings.Secure.getInt( 204 cr, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED, 1) == 1); 205 206 if (mUmsEnabled) { 207 if (newUmsNotifyEnabled) { 208 Intent intent = new Intent(); 209 intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class); 210 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 211 setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title, 212 com.android.internal.R.string.usb_storage_stop_notification_message, 213 com.android.internal.R.drawable.stat_sys_warning, 214 false, true, pi); 215 } else { 216 setUsbStorageNotification(0, 0, 0, false, false, null); 217 } 218 } 219 if (newUmsNotifyEnabled != mUmsActiveNotify) { 220 mUmsActiveNotify = newUmsNotifyEnabled; 221 } 222 } 223 } 224 225 BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 226 public void onReceive(Context context, Intent intent) { 227 String action = intent.getAction(); 228 229 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 230 /* 231 * Vold does not run in the simulator, so fake out a mounted 232 * event to trigger MediaScanner 233 */ 234 if ("simulator".equals(SystemProperties.get("ro.product.device"))) { 235 notifyMediaMounted( 236 Environment.getExternalStorageDirectory().getPath(), false); 237 return; 238 } 239 240 Thread thread = new Thread( 241 mConnector, NativeDaemonConnector.class.getName()); 242 thread.start(); 243 } 244 } 245 }; 246 247 public void shutdown() { 248 if (mContext.checkCallingOrSelfPermission( 249 android.Manifest.permission.SHUTDOWN) 250 != PackageManager.PERMISSION_GRANTED) { 251 throw new SecurityException("Requires SHUTDOWN permission"); 252 } 253 254 Log.d(TAG, "Shutting down"); 255 String state = Environment.getExternalStorageState(); 256 257 if (state.equals(Environment.MEDIA_SHARED)) { 258 /* 259 * If the media is currently shared, unshare it. 260 * XXX: This is still dangerous!. We should not 261 * be rebooting at *all* if UMS is enabled, since 262 * the UMS host could have dirty FAT cache entries 263 * yet to flush. 264 */ 265 try { 266 setMassStorageEnabled(false); 267 } catch (Exception e) { 268 Log.e(TAG, "ums disable failed", e); 269 } 270 } else if (state.equals(Environment.MEDIA_CHECKING)) { 271 /* 272 * If the media is being checked, then we need to wait for 273 * it to complete before being able to proceed. 274 */ 275 // XXX: @hackbod - Should we disable the ANR timer here? 276 int retries = 30; 277 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) { 278 try { 279 Thread.sleep(1000); 280 } catch (InterruptedException iex) { 281 Log.e(TAG, "Interrupted while waiting for media", iex); 282 break; 283 } 284 state = Environment.getExternalStorageState(); 285 } 286 if (retries == 0) { 287 Log.e(TAG, "Timed out waiting for media to check"); 288 } 289 } 290 291 if (state.equals(Environment.MEDIA_MOUNTED)) { 292 /* 293 * If the media is mounted, then gracefully unmount it. 294 */ 295 try { 296 String m = Environment.getExternalStorageDirectory().toString(); 297 unmountVolume(m); 298 299 int retries = 12; 300 while (!state.equals(Environment.MEDIA_UNMOUNTED) && (retries-- >=0)) { 301 try { 302 Thread.sleep(1000); 303 } catch (InterruptedException iex) { 304 Log.e(TAG, "Interrupted while waiting for media", iex); 305 break; 306 } 307 state = Environment.getExternalStorageState(); 308 } 309 if (retries == 0) { 310 Log.e(TAG, "Timed out waiting for media to unmount"); 311 } 312 } catch (Exception e) { 313 Log.e(TAG, "external storage unmount failed", e); 314 } 315 } 316 } 317 318 /** 319 * @return true if USB mass storage support is enabled. 320 */ 321 public boolean getMassStorageEnabled() { 322 return mUmsEnabled; 323 } 324 325 /** 326 * Enables or disables USB mass storage support. 327 * 328 * @param enable true to enable USB mass storage support 329 */ 330 public void setMassStorageEnabled(boolean enable) throws IllegalStateException { 331 if (mContext.checkCallingOrSelfPermission( 332 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) 333 != PackageManager.PERMISSION_GRANTED) { 334 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission"); 335 } 336 try { 337 String vp = Environment.getExternalStorageDirectory().getPath(); 338 String vs = getVolumeState(vp); 339 340 mUmsEnabling = enable; 341 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { 342 unmountVolume(vp); 343 mUmsEnabling = false; 344 updateUsbMassStorageNotification(true, false); 345 } 346 347 setShareMethodEnabled(vp, "ums", enable); 348 mUmsEnabled = enable; 349 mUmsEnabling = false; 350 if (!enable) { 351 mountVolume(vp); 352 if (mPromptUms) { 353 updateUsbMassStorageNotification(false, false); 354 } else { 355 updateUsbMassStorageNotification(true, false); 356 } 357 } 358 } catch (IllegalStateException rex) { 359 Log.e(TAG, "Failed to set ums enable {" + enable + "}"); 360 return; 361 } 362 } 363 364 /** 365 * @return true if USB mass storage is connected. 366 */ 367 public boolean getMassStorageConnected() { 368 return mUmsConnected; 369 } 370 371 /** 372 * @return state of the volume at the specified mount point 373 */ 374 public String getVolumeState(String mountPoint) throws IllegalStateException { 375 /* 376 * XXX: Until we have multiple volume discovery, just hardwire 377 * this to /sdcard 378 */ 379 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) { 380 Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); 381 throw new IllegalArgumentException(); 382 } 383 384 return mLegacyState; 385 } 386 387 388 /** 389 * Attempt to mount external media 390 */ 391 public void mountVolume(String mountPath) throws IllegalStateException { 392 if (mContext.checkCallingOrSelfPermission( 393 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) 394 != PackageManager.PERMISSION_GRANTED) { 395 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission"); 396 } 397 mConnector.doCommand(String.format("mount %s", mountPath)); 398 } 399 400 /** 401 * Attempt to unmount external media to prepare for eject 402 */ 403 public void unmountVolume(String mountPath) throws IllegalStateException { 404 if (mContext.checkCallingOrSelfPermission( 405 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) 406 != PackageManager.PERMISSION_GRANTED) { 407 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission"); 408 } 409 410 // Set a flag so that when we get the unmounted event, we know 411 // to display the notification 412 mShowSafeUnmountNotificationWhenUnmounted = true; 413 414 mConnector.doCommand(String.format("unmount %s", mountPath)); 415 } 416 417 /** 418 * Attempt to format external media 419 */ 420 public void formatVolume(String formatPath) throws IllegalStateException { 421 if (mContext.checkCallingOrSelfPermission( 422 android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS) 423 != PackageManager.PERMISSION_GRANTED) { 424 throw new SecurityException("Requires MOUNT_FORMAT_FILESYSTEMS permission"); 425 } 426 427 mConnector.doCommand(String.format("format %s", formatPath)); 428 } 429 430 boolean getShareAvailable(String method) throws IllegalStateException { 431 ArrayList<String> rsp = mConnector.doCommand("share_available " + method); 432 433 for (String line : rsp) { 434 String []tok = line.split(" "); 435 int code = Integer.parseInt(tok[0]); 436 if (code == VoldResponseCode.ShareAvailabilityResult) { 437 if (tok[2].equals("available")) 438 return true; 439 return false; 440 } else { 441 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 442 } 443 } 444 throw new IllegalStateException("Got an empty response"); 445 } 446 447 /** 448 * Enables or disables USB mass storage support. 449 * 450 * @param enable true to enable USB mass storage support 451 */ 452 void setShareMethodEnabled(String mountPoint, String method, 453 boolean enable) throws IllegalStateException { 454 mConnector.doCommand(String.format( 455 "%sshare %s %s", (enable ? "" : "un"), mountPoint, method)); 456 } 457 458 459 /** 460 * Returns true if we're playing media notification sounds. 461 */ 462 public boolean getPlayNotificationSounds() { 463 return mPlaySounds; 464 } 465 466 /** 467 * Set whether or not we're playing media notification sounds. 468 */ 469 public void setPlayNotificationSounds(boolean enabled) { 470 if (mContext.checkCallingOrSelfPermission( 471 android.Manifest.permission.WRITE_SETTINGS) 472 != PackageManager.PERMISSION_GRANTED) { 473 throw new SecurityException("Requires WRITE_SETTINGS permission"); 474 } 475 mPlaySounds = enabled; 476 SystemProperties.set("persist.service.mount.playsnd", (enabled ? "1" : "0")); 477 } 478 479 void updatePublicVolumeState(String mountPoint, String state) { 480 if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) { 481 Log.w(TAG, "Multiple volumes not currently supported"); 482 return; 483 } 484 Log.i(TAG, "State for {" + mountPoint + "} = {" + state + "}"); 485 mLegacyState = state; 486 } 487 488 /** 489 * Update the state of the USB mass storage notification 490 */ 491 void updateUsbMassStorageNotification(boolean suppressIfConnected, boolean sound) { 492 493 try { 494 495 if (getMassStorageConnected() && !suppressIfConnected) { 496 Intent intent = new Intent(); 497 intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class); 498 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 499 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 500 setUsbStorageNotification( 501 com.android.internal.R.string.usb_storage_notification_title, 502 com.android.internal.R.string.usb_storage_notification_message, 503 com.android.internal.R.drawable.stat_sys_data_usb, 504 sound, true, pi); 505 } else { 506 setUsbStorageNotification(0, 0, 0, false, false, null); 507 } 508 } catch (IllegalStateException e) { 509 // Nothing to do 510 } 511 } 512 513 void handlePossibleExplicitUnmountBroadcast(String path) { 514 if (mMounted) { 515 mMounted = false; 516 // Update media status on PackageManagerService to unmount packages on sdcard 517 mPms.updateExternalMediaStatus(false); 518 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, 519 Uri.parse("file://" + path)); 520 mContext.sendBroadcast(intent); 521 } 522 } 523 524 /** 525 * 526 * Callback from NativeDaemonConnector 527 */ 528 public void onDaemonConnected() { 529 /* 530 * Since we'll be calling back into the NativeDaemonConnector, 531 * we need to do our work in a new thread. 532 */ 533 new Thread() { 534 public void run() { 535 /** 536 * Determine media state and UMS detection status 537 */ 538 String path = Environment.getExternalStorageDirectory().getPath(); 539 String state = Environment.MEDIA_REMOVED; 540 541 try { 542 String[] vols = mConnector.doListCommand( 543 "list_volumes", VoldResponseCode.VolumeListResult); 544 for (String volstr : vols) { 545 String[] tok = volstr.split(" "); 546 // FMT: <label> <mountpoint> <state> 547 if (!tok[1].equals(path)) { 548 Log.w(TAG, String.format( 549 "Skipping unknown volume '%s'",tok[1])); 550 continue; 551 } 552 int st = Integer.parseInt(tok[2]); 553 if (st == VolumeState.NoMedia) { 554 state = Environment.MEDIA_REMOVED; 555 } else if (st == VolumeState.Idle) { 556 state = Environment.MEDIA_UNMOUNTED; 557 try { 558 mountVolume(path); 559 } catch (Exception ex) { 560 Log.e(TAG, "Connection-mount failed", ex); 561 } 562 } else if (st == VolumeState.Mounted) { 563 state = Environment.MEDIA_MOUNTED; 564 Log.i(TAG, "Media already mounted on daemon connection"); 565 } else if (st == VolumeState.Shared) { 566 state = Environment.MEDIA_SHARED; 567 Log.i(TAG, "Media shared on daemon connection"); 568 } else { 569 throw new Exception(String.format("Unexpected state %d", st)); 570 } 571 } 572 updatePublicVolumeState(path, state); 573 } catch (Exception e) { 574 Log.e(TAG, "Error processing initial volume state", e); 575 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 576 } 577 578 try { 579 boolean avail = getShareAvailable("ums"); 580 notifyShareAvailabilityChange("ums", avail); 581 } catch (Exception ex) { 582 Log.w(TAG, "Failed to get share availability"); 583 } 584 } 585 }.start(); 586 } 587 588 /** 589 * 590 * Callback from NativeDaemonConnector 591 */ 592 public boolean onEvent(int code, String raw, String[] cooked) { 593 // Log.d(TAG, "event {" + raw + "}"); 594 if (code == VoldResponseCode.VolumeStateChange) { 595 // FMT: NNN Volume <label> <mountpoint> state changed 596 // from <old_#> (<old_str>) to <new_#> (<new_str>) 597 notifyVolumeStateChange( 598 cooked[2], cooked[3], Integer.parseInt(cooked[7]), 599 Integer.parseInt(cooked[10])); 600 } else if (code == VoldResponseCode.VolumeMountFailedBlank) { 601 // FMT: NNN Volume <label> <mountpoint> mount failed - no supported file-systems 602 notifyMediaNoFs(cooked[3]); 603 // FMT: NNN Volume <label> <mountpoint> mount failed - no media 604 } else if (code == VoldResponseCode.VolumeMountFailedNoMedia) { 605 notifyMediaRemoved(cooked[3]); 606 } else if (code == VoldResponseCode.VolumeMountFailedDamaged) { 607 // FMT: NNN Volume <label> <mountpoint> mount failed - filesystem check failed 608 notifyMediaUnmountable(cooked[3]); 609 } else if (code == VoldResponseCode.ShareAvailabilityChange) { 610 // FMT: NNN Share method <method> now <available|unavailable> 611 boolean avail = false; 612 if (cooked[5].equals("available")) { 613 avail = true; 614 } 615 notifyShareAvailabilityChange(cooked[3], avail); 616 } else if (code == VoldResponseCode.VolumeDiskInserted) { 617 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>) 618 notifyMediaInserted(cooked[3]); 619 } else if (code == VoldResponseCode.VolumeDiskRemoved) { 620 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>) 621 notifyMediaRemoved(cooked[3]); 622 } else if (code == VoldResponseCode.VolumeBadRemoval) { 623 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>) 624 notifyMediaBadRemoval(cooked[3]); 625 } else { 626 return false; 627 } 628 return true; 629 } 630 631 void notifyVolumeStateChange(String label, String mountPoint, int oldState, 632 int newState) throws IllegalStateException { 633 String vs = getVolumeState(mountPoint); 634 635 if (newState == VolumeState.Init) { 636 } else if (newState == VolumeState.NoMedia) { 637 // NoMedia is handled via Disk Remove events 638 } else if (newState == VolumeState.Idle) { 639 /* 640 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or 641 * if we're in the process of enabling UMS 642 */ 643 if (!vs.equals(Environment.MEDIA_BAD_REMOVAL) && 644 !vs.equals(Environment.MEDIA_NOFS) && 645 !vs.equals(Environment.MEDIA_UNMOUNTABLE) && 646 !mUmsEnabling) { 647 notifyMediaUnmounted(mountPoint); 648 } 649 } else if (newState == VolumeState.Pending) { 650 } else if (newState == VolumeState.Checking) { 651 notifyMediaChecking(mountPoint); 652 } else if (newState == VolumeState.Mounted) { 653 notifyMediaMounted(mountPoint, false); 654 } else if (newState == VolumeState.Unmounting) { 655 notifyMediaUnmounting(mountPoint); 656 } else if (newState == VolumeState.Formatting) { 657 } else if (newState == VolumeState.Shared) { 658 notifyMediaShared(mountPoint, false); 659 } else if (newState == VolumeState.SharedMnt) { 660 notifyMediaShared(mountPoint, true); 661 } else { 662 Log.e(TAG, "Unhandled VolumeState {" + newState + "}"); 663 } 664 } 665 666 667 /** 668 * Broadcasts the USB mass storage connected event to all clients. 669 */ 670 void notifyUmsConnected() { 671 mUmsConnected = true; 672 673 String storageState = Environment.getExternalStorageState(); 674 if (!storageState.equals(Environment.MEDIA_REMOVED) && 675 !storageState.equals(Environment.MEDIA_BAD_REMOVAL) && 676 !storageState.equals(Environment.MEDIA_CHECKING)) { 677 678 if (mAutoStartUms) { 679 try { 680 setMassStorageEnabled(true); 681 } catch (IllegalStateException e) { 682 } 683 } else if (mPromptUms) { 684 updateUsbMassStorageNotification(false, true); 685 } 686 } 687 688 Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED); 689 mContext.sendBroadcast(intent); 690 } 691 692 void notifyShareAvailabilityChange(String method, final boolean avail) { 693 if (!method.equals("ums")) { 694 Log.w(TAG, "Ignoring unsupported share method {" + method + "}"); 695 return; 696 } 697 698 /* 699 * Notification needs to run in a different thread as 700 * it may need to call back into vold 701 */ 702 new Thread() { 703 public void run() { 704 try { 705 if (avail) { 706 notifyUmsConnected(); 707 } else { 708 notifyUmsDisconnected(); 709 } 710 } catch (Exception ex) { 711 Log.w(TAG, "Failed to mount media on insertion"); 712 } 713 } 714 }.start(); 715 } 716 717 /** 718 * Broadcasts the USB mass storage disconnected event to all clients. 719 */ 720 void notifyUmsDisconnected() { 721 mUmsConnected = false; 722 if (mUmsEnabled) { 723 try { 724 Log.w(TAG, "UMS disconnected while enabled!"); 725 setMassStorageEnabled(false); 726 } catch (Exception ex) { 727 Log.e(TAG, "Error disabling UMS on unsafe UMS disconnect", ex); 728 } 729 } 730 updateUsbMassStorageNotification(false, false); 731 Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED); 732 mContext.sendBroadcast(intent); 733 } 734 735 void notifyMediaInserted(final String path) throws IllegalStateException { 736 new Thread() { 737 public void run() { 738 try { 739 mountVolume(path); 740 } catch (Exception ex) { 741 Log.w(TAG, "Failed to mount media on insertion", ex); 742 } 743 } 744 }.start(); 745 } 746 747 /** 748 * Broadcasts the media removed event to all clients. 749 */ 750 void notifyMediaRemoved(String path) throws IllegalStateException { 751 752 // Suppress this on bad removal 753 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) { 754 return; 755 } 756 757 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 758 759 updateUsbMassStorageNotification(true, false); 760 761 setMediaStorageNotification( 762 com.android.internal.R.string.ext_media_nomedia_notification_title, 763 com.android.internal.R.string.ext_media_nomedia_notification_message, 764 com.android.internal.R.drawable.stat_notify_sdcard_usb, 765 true, false, null); 766 handlePossibleExplicitUnmountBroadcast(path); 767 768 Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED, 769 Uri.parse("file://" + path)); 770 mContext.sendBroadcast(intent); 771 } 772 773 /** 774 * Broadcasts the media unmounted event to all clients. 775 */ 776 void notifyMediaUnmounted(String path) { 777 778 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 779 780 // Update media status on PackageManagerService to unmount packages on sdcard 781 mPms.updateExternalMediaStatus(false); 782 if (mShowSafeUnmountNotificationWhenUnmounted) { 783 setMediaStorageNotification( 784 com.android.internal.R.string.ext_media_safe_unmount_notification_title, 785 com.android.internal.R.string.ext_media_safe_unmount_notification_message, 786 com.android.internal.R.drawable.stat_notify_sdcard, 787 true, true, null); 788 mShowSafeUnmountNotificationWhenUnmounted = false; 789 } else { 790 setMediaStorageNotification(0, 0, 0, false, false, null); 791 } 792 updateUsbMassStorageNotification(false, false); 793 794 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, 795 Uri.parse("file://" + path)); 796 mContext.sendBroadcast(intent); 797 } 798 799 /** 800 * Broadcasts the media checking event to all clients. 801 */ 802 void notifyMediaChecking(String path) { 803 updatePublicVolumeState(path, Environment.MEDIA_CHECKING); 804 805 setMediaStorageNotification( 806 com.android.internal.R.string.ext_media_checking_notification_title, 807 com.android.internal.R.string.ext_media_checking_notification_message, 808 com.android.internal.R.drawable.stat_notify_sdcard_prepare, 809 true, false, null); 810 811 updateUsbMassStorageNotification(true, false); 812 Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING, 813 Uri.parse("file://" + path)); 814 mContext.sendBroadcast(intent); 815 } 816 817 /** 818 * Broadcasts the media nofs event to all clients. 819 */ 820 void notifyMediaNoFs(String path) { 821 updatePublicVolumeState(path, Environment.MEDIA_NOFS); 822 823 Intent intent = new Intent(); 824 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); 825 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 826 827 setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title, 828 com.android.internal.R.string.ext_media_nofs_notification_message, 829 com.android.internal.R.drawable.stat_notify_sdcard_usb, 830 true, false, pi); 831 updateUsbMassStorageNotification(false, false); 832 intent = new Intent(Intent.ACTION_MEDIA_NOFS, 833 Uri.parse("file://" + path)); 834 mContext.sendBroadcast(intent); 835 } 836 837 /** 838 * Broadcasts the media mounted event to all clients. 839 */ 840 void notifyMediaMounted(String path, boolean readOnly) { 841 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED); 842 843 // Update media status on PackageManagerService to mount packages on sdcard 844 mPms.updateExternalMediaStatus(true); 845 setMediaStorageNotification(0, 0, 0, false, false, null); 846 updateUsbMassStorageNotification(false, false); 847 Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED, 848 Uri.parse("file://" + path)); 849 intent.putExtra("read-only", readOnly); 850 mMounted = true; 851 mContext.sendBroadcast(intent); 852 } 853 854 /** 855 * Broadcasts the media shared event to all clients. 856 */ 857 void notifyMediaShared(String path, boolean mounted) { 858 if (mounted) { 859 Log.e(TAG, "Live shared mounts not supported yet!"); 860 return; 861 } 862 863 updatePublicVolumeState(path, Environment.MEDIA_SHARED); 864 865 if (mUmsActiveNotify) { 866 Intent intent = new Intent(); 867 intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class); 868 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 869 setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title, 870 com.android.internal.R.string.usb_storage_stop_notification_message, 871 com.android.internal.R.drawable.stat_sys_warning, 872 false, true, pi); 873 } 874 handlePossibleExplicitUnmountBroadcast(path); 875 Intent intent = new Intent(Intent.ACTION_MEDIA_SHARED, 876 Uri.parse("file://" + path)); 877 mContext.sendBroadcast(intent); 878 } 879 880 /** 881 * Broadcasts the media bad removal event to all clients. 882 */ 883 void notifyMediaBadRemoval(String path) { 884 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL); 885 886 updateUsbMassStorageNotification(true, false); 887 setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title, 888 com.android.internal.R.string.ext_media_badremoval_notification_message, 889 com.android.internal.R.drawable.stat_sys_warning, 890 true, true, null); 891 892 handlePossibleExplicitUnmountBroadcast(path); 893 Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, 894 Uri.parse("file://" + path)); 895 mContext.sendBroadcast(intent); 896 } 897 898 /** 899 * Broadcasts the media unmountable event to all clients. 900 */ 901 void notifyMediaUnmountable(String path) { 902 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE); 903 904 Intent intent = new Intent(); 905 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); 906 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 907 908 setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title, 909 com.android.internal.R.string.ext_media_unmountable_notification_message, 910 com.android.internal.R.drawable.stat_notify_sdcard_usb, 911 true, false, pi); 912 updateUsbMassStorageNotification(false, false); 913 914 handlePossibleExplicitUnmountBroadcast(path); 915 916 intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, 917 Uri.parse("file://" + path)); 918 mContext.sendBroadcast(intent); 919 } 920 921 /** 922 * Broadcasts the media eject event to all clients. 923 */ 924 void notifyMediaUnmounting(String path) { 925 Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT, 926 Uri.parse("file://" + path)); 927 mContext.sendBroadcast(intent); 928 } 929 930 /** 931 * Sets the USB storage notification. 932 */ 933 private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible, 934 PendingIntent pi) { 935 936 if (!visible && mUsbStorageNotification == null) { 937 return; 938 } 939 940 NotificationManager notificationManager = (NotificationManager) mContext 941 .getSystemService(Context.NOTIFICATION_SERVICE); 942 943 if (notificationManager == null) { 944 return; 945 } 946 947 if (visible) { 948 Resources r = Resources.getSystem(); 949 CharSequence title = r.getText(titleId); 950 CharSequence message = r.getText(messageId); 951 952 if (mUsbStorageNotification == null) { 953 mUsbStorageNotification = new Notification(); 954 mUsbStorageNotification.icon = icon; 955 mUsbStorageNotification.when = 0; 956 } 957 958 if (sound && mPlaySounds) { 959 mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; 960 } else { 961 mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; 962 } 963 964 mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; 965 966 mUsbStorageNotification.tickerText = title; 967 if (pi == null) { 968 Intent intent = new Intent(); 969 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); 970 } 971 972 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi); 973 } 974 975 final int notificationId = mUsbStorageNotification.icon; 976 if (visible) { 977 notificationManager.notify(notificationId, mUsbStorageNotification); 978 } else { 979 notificationManager.cancel(notificationId); 980 } 981 } 982 983 private synchronized boolean getMediaStorageNotificationDismissable() { 984 if ((mMediaStorageNotification != null) && 985 ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) == 986 Notification.FLAG_AUTO_CANCEL)) 987 return true; 988 989 return false; 990 } 991 992 /** 993 * Sets the media storage notification. 994 */ 995 private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible, 996 boolean dismissable, PendingIntent pi) { 997 998 if (!visible && mMediaStorageNotification == null) { 999 return; 1000 } 1001 1002 NotificationManager notificationManager = (NotificationManager) mContext 1003 .getSystemService(Context.NOTIFICATION_SERVICE); 1004 1005 if (notificationManager == null) { 1006 return; 1007 } 1008 1009 if (mMediaStorageNotification != null && visible) { 1010 /* 1011 * Dismiss the previous notification - we're about to 1012 * re-use it. 1013 */ 1014 final int notificationId = mMediaStorageNotification.icon; 1015 notificationManager.cancel(notificationId); 1016 } 1017 1018 if (visible) { 1019 Resources r = Resources.getSystem(); 1020 CharSequence title = r.getText(titleId); 1021 CharSequence message = r.getText(messageId); 1022 1023 if (mMediaStorageNotification == null) { 1024 mMediaStorageNotification = new Notification(); 1025 mMediaStorageNotification.when = 0; 1026 } 1027 1028 if (mPlaySounds) { 1029 mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND; 1030 } else { 1031 mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; 1032 } 1033 1034 if (dismissable) { 1035 mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; 1036 } else { 1037 mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; 1038 } 1039 1040 mMediaStorageNotification.tickerText = title; 1041 if (pi == null) { 1042 Intent intent = new Intent(); 1043 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); 1044 } 1045 1046 mMediaStorageNotification.icon = icon; 1047 mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi); 1048 } 1049 1050 final int notificationId = mMediaStorageNotification.icon; 1051 if (visible) { 1052 notificationManager.notify(notificationId, mMediaStorageNotification); 1053 } else { 1054 notificationManager.cancel(notificationId); 1055 } 1056 } 1057 1058 public String[] getSecureContainerList() throws IllegalStateException { 1059 return mConnector.doListCommand("list_asec", VoldResponseCode.AsecListResult); 1060 } 1061 1062 public String createSecureContainer(String id, int sizeMb, String fstype, 1063 String key, int ownerUid) throws IllegalStateException { 1064 String cmd = String.format("create_asec %s %d %s %s %d", 1065 id, sizeMb, fstype, key, ownerUid); 1066 mConnector.doCommand(cmd); 1067 return getSecureContainerPath(id); 1068 } 1069 1070 public void finalizeSecureContainer(String id) throws IllegalStateException { 1071 mConnector.doCommand(String.format("finalize_asec %s", id)); 1072 } 1073 1074 public void destroySecureContainer(String id) throws IllegalStateException { 1075 mConnector.doCommand(String.format("destroy_asec %s", id)); 1076 } 1077 1078 public String mountSecureContainer(String id, String key, 1079 int ownerUid) throws IllegalStateException { 1080 String cmd = String.format("mount_asec %s %s %d", 1081 id, key, ownerUid); 1082 mConnector.doCommand(cmd); 1083 return getSecureContainerPath(id); 1084 } 1085 1086 public void unmountSecureContainer(String id) throws IllegalStateException { 1087 String cmd = String.format("unmount_asec %s", id); 1088 mConnector.doCommand(cmd); 1089 } 1090 1091 public void renameSecureContainer(String oldId, String newId) throws IllegalStateException { 1092 String cmd = String.format("rename_asec %s %s", oldId, newId); 1093 mConnector.doCommand(cmd); 1094 } 1095 1096 public String getSecureContainerPath(String id) throws IllegalStateException { 1097 ArrayList<String> rsp = mConnector.doCommand("asec_path " + id); 1098 1099 for (String line : rsp) { 1100 String []tok = line.split(" "); 1101 int code = Integer.parseInt(tok[0]); 1102 if (code == VoldResponseCode.AsecPathResult) { 1103 return tok[1]; 1104 } else { 1105 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1106 } 1107 } 1108 throw new IllegalStateException("Got an empty response"); 1109 } 1110} 1111 1112