MountService.java revision ba87e3e6c985e7175152993b5efcc7dd2f0e1c93
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.RemoteException; 32import android.os.SystemProperties; 33import android.os.UEventObserver; 34import android.text.TextUtils; 35import android.util.Log; 36 37import java.io.File; 38import java.io.FileReader; 39 40/** 41 * MountService implements an to the mount service daemon 42 * @hide 43 */ 44class MountService extends IMountService.Stub { 45 46 private static final String TAG = "MountService"; 47 48 /** 49 * Binder context for this service 50 */ 51 private Context mContext; 52 53 /** 54 * listener object for communicating with the mount service daemon 55 */ 56 private MountListener mListener; 57 58 /** 59 * The notification that is shown when a USB mass storage host 60 * is connected. 61 * <p> 62 * This is lazily created, so use {@link #setUsbStorageNotification()}. 63 */ 64 private Notification mUsbStorageNotification; 65 66 67 /** 68 * The notification that is shown when the following media events occur: 69 * - Media is being checked 70 * - Media is blank (or unknown filesystem) 71 * - Media is corrupt 72 * - Media is safe to unmount 73 * - Media is missing 74 * <p> 75 * This is lazily created, so use {@link #setMediaStorageNotification()}. 76 */ 77 private Notification mMediaStorageNotification; 78 79 private boolean mShowSafeUnmountNotificationWhenUnmounted; 80 81 private boolean mPlaySounds; 82 83 private boolean mMounted; 84 85 private boolean mAutoStartUms; 86 87 /** 88 * Constructs a new MountService instance 89 * 90 * @param context Binder context for this service 91 */ 92 public MountService(Context context) { 93 mContext = context; 94 95 // Register a BOOT_COMPLETED handler so that we can start 96 // MountListener. We defer the startup so that we don't 97 // start processing events before we ought-to 98 mContext.registerReceiver(mBroadcastReceiver, 99 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); 100 101 mListener = new MountListener(this); 102 mShowSafeUnmountNotificationWhenUnmounted = false; 103 104 mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1"); 105 106 mAutoStartUms = SystemProperties.get("persist.service.mount.umsauto", "0").equals("1"); 107 } 108 109 BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 110 public void onReceive(Context context, Intent intent) { 111 if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { 112 Thread thread = new Thread(mListener, MountListener.class.getName()); 113 thread.start(); 114 } 115 } 116 }; 117 118 /** 119 * @return true if USB mass storage support is enabled. 120 */ 121 public boolean getMassStorageEnabled() throws RemoteException { 122 return mListener.getMassStorageEnabled(); 123 } 124 125 /** 126 * Enables or disables USB mass storage support. 127 * 128 * @param enable true to enable USB mass storage support 129 */ 130 public void setMassStorageEnabled(boolean enable) throws RemoteException { 131 mListener.setMassStorageEnabled(enable); 132 } 133 134 /** 135 * @return true if USB mass storage is connected. 136 */ 137 public boolean getMassStorageConnected() throws RemoteException { 138 return mListener.getMassStorageConnected(); 139 } 140 141 /** 142 * Attempt to mount external media 143 */ 144 public void mountMedia(String mountPath) throws RemoteException { 145 if (mContext.checkCallingOrSelfPermission( 146 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) 147 != PackageManager.PERMISSION_GRANTED) { 148 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission"); 149 } 150 mListener.mountMedia(mountPath); 151 } 152 153 /** 154 * Attempt to unmount external media to prepare for eject 155 */ 156 public void unmountMedia(String mountPath) throws RemoteException { 157 if (mContext.checkCallingOrSelfPermission( 158 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) 159 != PackageManager.PERMISSION_GRANTED) { 160 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission"); 161 } 162 163 // Set a flag so that when we get the unmounted event, we know 164 // to display the notification 165 mShowSafeUnmountNotificationWhenUnmounted = true; 166 167 // tell mountd to unmount the media 168 mListener.ejectMedia(mountPath); 169 } 170 171 /** 172 * Attempt to format external media 173 */ 174 public void formatMedia(String formatPath) throws RemoteException { 175 if (mContext.checkCallingOrSelfPermission( 176 android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS) 177 != PackageManager.PERMISSION_GRANTED) { 178 throw new SecurityException("Requires MOUNT_FORMAT_FILESYSTEMS permission"); 179 } 180 181 mListener.formatMedia(formatPath); 182 } 183 184 /** 185 * Returns true if we're playing media notification sounds. 186 */ 187 public boolean getPlayNotificationSounds() { 188 return mPlaySounds; 189 } 190 191 /** 192 * Set whether or not we're playing media notification sounds. 193 */ 194 public void setPlayNotificationSounds(boolean enabled) { 195 if (mContext.checkCallingOrSelfPermission( 196 android.Manifest.permission.WRITE_SETTINGS) 197 != PackageManager.PERMISSION_GRANTED) { 198 throw new SecurityException("Requires WRITE_SETTINGS permission"); 199 } 200 mPlaySounds = enabled; 201 SystemProperties.set("persist.service.mount.playsnd", (enabled ? "1" : "0")); 202 } 203 204 /** 205 * Returns true if we auto-start UMS on cable insertion. 206 */ 207 public boolean getAutoStartUms() { 208 return mAutoStartUms; 209 } 210 211 /** 212 * Set whether or not we're playing media notification sounds. 213 */ 214 public void setAutoStartUms(boolean enabled) { 215 if (mContext.checkCallingOrSelfPermission( 216 android.Manifest.permission.WRITE_SETTINGS) 217 != PackageManager.PERMISSION_GRANTED) { 218 throw new SecurityException("Requires WRITE_SETTINGS permission"); 219 } 220 mAutoStartUms = enabled; 221 SystemProperties.set("persist.service.mount.umsauto", (enabled ? "1" : "0")); 222 } 223 224 /** 225 * Update the state of the USB mass storage notification 226 */ 227 void updateUsbMassStorageNotification(boolean suppressIfConnected, boolean sound) { 228 229 try { 230 231 if (getMassStorageConnected() && !suppressIfConnected) { 232 Intent intent = new Intent(); 233 intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class); 234 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 235 setUsbStorageNotification( 236 com.android.internal.R.string.usb_storage_notification_title, 237 com.android.internal.R.string.usb_storage_notification_message, 238 com.android.internal.R.drawable.stat_sys_data_usb, 239 sound, true, pi); 240 } else { 241 setUsbStorageNotification(0, 0, 0, false, false, null); 242 } 243 } catch (RemoteException e) { 244 // Nothing to do 245 } 246 } 247 248 void handlePossibleExplicitUnmountBroadcast(String path) { 249 if (mMounted) { 250 mMounted = false; 251 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, 252 Uri.parse("file://" + path)); 253 mContext.sendBroadcast(intent); 254 } 255 } 256 257 /** 258 * Broadcasts the USB mass storage connected event to all clients. 259 */ 260 void notifyUmsConnected() { 261 String storageState = Environment.getExternalStorageState(); 262 if (!storageState.equals(Environment.MEDIA_REMOVED) && 263 !storageState.equals(Environment.MEDIA_BAD_REMOVAL) && 264 !storageState.equals(Environment.MEDIA_CHECKING)) { 265 266 if (mAutoStartUms) { 267 try { 268 setMassStorageEnabled(true); 269 } catch (RemoteException e) { 270 } 271 } else { 272 updateUsbMassStorageNotification(false, true); 273 } 274 } 275 276 Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED); 277 mContext.sendBroadcast(intent); 278 } 279 280 /** 281 * Broadcasts the USB mass storage disconnected event to all clients. 282 */ 283 void notifyUmsDisconnected() { 284 updateUsbMassStorageNotification(false, false); 285 Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED); 286 mContext.sendBroadcast(intent); 287 } 288 289 /** 290 * Broadcasts the media removed event to all clients. 291 */ 292 void notifyMediaRemoved(String path) { 293 updateUsbMassStorageNotification(true, false); 294 295 setMediaStorageNotification( 296 com.android.internal.R.string.ext_media_nomedia_notification_title, 297 com.android.internal.R.string.ext_media_nomedia_notification_message, 298 com.android.internal.R.drawable.stat_sys_no_sim, 299 true, false, null); 300 handlePossibleExplicitUnmountBroadcast(path); 301 302 Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED, 303 Uri.parse("file://" + path)); 304 mContext.sendBroadcast(intent); 305 } 306 307 /** 308 * Broadcasts the media unmounted event to all clients. 309 */ 310 void notifyMediaUnmounted(String path) { 311 if (mShowSafeUnmountNotificationWhenUnmounted) { 312 setMediaStorageNotification( 313 com.android.internal.R.string.ext_media_safe_unmount_notification_title, 314 com.android.internal.R.string.ext_media_safe_unmount_notification_message, 315 com.android.internal.R.drawable.stat_notify_sim_toolkit, 316 true, true, null); 317 mShowSafeUnmountNotificationWhenUnmounted = false; 318 } else { 319 setMediaStorageNotification(0, 0, 0, false, false, null); 320 } 321 updateUsbMassStorageNotification(false, false); 322 323 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, 324 Uri.parse("file://" + path)); 325 mContext.sendBroadcast(intent); 326 } 327 328 /** 329 * Broadcasts the media checking event to all clients. 330 */ 331 void notifyMediaChecking(String path) { 332 setMediaStorageNotification( 333 com.android.internal.R.string.ext_media_checking_notification_title, 334 com.android.internal.R.string.ext_media_checking_notification_message, 335 com.android.internal.R.drawable.stat_notify_sim_toolkit, 336 true, false, null); 337 338 updateUsbMassStorageNotification(true, false); 339 Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING, 340 Uri.parse("file://" + path)); 341 mContext.sendBroadcast(intent); 342 } 343 344 /** 345 * Broadcasts the media nofs event to all clients. 346 */ 347 void notifyMediaNoFs(String path) { 348 349 Intent intent = new Intent(); 350 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); 351 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 352 353 setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title, 354 com.android.internal.R.string.ext_media_nofs_notification_message, 355 com.android.internal.R.drawable.stat_sys_no_sim, 356 true, false, pi); 357 updateUsbMassStorageNotification(false, false); 358 intent = new Intent(Intent.ACTION_MEDIA_NOFS, 359 Uri.parse("file://" + path)); 360 mContext.sendBroadcast(intent); 361 } 362 363 /** 364 * Broadcasts the media mounted event to all clients. 365 */ 366 void notifyMediaMounted(String path, boolean readOnly) { 367 setMediaStorageNotification(0, 0, 0, false, false, null); 368 updateUsbMassStorageNotification(false, false); 369 Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED, 370 Uri.parse("file://" + path)); 371 intent.putExtra("read-only", readOnly); 372 mMounted = true; 373 mContext.sendBroadcast(intent); 374 } 375 376 /** 377 * Broadcasts the media shared event to all clients. 378 */ 379 void notifyMediaShared(String path) { 380 Intent intent = new Intent(); 381 intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class); 382 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 383 setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title, 384 com.android.internal.R.string.usb_storage_stop_notification_message, 385 com.android.internal.R.drawable.stat_sys_warning, 386 false, true, pi); 387 handlePossibleExplicitUnmountBroadcast(path); 388 intent = new Intent(Intent.ACTION_MEDIA_SHARED, 389 Uri.parse("file://" + path)); 390 mContext.sendBroadcast(intent); 391 } 392 393 /** 394 * Broadcasts the media bad removal event to all clients. 395 */ 396 void notifyMediaBadRemoval(String path) { 397 updateUsbMassStorageNotification(true, false); 398 setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title, 399 com.android.internal.R.string.ext_media_badremoval_notification_message, 400 com.android.internal.R.drawable.stat_sys_warning, 401 true, true, null); 402 403 handlePossibleExplicitUnmountBroadcast(path); 404 Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, 405 Uri.parse("file://" + path)); 406 mContext.sendBroadcast(intent); 407 408 intent = new Intent(Intent.ACTION_MEDIA_REMOVED, 409 Uri.parse("file://" + path)); 410 mContext.sendBroadcast(intent); 411 } 412 413 /** 414 * Broadcasts the media unmountable event to all clients. 415 */ 416 void notifyMediaUnmountable(String path) { 417 Intent intent = new Intent(); 418 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); 419 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 420 421 setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title, 422 com.android.internal.R.string.ext_media_unmountable_notification_message, 423 com.android.internal.R.drawable.stat_sys_no_sim, 424 true, false, pi); 425 updateUsbMassStorageNotification(false, false); 426 427 handlePossibleExplicitUnmountBroadcast(path); 428 429 intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, 430 Uri.parse("file://" + path)); 431 mContext.sendBroadcast(intent); 432 } 433 434 /** 435 * Broadcasts the media eject event to all clients. 436 */ 437 void notifyMediaEject(String path) { 438 Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT, 439 Uri.parse("file://" + path)); 440 mContext.sendBroadcast(intent); 441 } 442 443 /** 444 * Sets the USB storage notification. 445 */ 446 private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible, 447 PendingIntent pi) { 448 449 if (!visible && mUsbStorageNotification == null) { 450 return; 451 } 452 453 NotificationManager notificationManager = (NotificationManager) mContext 454 .getSystemService(Context.NOTIFICATION_SERVICE); 455 456 if (notificationManager == null) { 457 return; 458 } 459 460 if (visible) { 461 Resources r = Resources.getSystem(); 462 CharSequence title = r.getText(titleId); 463 CharSequence message = r.getText(messageId); 464 465 if (mUsbStorageNotification == null) { 466 mUsbStorageNotification = new Notification(); 467 mUsbStorageNotification.icon = icon; 468 mUsbStorageNotification.when = 0; 469 } 470 471 if (sound && mPlaySounds) { 472 mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; 473 } else { 474 mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; 475 } 476 477 mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; 478 479 mUsbStorageNotification.tickerText = title; 480 if (pi == null) { 481 Intent intent = new Intent(); 482 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); 483 } 484 485 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi); 486 } 487 488 final int notificationId = mUsbStorageNotification.icon; 489 if (visible) { 490 notificationManager.notify(notificationId, mUsbStorageNotification); 491 } else { 492 notificationManager.cancel(notificationId); 493 } 494 } 495 496 private synchronized boolean getMediaStorageNotificationDismissable() { 497 if ((mMediaStorageNotification != null) && 498 ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) == 499 Notification.FLAG_AUTO_CANCEL)) 500 return true; 501 502 return false; 503 } 504 505 /** 506 * Sets the media storage notification. 507 */ 508 private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible, 509 boolean dismissable, PendingIntent pi) { 510 511 if (!visible && mMediaStorageNotification == null) { 512 return; 513 } 514 515 NotificationManager notificationManager = (NotificationManager) mContext 516 .getSystemService(Context.NOTIFICATION_SERVICE); 517 518 if (notificationManager == null) { 519 return; 520 } 521 522 if (mMediaStorageNotification != null && visible) { 523 /* 524 * Dismiss the previous notification - we're about to 525 * re-use it. 526 */ 527 final int notificationId = mMediaStorageNotification.icon; 528 notificationManager.cancel(notificationId); 529 } 530 531 if (visible) { 532 Resources r = Resources.getSystem(); 533 CharSequence title = r.getText(titleId); 534 CharSequence message = r.getText(messageId); 535 536 if (mMediaStorageNotification == null) { 537 mMediaStorageNotification = new Notification(); 538 mMediaStorageNotification.when = 0; 539 } 540 541 if (mPlaySounds) { 542 mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND; 543 } else { 544 mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; 545 } 546 547 if (dismissable) { 548 mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; 549 } else { 550 mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; 551 } 552 553 mMediaStorageNotification.tickerText = title; 554 if (pi == null) { 555 Intent intent = new Intent(); 556 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); 557 } 558 559 mMediaStorageNotification.icon = icon; 560 mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi); 561 } 562 563 final int notificationId = mMediaStorageNotification.icon; 564 if (visible) { 565 notificationManager.notify(notificationId, mMediaStorageNotification); 566 } else { 567 notificationManager.cancel(notificationId); 568 } 569 } 570} 571 572