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