MountService.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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.Context; 23import android.content.Intent; 24import android.content.pm.PackageManager; 25import android.content.res.Resources; 26import android.net.Uri; 27import android.os.IMountService; 28import android.os.Environment; 29import android.os.RemoteException; 30import android.os.UEventObserver; 31import android.util.Log; 32 33import java.io.File; 34import java.io.FileReader; 35 36/** 37 * MountService implements an to the mount service daemon 38 * @hide 39 */ 40class MountService extends IMountService.Stub { 41 42 private static final String TAG = "MountService"; 43 44 /** 45 * Binder context for this service 46 */ 47 private Context mContext; 48 49 /** 50 * listener object for communicating with the mount service daemon 51 */ 52 private MountListener mListener; 53 54 /** 55 * The notification that is shown when USB is connected. It leads the user 56 * to a dialog to enable mass storage mode. 57 * <p> 58 * This is lazily created, so use {@link #getUsbStorageNotification()}. 59 */ 60 private Notification mUsbStorageNotification; 61 62 private class SdDoorListener extends UEventObserver { 63 static final String SD_DOOR_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/sd-door"; 64 static final String SD_DOOR_SWITCH_NAME = "sd-door"; 65 66 public void onUEvent(UEvent event) { 67 if (SD_DOOR_SWITCH_NAME.equals(event.get("SWITCH_NAME"))) { 68 sdDoorStateChanged(event.get("SWITCH_STATE")); 69 } 70 } 71 }; 72 73 /** 74 * Constructs a new MountService instance 75 * 76 * @param context Binder context for this service 77 */ 78 public MountService(Context context) { 79 mContext = context; 80 mListener = new MountListener(this); 81 Thread thread = new Thread(mListener, MountListener.class.getName()); 82 thread.start(); 83 SdDoorListener sdDoorListener = new SdDoorListener(); 84 sdDoorListener.startObserving(SdDoorListener.SD_DOOR_UEVENT_MATCH); 85 } 86 87 /** 88 * @return true if USB mass storage support is enabled. 89 */ 90 public boolean getMassStorageEnabled() throws RemoteException { 91 return mListener.getMassStorageEnabled(); 92 } 93 94 /** 95 * Enables or disables USB mass storage support. 96 * 97 * @param enable true to enable USB mass storage support 98 */ 99 public void setMassStorageEnabled(boolean enable) throws RemoteException { 100 mListener.setMassStorageEnabled(enable); 101 } 102 103 /** 104 * @return true if USB mass storage is connected. 105 */ 106 public boolean getMassStorageConnected() throws RemoteException { 107 return mListener.getMassStorageConnected(); 108 } 109 110 /** 111 * Attempt to mount external media 112 */ 113 public void mountMedia(String mountPath) throws RemoteException { 114 if (mContext.checkCallingOrSelfPermission( 115 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) 116 != PackageManager.PERMISSION_GRANTED) { 117 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission"); 118 } 119 mListener.mountMedia(mountPath); 120 } 121 122 /** 123 * Attempt to unmount external media to prepare for eject 124 */ 125 public void unmountMedia(String mountPath) throws RemoteException { 126 if (mContext.checkCallingOrSelfPermission( 127 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) 128 != PackageManager.PERMISSION_GRANTED) { 129 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission"); 130 } 131 132 // tell mountd to unmount the media 133 mListener.ejectMedia(mountPath); 134 } 135 136 /** 137 * Broadcasts the USB mass storage connected event to all clients. 138 */ 139 void notifyUmsConnected() { 140 setUsbStorageNotificationVisibility(true); 141 Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED); 142 mContext.sendBroadcast(intent); 143 } 144 145 /** 146 * Broadcasts the USB mass storage disconnected event to all clients. 147 */ 148 void notifyUmsDisconnected() { 149 setUsbStorageNotificationVisibility(false); 150 Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED); 151 mContext.sendBroadcast(intent); 152 } 153 154 /** 155 * Broadcasts the media removed event to all clients. 156 */ 157 void notifyMediaRemoved(String path) { 158 Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED, 159 Uri.parse("file://" + path)); 160 mContext.sendBroadcast(intent); 161 } 162 163 /** 164 * Broadcasts the media unmounted event to all clients. 165 */ 166 void notifyMediaUnmounted(String path) { 167 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, 168 Uri.parse("file://" + path)); 169 mContext.sendBroadcast(intent); 170 } 171 172 /** 173 * Broadcasts the media mounted event to all clients. 174 */ 175 void notifyMediaMounted(String path, boolean readOnly) { 176 Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED, 177 Uri.parse("file://" + path)); 178 intent.putExtra("read-only", readOnly); 179 mContext.sendBroadcast(intent); 180 } 181 182 /** 183 * Broadcasts the media shared event to all clients. 184 */ 185 void notifyMediaShared(String path) { 186 Intent intent = new Intent(Intent.ACTION_MEDIA_SHARED, 187 Uri.parse("file://" + path)); 188 mContext.sendBroadcast(intent); 189 } 190 191 /** 192 * Broadcasts the media bad removal event to all clients. 193 */ 194 void notifyMediaBadRemoval(String path) { 195 Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, 196 Uri.parse("file://" + path)); 197 mContext.sendBroadcast(intent); 198 } 199 200 /** 201 * Broadcasts the media unmountable event to all clients. 202 */ 203 void notifyMediaUnmountable(String path) { 204 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, 205 Uri.parse("file://" + path)); 206 mContext.sendBroadcast(intent); 207 } 208 209 /** 210 * Broadcasts the media eject event to all clients. 211 */ 212 void notifyMediaEject(String path) { 213 Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT, 214 Uri.parse("file://" + path)); 215 mContext.sendBroadcast(intent); 216 } 217 218 private void sdDoorStateChanged(String doorState) { 219 File directory = Environment.getExternalStorageDirectory(); 220 String storageState = Environment.getExternalStorageState(); 221 222 if (directory != null) { 223 try { 224 if (doorState.equals("open") && (storageState.equals(Environment.MEDIA_MOUNTED) || 225 storageState.equals(Environment.MEDIA_MOUNTED_READ_ONLY))) { 226 // request SD card unmount if SD card door is opened 227 unmountMedia(directory.getPath()); 228 } else if (doorState.equals("closed") && storageState.equals(Environment.MEDIA_UNMOUNTED)) { 229 // attempt to remount SD card 230 mountMedia(directory.getPath()); 231 } 232 } catch (RemoteException e) { 233 // Nothing to do. 234 } 235 } 236 } 237 238 /** 239 * Sets the visibility of the USB storage notification. This should be 240 * called when a USB cable is connected and also when it is disconnected. 241 * 242 * @param visible Whether to show or hide the notification. 243 */ 244 private void setUsbStorageNotificationVisibility(boolean visible) { 245 NotificationManager notificationManager = (NotificationManager) mContext 246 .getSystemService(Context.NOTIFICATION_SERVICE); 247 if (notificationManager == null) { 248 return; 249 } 250 251 /* 252 * The convention for notification IDs is to use the icon's resource ID 253 * when the icon is only used by a single notification type, which is 254 * the case here. 255 */ 256 Notification notification = getUsbStorageNotification(); 257 final int notificationId = notification.icon; 258 259 if (visible) { 260 notificationManager.notify(notificationId, notification); 261 } else { 262 notificationManager.cancel(notificationId); 263 } 264 } 265 266 /** 267 * Gets the USB storage notification. 268 * 269 * @return A {@link Notification} that leads to the dialog to enable USB storage. 270 */ 271 private synchronized Notification getUsbStorageNotification() { 272 Resources r = Resources.getSystem(); 273 CharSequence title = 274 r.getText(com.android.internal.R.string.usb_storage_notification_title); 275 CharSequence message = 276 r.getText(com.android.internal.R.string.usb_storage_notification_message); 277 278 if (mUsbStorageNotification == null) { 279 mUsbStorageNotification = new Notification(); 280 mUsbStorageNotification.icon = com.android.internal.R.drawable.stat_sys_data_usb; 281 mUsbStorageNotification.when = 0; 282 mUsbStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; 283 mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; 284 } 285 286 mUsbStorageNotification.tickerText = title; 287 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, 288 getUsbStorageDialogIntent()); 289 290 return mUsbStorageNotification; 291 } 292 293 /** 294 * Creates a pending intent to start the USB storage activity. 295 * 296 * @return A {@link PendingIntent} that start the USB storage activity. 297 */ 298 private PendingIntent getUsbStorageDialogIntent() { 299 Intent intent = new Intent(); 300 intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class); 301 return PendingIntent.getActivity(mContext, 0, intent, 0); 302 } 303} 304 305