diff options
Diffstat (limited to 'services/java/com/android/server/MountService.java')
-rw-r--r-- | services/java/com/android/server/MountService.java | 399 |
1 files changed, 314 insertions, 85 deletions
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 002ebeda208a..0feb1da76084 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -19,15 +19,19 @@ package com.android.server; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.Uri; import android.os.IMountService; import android.os.Environment; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UEventObserver; +import android.text.TextUtils; import android.util.Log; import java.io.File; @@ -52,24 +56,32 @@ class MountService extends IMountService.Stub { private MountListener mListener; /** - * The notification that is shown when USB is connected. It leads the user - * to a dialog to enable mass storage mode. + * The notification that is shown when a USB mass storage host + * is connected. * <p> - * This is lazily created, so use {@link #getUsbStorageNotification()}. + * This is lazily created, so use {@link #setUsbStorageNotification()}. */ private Notification mUsbStorageNotification; - private class SdDoorListener extends UEventObserver { - static final String SD_DOOR_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/sd-door"; - static final String SD_DOOR_SWITCH_NAME = "sd-door"; - public void onUEvent(UEvent event) { - if (SD_DOOR_SWITCH_NAME.equals(event.get("SWITCH_NAME"))) { - sdDoorStateChanged(event.get("SWITCH_STATE")); - } - } - }; + /** + * The notification that is shown when the following media events occur: + * - Media is being checked + * - Media is blank (or unknown filesystem) + * - Media is corrupt + * - Media is safe to unmount + * - Media is missing + * <p> + * This is lazily created, so use {@link #setMediaStorageNotification()}. + */ + private Notification mMediaStorageNotification; + private boolean mShowSafeUnmountNotificationWhenUnmounted; + + private boolean mPlaySounds; + + private boolean mMounted; + /** * Constructs a new MountService instance * @@ -77,13 +89,28 @@ class MountService extends IMountService.Stub { */ public MountService(Context context) { mContext = context; + + // Register a BOOT_COMPLETED handler so that we can start + // MountListener. We defer the startup so that we don't + // start processing events before we ought-to + mContext.registerReceiver(mBroadcastReceiver, + new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); + mListener = new MountListener(this); - Thread thread = new Thread(mListener, MountListener.class.getName()); - thread.start(); - SdDoorListener sdDoorListener = new SdDoorListener(); - sdDoorListener.startObserving(SdDoorListener.SD_DOOR_UEVENT_MATCH); + mShowSafeUnmountNotificationWhenUnmounted = false; + + mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1"); } + BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { + Thread thread = new Thread(mListener, MountListener.class.getName()); + thread.start(); + } + } + }; + /** * @return true if USB mass storage support is enabled. */ @@ -129,19 +156,92 @@ class MountService extends IMountService.Stub { throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission"); } + // Set a flag so that when we get the unmounted event, we know + // to display the notification + mShowSafeUnmountNotificationWhenUnmounted = true; + // tell mountd to unmount the media mListener.ejectMedia(mountPath); } /** + * Attempt to format external media + */ + public void formatMedia(String formatPath) throws RemoteException { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires MOUNT_FORMAT_FILESYSTEMS permission"); + } + + mListener.formatMedia(formatPath); + } + + /** + * Returns true if we're playing media notification sounds. + */ + public boolean getPlayNotificationSounds() { + return mPlaySounds; + } + + /** + * Set whether or not we're playing media notification sounds. + */ + public void setPlayNotificationSounds(boolean enabled) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.WRITE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires WRITE_SETTINGS permission"); + } + mPlaySounds = enabled; + SystemProperties.set("persist.service.mount.playsnd", (enabled ? "1" : "0")); + } + + /** + * Update the state of the USB mass storage notification + */ + void updateUsbMassStorageNotification(boolean suppressIfConnected, boolean sound) { + + try { + + if (getMassStorageConnected() && !suppressIfConnected) { + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + setUsbStorageNotification( + com.android.internal.R.string.usb_storage_notification_title, + com.android.internal.R.string.usb_storage_notification_message, + com.android.internal.R.drawable.stat_sys_data_usb, + sound, true, pi); + } else { + setUsbStorageNotification(0, 0, 0, false, false, null); + } + } catch (RemoteException e) { + // Nothing to do + } + } + + void handlePossibleExplicitUnmountBroadcast(String path) { + if (mMounted) { + mMounted = false; + Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, + Uri.parse("file://" + path)); + mContext.sendBroadcast(intent); + } + } + + /** * Broadcasts the USB mass storage connected event to all clients. */ void notifyUmsConnected() { String storageState = Environment.getExternalStorageState(); if (!storageState.equals(Environment.MEDIA_REMOVED) && - !storageState.equals(Environment.MEDIA_BAD_REMOVAL)) { - setUsbStorageNotificationVisibility(true); + !storageState.equals(Environment.MEDIA_BAD_REMOVAL) && + !storageState.equals(Environment.MEDIA_CHECKING)) { + + updateUsbMassStorageNotification(false, true); } + Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED); mContext.sendBroadcast(intent); } @@ -150,7 +250,7 @@ class MountService extends IMountService.Stub { * Broadcasts the USB mass storage disconnected event to all clients. */ void notifyUmsDisconnected() { - setUsbStorageNotificationVisibility(false); + updateUsbMassStorageNotification(false, false); Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED); mContext.sendBroadcast(intent); } @@ -159,6 +259,15 @@ class MountService extends IMountService.Stub { * Broadcasts the media removed event to all clients. */ void notifyMediaRemoved(String path) { + updateUsbMassStorageNotification(true, false); + + setMediaStorageNotification( + com.android.internal.R.string.ext_media_nomedia_notification_title, + com.android.internal.R.string.ext_media_nomedia_notification_message, + com.android.internal.R.drawable.stat_sys_no_sim, + true, false, null); + handlePossibleExplicitUnmountBroadcast(path); + Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path)); mContext.sendBroadcast(intent); @@ -168,18 +277,68 @@ class MountService extends IMountService.Stub { * Broadcasts the media unmounted event to all clients. */ void notifyMediaUnmounted(String path) { + if (mShowSafeUnmountNotificationWhenUnmounted) { + setMediaStorageNotification( + com.android.internal.R.string.ext_media_safe_unmount_notification_title, + com.android.internal.R.string.ext_media_safe_unmount_notification_message, + com.android.internal.R.drawable.stat_notify_sim_toolkit, + true, true, null); + mShowSafeUnmountNotificationWhenUnmounted = false; + } else { + setMediaStorageNotification(0, 0, 0, false, false, null); + } + updateUsbMassStorageNotification(false, false); + Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path)); mContext.sendBroadcast(intent); } /** + * Broadcasts the media checking event to all clients. + */ + void notifyMediaChecking(String path) { + setMediaStorageNotification( + com.android.internal.R.string.ext_media_checking_notification_title, + com.android.internal.R.string.ext_media_checking_notification_message, + com.android.internal.R.drawable.stat_notify_sim_toolkit, + true, false, null); + + updateUsbMassStorageNotification(true, false); + Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING, + Uri.parse("file://" + path)); + mContext.sendBroadcast(intent); + } + + /** + * Broadcasts the media nofs event to all clients. + */ + void notifyMediaNoFs(String path) { + + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + + setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title, + com.android.internal.R.string.ext_media_nofs_notification_message, + com.android.internal.R.drawable.stat_sys_no_sim, + true, false, pi); + updateUsbMassStorageNotification(false, false); + intent = new Intent(Intent.ACTION_MEDIA_NOFS, + Uri.parse("file://" + path)); + mContext.sendBroadcast(intent); + } + + /** * Broadcasts the media mounted event to all clients. */ void notifyMediaMounted(String path, boolean readOnly) { + setMediaStorageNotification(0, 0, 0, false, false, null); + updateUsbMassStorageNotification(false, false); Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path)); intent.putExtra("read-only", readOnly); + mMounted = true; mContext.sendBroadcast(intent); } @@ -187,7 +346,15 @@ class MountService extends IMountService.Stub { * Broadcasts the media shared event to all clients. */ void notifyMediaShared(String path) { - Intent intent = new Intent(Intent.ACTION_MEDIA_SHARED, + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title, + com.android.internal.R.string.usb_storage_stop_notification_message, + com.android.internal.R.drawable.stat_sys_warning, + false, true, pi); + handlePossibleExplicitUnmountBroadcast(path); + intent = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path)); mContext.sendBroadcast(intent); } @@ -196,16 +363,39 @@ class MountService extends IMountService.Stub { * Broadcasts the media bad removal event to all clients. */ void notifyMediaBadRemoval(String path) { + updateUsbMassStorageNotification(true, false); + setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title, + com.android.internal.R.string.ext_media_badremoval_notification_message, + com.android.internal.R.drawable.stat_sys_warning, + true, true, null); + + handlePossibleExplicitUnmountBroadcast(path); Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path)); mContext.sendBroadcast(intent); + + intent = new Intent(Intent.ACTION_MEDIA_REMOVED, + Uri.parse("file://" + path)); + mContext.sendBroadcast(intent); } /** * Broadcasts the media unmountable event to all clients. */ void notifyMediaUnmountable(String path) { - Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + + setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title, + com.android.internal.R.string.ext_media_unmountable_notification_message, + com.android.internal.R.drawable.stat_sys_no_sim, + true, false, pi); + updateUsbMassStorageNotification(false, false); + + handlePossibleExplicitUnmountBroadcast(path); + + intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path)); mContext.sendBroadcast(intent); } @@ -219,90 +409,129 @@ class MountService extends IMountService.Stub { mContext.sendBroadcast(intent); } - private void sdDoorStateChanged(String doorState) { - File directory = Environment.getExternalStorageDirectory(); - String storageState = Environment.getExternalStorageState(); - - if (directory != null) { - try { - if (doorState.equals("open") && (storageState.equals(Environment.MEDIA_MOUNTED) || - storageState.equals(Environment.MEDIA_MOUNTED_READ_ONLY))) { - // request SD card unmount if SD card door is opened - unmountMedia(directory.getPath()); - } else if (doorState.equals("closed") && storageState.equals(Environment.MEDIA_UNMOUNTED)) { - // attempt to remount SD card - mountMedia(directory.getPath()); - } - } catch (RemoteException e) { - // Nothing to do. - } - } - } - /** - * Sets the visibility of the USB storage notification. This should be - * called when a USB cable is connected and also when it is disconnected. - * - * @param visible Whether to show or hide the notification. + * Sets the USB storage notification. */ - private void setUsbStorageNotificationVisibility(boolean visible) { + private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible, + PendingIntent pi) { + + if (!visible && mUsbStorageNotification == null) { + return; + } + NotificationManager notificationManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager == null) { return; } - - /* - * The convention for notification IDs is to use the icon's resource ID - * when the icon is only used by a single notification type, which is - * the case here. - */ - Notification notification = getUsbStorageNotification(); - final int notificationId = notification.icon; if (visible) { - notificationManager.notify(notificationId, notification); + Resources r = Resources.getSystem(); + CharSequence title = r.getText(titleId); + CharSequence message = r.getText(messageId); + + if (mUsbStorageNotification == null) { + mUsbStorageNotification = new Notification(); + mUsbStorageNotification.icon = icon; + mUsbStorageNotification.when = 0; + } + + if (sound && mPlaySounds) { + mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; + } else { + mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; + } + + mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; + + mUsbStorageNotification.tickerText = title; + if (pi == null) { + Intent intent = new Intent(); + pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); + } + + mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi); + } + + final int notificationId = mUsbStorageNotification.icon; + if (visible) { + notificationManager.notify(notificationId, mUsbStorageNotification); } else { notificationManager.cancel(notificationId); } } + private synchronized boolean getMediaStorageNotificationDismissable() { + if ((mMediaStorageNotification != null) && + ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) == + Notification.FLAG_AUTO_CANCEL)) + return true; + + return false; + } + /** - * Gets the USB storage notification. - * - * @return A {@link Notification} that leads to the dialog to enable USB storage. + * Sets the media storage notification. */ - private synchronized Notification getUsbStorageNotification() { - Resources r = Resources.getSystem(); - CharSequence title = - r.getText(com.android.internal.R.string.usb_storage_notification_title); - CharSequence message = - r.getText(com.android.internal.R.string.usb_storage_notification_message); - - if (mUsbStorageNotification == null) { - mUsbStorageNotification = new Notification(); - mUsbStorageNotification.icon = com.android.internal.R.drawable.stat_sys_data_usb; - mUsbStorageNotification.when = 0; - mUsbStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; - mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; + private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible, + boolean dismissable, PendingIntent pi) { + + if (!visible && mMediaStorageNotification == null) { + return; } - mUsbStorageNotification.tickerText = title; - mUsbStorageNotification.setLatestEventInfo(mContext, title, message, - getUsbStorageDialogIntent()); + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); - return mUsbStorageNotification; - } + if (notificationManager == null) { + return; + } + + if (mMediaStorageNotification != null && visible) { + /* + * Dismiss the previous notification - we're about to + * re-use it. + */ + final int notificationId = mMediaStorageNotification.icon; + notificationManager.cancel(notificationId); + } + + if (visible) { + Resources r = Resources.getSystem(); + CharSequence title = r.getText(titleId); + CharSequence message = r.getText(messageId); + + if (mMediaStorageNotification == null) { + mMediaStorageNotification = new Notification(); + mMediaStorageNotification.when = 0; + if (mPlaySounds) { + mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND; + } + } + + if (dismissable) { + mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; + } else { + mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; + } + + mMediaStorageNotification.tickerText = title; + if (pi == null) { + Intent intent = new Intent(); + pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); + } + + mMediaStorageNotification.icon = icon; + mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi); + } - /** - * Creates a pending intent to start the USB storage activity. - * - * @return A {@link PendingIntent} that start the USB storage activity. - */ - private PendingIntent getUsbStorageDialogIntent() { - Intent intent = new Intent(); - intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class); - return PendingIntent.getActivity(mContext, 0, intent, 0); + final int notificationId = mMediaStorageNotification.icon; + if (visible) { + notificationManager.notify(notificationId, mMediaStorageNotification); + } else { + notificationManager.cancel(notificationId); + } } } |