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