blob: 6f29332d5bc8f8f7c85e26d252095b238b94c70c [file] [log] [blame]
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001/*
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.net.Uri;
26import android.os.IMountService;
27import android.os.Environment;
28import android.os.RemoteException;
29import android.os.UEventObserver;
30import android.util.Log;
31
32import java.io.File;
33import java.io.FileReader;
34
35/**
36 * MountService implements an to the mount service daemon
37 * @hide
38 */
39class MountService extends IMountService.Stub {
40
41 private static final String TAG = "MountService";
42
43 /**
44 * Binder context for this service
45 */
46 private Context mContext;
47
48 /**
49 * listener object for communicating with the mount service daemon
50 */
51 private MountListener mListener;
52
53 /**
54 * The notification that is shown when USB is connected. It leads the user
55 * to a dialog to enable mass storage mode.
56 * <p>
57 * This is lazily created, so use {@link #getUsbStorageNotification()}.
58 */
59 private Notification mUsbStorageNotification;
60
61 private class SdDoorListener extends UEventObserver {
62 static final String SD_DOOR_UEVENT_MATCH = "DEVPATH=/class/switch/sd-door";
63
64 public void onUEvent(UEvent event) {
65 if ("sd-door".equals(event.get("SWITCH_NAME"))) {
66 sdDoorStateChanged(event.get("SWITCH_STATE"));
67 }
68 }
69 };
70
71 /**
72 * Constructs a new MountService instance
73 *
74 * @param context Binder context for this service
75 */
76 public MountService(Context context) {
77 mContext = context;
78 mListener = new MountListener(this);
79 Thread thread = new Thread(mListener, MountListener.class.getName());
80 thread.start();
81 SdDoorListener sdDoorListener = new SdDoorListener();
82 sdDoorListener.startObserving(SdDoorListener.SD_DOOR_UEVENT_MATCH);
83 }
84
85 /**
86 * @return true if USB mass storage support is enabled.
87 */
88 public boolean getMassStorageEnabled() throws RemoteException {
89 return mListener.getMassStorageEnabled();
90 }
91
92 /**
93 * Enables or disables USB mass storage support.
94 *
95 * @param enable true to enable USB mass storage support
96 */
97 public void setMassStorageEnabled(boolean enable) throws RemoteException {
98 mListener.setMassStorageEnabled(enable);
99 }
100
101 /**
102 * @return true if USB mass storage is connected.
103 */
104 public boolean getMassStorageConnected() throws RemoteException {
105 return mListener.getMassStorageConnected();
106 }
107
108 /**
109 * Attempt to mount external media
110 */
111 public void mountMedia(String mountPath) throws RemoteException {
112 if (mContext.checkCallingOrSelfPermission(
113 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
114 != PackageManager.PERMISSION_GRANTED) {
115 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
116 }
117 mListener.mountMedia(mountPath);
118 }
119
120 /**
121 * Attempt to unmount external media to prepare for eject
122 */
123 public void unmountMedia(String mountPath) throws RemoteException {
124 if (mContext.checkCallingOrSelfPermission(
125 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
126 != PackageManager.PERMISSION_GRANTED) {
127 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
128 }
129
130 // notify listeners that we are trying to eject
131 notifyMediaEject(mountPath);
132 // tell usbd 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 if (mUsbStorageNotification == null) {
273 CharSequence title =
274 mContext.getString(com.android.internal.R.string.usb_storage_notification_title);
275 CharSequence message =
276 mContext.getString(com.android.internal.R.string.usb_storage_notification_message);
277
278 mUsbStorageNotification = new Notification();
279 mUsbStorageNotification.icon = com.android.internal.R.drawable.stat_sys_data_usb;
280 mUsbStorageNotification.tickerText = title;
281 mUsbStorageNotification.when = 0;
282 mUsbStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
283 mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
284 mUsbStorageNotification.setLatestEventInfo(mContext, title, message,
285 getUsbStorageDialogIntent());
286 }
287
288 return mUsbStorageNotification;
289 }
290
291 /**
292 * Creates a pending intent to start the USB storage activity.
293 *
294 * @return A {@link PendingIntent} that start the USB storage activity.
295 */
296 private PendingIntent getUsbStorageDialogIntent() {
297 Intent intent = new Intent();
298 intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);
299 return PendingIntent.getActivity(mContext, 0, intent, 0);
300 }
301}
302