diff options
Diffstat (limited to 'services/java/com/android/server/MountService.java')
-rw-r--r-- | services/java/com/android/server/MountService.java | 540 |
1 files changed, 540 insertions, 0 deletions
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java new file mode 100644 index 0000000..8814e48 --- /dev/null +++ b/services/java/com/android/server/MountService.java @@ -0,0 +1,540 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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; +import java.io.FileReader; + +/** + * MountService implements an to the mount service daemon + * @hide + */ +class MountService extends IMountService.Stub { + + private static final String TAG = "MountService"; + + /** + * Binder context for this service + */ + private Context mContext; + + /** + * listener object for communicating with the mount service daemon + */ + private MountListener mListener; + + /** + * The notification that is shown when a USB mass storage host + * is connected. + * <p> + * This is lazily created, so use {@link #setUsbStorageNotification()}. + */ + private Notification mUsbStorageNotification; + + + /** + * 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 + * + * @param context Binder context for this service + */ + 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); + 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. + */ + public boolean getMassStorageEnabled() throws RemoteException { + return mListener.getMassStorageEnabled(); + } + + /** + * Enables or disables USB mass storage support. + * + * @param enable true to enable USB mass storage support + */ + public void setMassStorageEnabled(boolean enable) throws RemoteException { + mListener.setMassStorageEnabled(enable); + } + + /** + * @return true if USB mass storage is connected. + */ + public boolean getMassStorageConnected() throws RemoteException { + return mListener.getMassStorageConnected(); + } + + /** + * Attempt to mount external media + */ + public void mountMedia(String mountPath) throws RemoteException { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission"); + } + mListener.mountMedia(mountPath); + } + + /** + * Attempt to unmount external media to prepare for eject + */ + public void unmountMedia(String mountPath) throws RemoteException { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) + != PackageManager.PERMISSION_GRANTED) { + 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) && + !storageState.equals(Environment.MEDIA_CHECKING)) { + + updateUsbMassStorageNotification(false, true); + } + + Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED); + mContext.sendBroadcast(intent); + } + + /** + * Broadcasts the USB mass storage disconnected event to all clients. + */ + void notifyUmsDisconnected() { + updateUsbMassStorageNotification(false, false); + Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED); + mContext.sendBroadcast(intent); + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * Broadcasts the media shared event to all clients. + */ + void notifyMediaShared(String path) { + 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); + } + + /** + * 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.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); + } + + /** + * Broadcasts the media eject event to all clients. + */ + void notifyMediaEject(String path) { + Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT, + Uri.parse("file://" + path)); + mContext.sendBroadcast(intent); + } + + /** + * Sets the USB storage notification. + */ + 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; + } + + if (visible) { + 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; + } + + /** + * Sets the media storage notification. + */ + private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible, + boolean dismissable, PendingIntent pi) { + + if (!visible && mMediaStorageNotification == null) { + return; + } + + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + 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; + } else { + 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); + } + + final int notificationId = mMediaStorageNotification.icon; + if (visible) { + notificationManager.notify(notificationId, mMediaStorageNotification); + } else { + notificationManager.cancel(notificationId); + } + } +} + |