MountService.java revision fd3530f90562bb7e66edfee39d90fc8beda82f1d
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 new Thread() { 530 public void run() { 531 try { 532 if (!getVolumeState(Environment.getExternalStorageDirectory().getPath()) 533 .equals(Environment.MEDIA_MOUNTED)) { 534 try { 535 mountVolume(Environment.getExternalStorageDirectory().getPath()); 536 } catch (Exception ex) { 537 Log.w(TAG, "Connection-mount failed"); 538 } 539 } else { 540 Log.d(TAG, "Skipping connection-mount; already mounted"); 541 } 542 } catch (IllegalStateException rex) { 543 Log.e(TAG, "Exception while handling connection mount ", rex); 544 } 545 546 try { 547 boolean avail = getShareAvailable("ums"); 548 notifyShareAvailabilityChange("ums", avail); 549 } catch (Exception ex) { 550 Log.w(TAG, "Failed to get share availability"); 551 } 552 } 553 }.start(); 554 } 555 556 /** 557 * 558 * Callback from NativeDaemonConnector 559 */ 560 public boolean onEvent(int code, String raw, String[] cooked) { 561 // Log.d(TAG, "event {" + raw + "}"); 562 if (code == VoldResponseCode.VolumeStateChange) { 563 // FMT: NNN Volume <label> <mountpoint> state changed 564 // from <old_#> (<old_str>) to <new_#> (<new_str>) 565 notifyVolumeStateChange( 566 cooked[2], cooked[3], Integer.parseInt(cooked[7]), 567 Integer.parseInt(cooked[10])); 568 } else if (code == VoldResponseCode.VolumeMountFailedBlank) { 569 // FMT: NNN Volume <label> <mountpoint> mount failed - no supported file-systems 570 notifyMediaNoFs(cooked[3]); 571 // FMT: NNN Volume <label> <mountpoint> mount failed - no media 572 } else if (code == VoldResponseCode.VolumeMountFailedNoMedia) { 573 notifyMediaRemoved(cooked[3]); 574 } else if (code == VoldResponseCode.VolumeMountFailedDamaged) { 575 // FMT: NNN Volume <label> <mountpoint> mount failed - filesystem check failed 576 notifyMediaUnmountable(cooked[3]); 577 } else if (code == VoldResponseCode.ShareAvailabilityChange) { 578 // FMT: NNN Share method <method> now <available|unavailable> 579 boolean avail = false; 580 if (cooked[5].equals("available")) { 581 avail = true; 582 } 583 notifyShareAvailabilityChange(cooked[3], avail); 584 } else if (code == VoldResponseCode.VolumeDiskInserted) { 585 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>) 586 notifyMediaInserted(cooked[3]); 587 } else if (code == VoldResponseCode.VolumeDiskRemoved) { 588 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>) 589 notifyMediaRemoved(cooked[3]); 590 } else if (code == VoldResponseCode.VolumeBadRemoval) { 591 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>) 592 notifyMediaBadRemoval(cooked[3]); 593 } else { 594 return false; 595 } 596 return true; 597 } 598 599 void notifyVolumeStateChange(String label, String mountPoint, int oldState, 600 int newState) throws IllegalStateException { 601 String vs = getVolumeState(mountPoint); 602 603 if (newState == VolumeState.Init) { 604 } else if (newState == VolumeState.NoMedia) { 605 // NoMedia is handled via Disk Remove events 606 } else if (newState == VolumeState.Idle) { 607 /* 608 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or 609 * if we're in the process of enabling UMS 610 */ 611 if (!vs.equals(Environment.MEDIA_BAD_REMOVAL) && 612 !vs.equals(Environment.MEDIA_NOFS) && 613 !vs.equals(Environment.MEDIA_UNMOUNTABLE) && 614 !mUmsEnabling) { 615 notifyMediaUnmounted(mountPoint); 616 } 617 } else if (newState == VolumeState.Pending) { 618 } else if (newState == VolumeState.Checking) { 619 notifyMediaChecking(mountPoint); 620 } else if (newState == VolumeState.Mounted) { 621 notifyMediaMounted(mountPoint, false); 622 } else if (newState == VolumeState.Unmounting) { 623 notifyMediaUnmounting(mountPoint); 624 } else if (newState == VolumeState.Formatting) { 625 } else if (newState == VolumeState.Shared) { 626 notifyMediaShared(mountPoint, false); 627 } else if (newState == VolumeState.SharedMnt) { 628 notifyMediaShared(mountPoint, true); 629 } else { 630 Log.e(TAG, "Unhandled VolumeState {" + newState + "}"); 631 } 632 } 633 634 635 /** 636 * Broadcasts the USB mass storage connected event to all clients. 637 */ 638 void notifyUmsConnected() { 639 mUmsConnected = true; 640 641 String storageState = Environment.getExternalStorageState(); 642 if (!storageState.equals(Environment.MEDIA_REMOVED) && 643 !storageState.equals(Environment.MEDIA_BAD_REMOVAL) && 644 !storageState.equals(Environment.MEDIA_CHECKING)) { 645 646 if (mAutoStartUms) { 647 try { 648 setMassStorageEnabled(true); 649 } catch (IllegalStateException e) { 650 } 651 } else if (mPromptUms) { 652 updateUsbMassStorageNotification(false, true); 653 } 654 } 655 656 Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED); 657 mContext.sendBroadcast(intent); 658 } 659 660 void notifyShareAvailabilityChange(String method, final boolean avail) { 661 if (!method.equals("ums")) { 662 Log.w(TAG, "Ignoring unsupported share method {" + method + "}"); 663 return; 664 } 665 666 /* 667 * Notification needs to run in a different thread as 668 * it may need to call back into vold 669 */ 670 new Thread() { 671 public void run() { 672 try { 673 if (avail) { 674 notifyUmsConnected(); 675 } else { 676 notifyUmsDisconnected(); 677 } 678 } catch (Exception ex) { 679 Log.w(TAG, "Failed to mount media on insertion"); 680 } 681 } 682 }.start(); 683 } 684 685 /** 686 * Broadcasts the USB mass storage disconnected event to all clients. 687 */ 688 void notifyUmsDisconnected() { 689 mUmsConnected = false; 690 if (mUmsEnabled) { 691 try { 692 Log.w(TAG, "UMS disconnected while enabled!"); 693 setMassStorageEnabled(false); 694 } catch (Exception ex) { 695 Log.e(TAG, "Error disabling UMS on unsafe UMS disconnect", ex); 696 } 697 } 698 updateUsbMassStorageNotification(false, false); 699 Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED); 700 mContext.sendBroadcast(intent); 701 } 702 703 void notifyMediaInserted(final String path) throws IllegalStateException { 704 new Thread() { 705 public void run() { 706 try { 707 mountVolume(path); 708 } catch (Exception ex) { 709 Log.w(TAG, "Failed to mount media on insertion", ex); 710 } 711 } 712 }.start(); 713 } 714 715 /** 716 * Broadcasts the media removed event to all clients. 717 */ 718 void notifyMediaRemoved(String path) throws IllegalStateException { 719 720 // Suppress this on bad removal 721 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) { 722 return; 723 } 724 725 updatePublicVolumeState(path, Environment.MEDIA_REMOVED); 726 727 updateUsbMassStorageNotification(true, false); 728 729 setMediaStorageNotification( 730 com.android.internal.R.string.ext_media_nomedia_notification_title, 731 com.android.internal.R.string.ext_media_nomedia_notification_message, 732 com.android.internal.R.drawable.stat_notify_sdcard_usb, 733 true, false, null); 734 handlePossibleExplicitUnmountBroadcast(path); 735 736 Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED, 737 Uri.parse("file://" + path)); 738 mContext.sendBroadcast(intent); 739 } 740 741 /** 742 * Broadcasts the media unmounted event to all clients. 743 */ 744 void notifyMediaUnmounted(String path) { 745 746 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); 747 748 // Update media status on PackageManagerService to unmount packages on sdcard 749 mPms.updateExternalMediaStatus(false); 750 if (mShowSafeUnmountNotificationWhenUnmounted) { 751 setMediaStorageNotification( 752 com.android.internal.R.string.ext_media_safe_unmount_notification_title, 753 com.android.internal.R.string.ext_media_safe_unmount_notification_message, 754 com.android.internal.R.drawable.stat_notify_sdcard, 755 true, true, null); 756 mShowSafeUnmountNotificationWhenUnmounted = false; 757 } else { 758 setMediaStorageNotification(0, 0, 0, false, false, null); 759 } 760 updateUsbMassStorageNotification(false, false); 761 762 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, 763 Uri.parse("file://" + path)); 764 mContext.sendBroadcast(intent); 765 } 766 767 /** 768 * Broadcasts the media checking event to all clients. 769 */ 770 void notifyMediaChecking(String path) { 771 updatePublicVolumeState(path, Environment.MEDIA_CHECKING); 772 773 setMediaStorageNotification( 774 com.android.internal.R.string.ext_media_checking_notification_title, 775 com.android.internal.R.string.ext_media_checking_notification_message, 776 com.android.internal.R.drawable.stat_notify_sdcard_prepare, 777 true, false, null); 778 779 updateUsbMassStorageNotification(true, false); 780 Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING, 781 Uri.parse("file://" + path)); 782 mContext.sendBroadcast(intent); 783 } 784 785 /** 786 * Broadcasts the media nofs event to all clients. 787 */ 788 void notifyMediaNoFs(String path) { 789 updatePublicVolumeState(path, Environment.MEDIA_NOFS); 790 791 Intent intent = new Intent(); 792 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); 793 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 794 795 setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title, 796 com.android.internal.R.string.ext_media_nofs_notification_message, 797 com.android.internal.R.drawable.stat_notify_sdcard_usb, 798 true, false, pi); 799 updateUsbMassStorageNotification(false, false); 800 intent = new Intent(Intent.ACTION_MEDIA_NOFS, 801 Uri.parse("file://" + path)); 802 mContext.sendBroadcast(intent); 803 } 804 805 /** 806 * Broadcasts the media mounted event to all clients. 807 */ 808 void notifyMediaMounted(String path, boolean readOnly) { 809 updatePublicVolumeState(path, Environment.MEDIA_MOUNTED); 810 811 // Update media status on PackageManagerService to mount packages on sdcard 812 mPms.updateExternalMediaStatus(true); 813 setMediaStorageNotification(0, 0, 0, false, false, null); 814 updateUsbMassStorageNotification(false, false); 815 Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED, 816 Uri.parse("file://" + path)); 817 intent.putExtra("read-only", readOnly); 818 mMounted = true; 819 mContext.sendBroadcast(intent); 820 } 821 822 /** 823 * Broadcasts the media shared event to all clients. 824 */ 825 void notifyMediaShared(String path, boolean mounted) { 826 if (mounted) { 827 Log.e(TAG, "Live shared mounts not supported yet!"); 828 return; 829 } 830 831 updatePublicVolumeState(path, Environment.MEDIA_SHARED); 832 833 if (mUmsActiveNotify) { 834 Intent intent = new Intent(); 835 intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class); 836 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 837 setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title, 838 com.android.internal.R.string.usb_storage_stop_notification_message, 839 com.android.internal.R.drawable.stat_sys_warning, 840 false, true, pi); 841 } 842 handlePossibleExplicitUnmountBroadcast(path); 843 Intent intent = new Intent(Intent.ACTION_MEDIA_SHARED, 844 Uri.parse("file://" + path)); 845 mContext.sendBroadcast(intent); 846 } 847 848 /** 849 * Broadcasts the media bad removal event to all clients. 850 */ 851 void notifyMediaBadRemoval(String path) { 852 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL); 853 854 updateUsbMassStorageNotification(true, false); 855 setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title, 856 com.android.internal.R.string.ext_media_badremoval_notification_message, 857 com.android.internal.R.drawable.stat_sys_warning, 858 true, true, null); 859 860 handlePossibleExplicitUnmountBroadcast(path); 861 Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, 862 Uri.parse("file://" + path)); 863 mContext.sendBroadcast(intent); 864 } 865 866 /** 867 * Broadcasts the media unmountable event to all clients. 868 */ 869 void notifyMediaUnmountable(String path) { 870 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE); 871 872 Intent intent = new Intent(); 873 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); 874 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 875 876 setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title, 877 com.android.internal.R.string.ext_media_unmountable_notification_message, 878 com.android.internal.R.drawable.stat_notify_sdcard_usb, 879 true, false, pi); 880 updateUsbMassStorageNotification(false, false); 881 882 handlePossibleExplicitUnmountBroadcast(path); 883 884 intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, 885 Uri.parse("file://" + path)); 886 mContext.sendBroadcast(intent); 887 } 888 889 /** 890 * Broadcasts the media eject event to all clients. 891 */ 892 void notifyMediaUnmounting(String path) { 893 Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT, 894 Uri.parse("file://" + path)); 895 mContext.sendBroadcast(intent); 896 } 897 898 /** 899 * Sets the USB storage notification. 900 */ 901 private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible, 902 PendingIntent pi) { 903 904 if (!visible && mUsbStorageNotification == null) { 905 return; 906 } 907 908 NotificationManager notificationManager = (NotificationManager) mContext 909 .getSystemService(Context.NOTIFICATION_SERVICE); 910 911 if (notificationManager == null) { 912 return; 913 } 914 915 if (visible) { 916 Resources r = Resources.getSystem(); 917 CharSequence title = r.getText(titleId); 918 CharSequence message = r.getText(messageId); 919 920 if (mUsbStorageNotification == null) { 921 mUsbStorageNotification = new Notification(); 922 mUsbStorageNotification.icon = icon; 923 mUsbStorageNotification.when = 0; 924 } 925 926 if (sound && mPlaySounds) { 927 mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; 928 } else { 929 mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; 930 } 931 932 mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; 933 934 mUsbStorageNotification.tickerText = title; 935 if (pi == null) { 936 Intent intent = new Intent(); 937 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); 938 } 939 940 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi); 941 } 942 943 final int notificationId = mUsbStorageNotification.icon; 944 if (visible) { 945 notificationManager.notify(notificationId, mUsbStorageNotification); 946 } else { 947 notificationManager.cancel(notificationId); 948 } 949 } 950 951 private synchronized boolean getMediaStorageNotificationDismissable() { 952 if ((mMediaStorageNotification != null) && 953 ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) == 954 Notification.FLAG_AUTO_CANCEL)) 955 return true; 956 957 return false; 958 } 959 960 /** 961 * Sets the media storage notification. 962 */ 963 private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible, 964 boolean dismissable, PendingIntent pi) { 965 966 if (!visible && mMediaStorageNotification == null) { 967 return; 968 } 969 970 NotificationManager notificationManager = (NotificationManager) mContext 971 .getSystemService(Context.NOTIFICATION_SERVICE); 972 973 if (notificationManager == null) { 974 return; 975 } 976 977 if (mMediaStorageNotification != null && visible) { 978 /* 979 * Dismiss the previous notification - we're about to 980 * re-use it. 981 */ 982 final int notificationId = mMediaStorageNotification.icon; 983 notificationManager.cancel(notificationId); 984 } 985 986 if (visible) { 987 Resources r = Resources.getSystem(); 988 CharSequence title = r.getText(titleId); 989 CharSequence message = r.getText(messageId); 990 991 if (mMediaStorageNotification == null) { 992 mMediaStorageNotification = new Notification(); 993 mMediaStorageNotification.when = 0; 994 } 995 996 if (mPlaySounds) { 997 mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND; 998 } else { 999 mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; 1000 } 1001 1002 if (dismissable) { 1003 mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; 1004 } else { 1005 mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; 1006 } 1007 1008 mMediaStorageNotification.tickerText = title; 1009 if (pi == null) { 1010 Intent intent = new Intent(); 1011 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); 1012 } 1013 1014 mMediaStorageNotification.icon = icon; 1015 mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi); 1016 } 1017 1018 final int notificationId = mMediaStorageNotification.icon; 1019 if (visible) { 1020 notificationManager.notify(notificationId, mMediaStorageNotification); 1021 } else { 1022 notificationManager.cancel(notificationId); 1023 } 1024 } 1025 1026 public String[] getSecureContainerList() throws IllegalStateException { 1027 ArrayList<String> rsp = mConnector.doCommand("list_asec"); 1028 1029 String[] rdata = new String[rsp.size()]; 1030 int idx = 0; 1031 1032 for (String line : rsp) { 1033 String []tok = line.split(" "); 1034 int code = Integer.parseInt(tok[0]); 1035 if (code == VoldResponseCode.AsecListResult) { 1036 rdata[idx++] = tok[1]; 1037 } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) { 1038 return rdata; 1039 } else { 1040 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1041 } 1042 } 1043 throw new IllegalStateException("Got an empty response"); 1044 } 1045 1046 public String createSecureContainer(String id, int sizeMb, String fstype, 1047 String key, int ownerUid) throws IllegalStateException { 1048 String cmd = String.format("create_asec %s %d %s %s %d", 1049 id, sizeMb, fstype, key, ownerUid); 1050 mConnector.doCommand(cmd); 1051 return getSecureContainerPath(id); 1052 } 1053 1054 public void finalizeSecureContainer(String id) throws IllegalStateException { 1055 mConnector.doCommand(String.format("finalize_asec %s", id)); 1056 } 1057 1058 public void destroySecureContainer(String id) throws IllegalStateException { 1059 mConnector.doCommand(String.format("destroy_asec %s", id)); 1060 } 1061 1062 public String mountSecureContainer(String id, String key, 1063 int ownerUid) throws IllegalStateException { 1064 String cmd = String.format("mount_asec %s %s %d", 1065 id, key, ownerUid); 1066 mConnector.doCommand(cmd); 1067 return getSecureContainerPath(id); 1068 } 1069 1070 public void unmountSecureContainer(String id) throws IllegalStateException { 1071 String cmd = String.format("unmount_asec %s ", id); 1072 mConnector.doCommand(cmd); 1073 } 1074 1075 public String getSecureContainerPath(String id) throws IllegalStateException { 1076 ArrayList<String> rsp = mConnector.doCommand("asec_path " + id); 1077 1078 for (String line : rsp) { 1079 String []tok = line.split(" "); 1080 int code = Integer.parseInt(tok[0]); 1081 if (code == VoldResponseCode.AsecPathResult) { 1082 return tok[1]; 1083 } else { 1084 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 1085 } 1086 } 1087 throw new IllegalStateException("Got an empty response"); 1088 } 1089} 1090 1091