blob: 8814e488c9e8ed0ce8e3827afa54ea311c91f796 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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