blob: f81c5198d0c7a2791b0ba0af1e43ab27b68d9384 [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
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070085 private boolean mAutoStartUms;
86
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 /**
88 * Constructs a new MountService instance
89 *
90 * @param context Binder context for this service
91 */
92 public MountService(Context context) {
93 mContext = context;
94
95 // Register a BOOT_COMPLETED handler so that we can start
96 // MountListener. We defer the startup so that we don't
97 // start processing events before we ought-to
98 mContext.registerReceiver(mBroadcastReceiver,
99 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
100
101 mListener = new MountListener(this);
102 mShowSafeUnmountNotificationWhenUnmounted = false;
103
104 mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1");
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700105
106 mAutoStartUms = SystemProperties.get("persist.service.mount.umsauto", "0").equals("1");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 }
108
109 BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
110 public void onReceive(Context context, Intent intent) {
111 if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
112 Thread thread = new Thread(mListener, MountListener.class.getName());
113 thread.start();
114 }
115 }
116 };
117
118 /**
119 * @return true if USB mass storage support is enabled.
120 */
121 public boolean getMassStorageEnabled() throws RemoteException {
122 return mListener.getMassStorageEnabled();
123 }
124
125 /**
126 * Enables or disables USB mass storage support.
127 *
128 * @param enable true to enable USB mass storage support
129 */
130 public void setMassStorageEnabled(boolean enable) throws RemoteException {
131 mListener.setMassStorageEnabled(enable);
132 }
133
134 /**
135 * @return true if USB mass storage is connected.
136 */
137 public boolean getMassStorageConnected() throws RemoteException {
138 return mListener.getMassStorageConnected();
139 }
140
141 /**
142 * Attempt to mount external media
143 */
144 public void mountMedia(String mountPath) throws RemoteException {
145 if (mContext.checkCallingOrSelfPermission(
146 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
147 != PackageManager.PERMISSION_GRANTED) {
148 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
149 }
150 mListener.mountMedia(mountPath);
151 }
152
153 /**
154 * Attempt to unmount external media to prepare for eject
155 */
156 public void unmountMedia(String mountPath) throws RemoteException {
157 if (mContext.checkCallingOrSelfPermission(
158 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
159 != PackageManager.PERMISSION_GRANTED) {
160 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
161 }
162
163 // Set a flag so that when we get the unmounted event, we know
164 // to display the notification
165 mShowSafeUnmountNotificationWhenUnmounted = true;
166
167 // tell mountd to unmount the media
168 mListener.ejectMedia(mountPath);
169 }
170
171 /**
172 * Attempt to format external media
173 */
174 public void formatMedia(String formatPath) throws RemoteException {
175 if (mContext.checkCallingOrSelfPermission(
176 android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
177 != PackageManager.PERMISSION_GRANTED) {
178 throw new SecurityException("Requires MOUNT_FORMAT_FILESYSTEMS permission");
179 }
180
181 mListener.formatMedia(formatPath);
182 }
183
184 /**
185 * Returns true if we're playing media notification sounds.
186 */
187 public boolean getPlayNotificationSounds() {
188 return mPlaySounds;
189 }
190
191 /**
192 * Set whether or not we're playing media notification sounds.
193 */
194 public void setPlayNotificationSounds(boolean enabled) {
195 if (mContext.checkCallingOrSelfPermission(
196 android.Manifest.permission.WRITE_SETTINGS)
197 != PackageManager.PERMISSION_GRANTED) {
198 throw new SecurityException("Requires WRITE_SETTINGS permission");
199 }
200 mPlaySounds = enabled;
201 SystemProperties.set("persist.service.mount.playsnd", (enabled ? "1" : "0"));
202 }
203
204 /**
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700205 * Returns true if we auto-start UMS on cable insertion.
206 */
207 public boolean getAutoStartUms() {
208 return mAutoStartUms;
209 }
210
211 /**
212 * Set whether or not we're playing media notification sounds.
213 */
214 public void setAutoStartUms(boolean enabled) {
215 if (mContext.checkCallingOrSelfPermission(
216 android.Manifest.permission.WRITE_SETTINGS)
217 != PackageManager.PERMISSION_GRANTED) {
218 throw new SecurityException("Requires WRITE_SETTINGS permission");
219 }
220 mAutoStartUms = enabled;
221 SystemProperties.set("persist.service.mount.umsauto", (enabled ? "1" : "0"));
222 }
223
224 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 * Update the state of the USB mass storage notification
226 */
227 void updateUsbMassStorageNotification(boolean suppressIfConnected, boolean sound) {
228
229 try {
230
231 if (getMassStorageConnected() && !suppressIfConnected) {
232 Intent intent = new Intent();
233 intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);
234 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
235 setUsbStorageNotification(
236 com.android.internal.R.string.usb_storage_notification_title,
237 com.android.internal.R.string.usb_storage_notification_message,
238 com.android.internal.R.drawable.stat_sys_data_usb,
239 sound, true, pi);
240 } else {
241 setUsbStorageNotification(0, 0, 0, false, false, null);
242 }
243 } catch (RemoteException e) {
244 // Nothing to do
245 }
246 }
247
248 void handlePossibleExplicitUnmountBroadcast(String path) {
249 if (mMounted) {
250 mMounted = false;
251 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
252 Uri.parse("file://" + path));
253 mContext.sendBroadcast(intent);
254 }
255 }
256
257 /**
258 * Broadcasts the USB mass storage connected event to all clients.
259 */
260 void notifyUmsConnected() {
261 String storageState = Environment.getExternalStorageState();
262 if (!storageState.equals(Environment.MEDIA_REMOVED) &&
263 !storageState.equals(Environment.MEDIA_BAD_REMOVAL) &&
264 !storageState.equals(Environment.MEDIA_CHECKING)) {
265
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700266 if (mAutoStartUms) {
267 try {
268 setMassStorageEnabled(true);
269 } catch (RemoteException e) {
270 }
271 } else {
272 updateUsbMassStorageNotification(false, true);
273 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800274 }
275
276 Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED);
277 mContext.sendBroadcast(intent);
278 }
279
280 /**
281 * Broadcasts the USB mass storage disconnected event to all clients.
282 */
283 void notifyUmsDisconnected() {
284 updateUsbMassStorageNotification(false, false);
285 Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
286 mContext.sendBroadcast(intent);
287 }
288
289 /**
290 * Broadcasts the media removed event to all clients.
291 */
292 void notifyMediaRemoved(String path) {
293 updateUsbMassStorageNotification(true, false);
294
295 setMediaStorageNotification(
296 com.android.internal.R.string.ext_media_nomedia_notification_title,
297 com.android.internal.R.string.ext_media_nomedia_notification_message,
298 com.android.internal.R.drawable.stat_sys_no_sim,
299 true, false, null);
300 handlePossibleExplicitUnmountBroadcast(path);
301
302 Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED,
303 Uri.parse("file://" + path));
304 mContext.sendBroadcast(intent);
305 }
306
307 /**
308 * Broadcasts the media unmounted event to all clients.
309 */
310 void notifyMediaUnmounted(String path) {
311 if (mShowSafeUnmountNotificationWhenUnmounted) {
312 setMediaStorageNotification(
313 com.android.internal.R.string.ext_media_safe_unmount_notification_title,
314 com.android.internal.R.string.ext_media_safe_unmount_notification_message,
315 com.android.internal.R.drawable.stat_notify_sim_toolkit,
316 true, true, null);
317 mShowSafeUnmountNotificationWhenUnmounted = false;
318 } else {
319 setMediaStorageNotification(0, 0, 0, false, false, null);
320 }
321 updateUsbMassStorageNotification(false, false);
322
323 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
324 Uri.parse("file://" + path));
325 mContext.sendBroadcast(intent);
326 }
327
328 /**
329 * Broadcasts the media checking event to all clients.
330 */
331 void notifyMediaChecking(String path) {
332 setMediaStorageNotification(
333 com.android.internal.R.string.ext_media_checking_notification_title,
334 com.android.internal.R.string.ext_media_checking_notification_message,
335 com.android.internal.R.drawable.stat_notify_sim_toolkit,
336 true, false, null);
337
338 updateUsbMassStorageNotification(true, false);
339 Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING,
340 Uri.parse("file://" + path));
341 mContext.sendBroadcast(intent);
342 }
343
344 /**
345 * Broadcasts the media nofs event to all clients.
346 */
347 void notifyMediaNoFs(String path) {
348
349 Intent intent = new Intent();
350 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
351 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
352
353 setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title,
354 com.android.internal.R.string.ext_media_nofs_notification_message,
355 com.android.internal.R.drawable.stat_sys_no_sim,
356 true, false, pi);
357 updateUsbMassStorageNotification(false, false);
358 intent = new Intent(Intent.ACTION_MEDIA_NOFS,
359 Uri.parse("file://" + path));
360 mContext.sendBroadcast(intent);
361 }
362
363 /**
364 * Broadcasts the media mounted event to all clients.
365 */
366 void notifyMediaMounted(String path, boolean readOnly) {
367 setMediaStorageNotification(0, 0, 0, false, false, null);
368 updateUsbMassStorageNotification(false, false);
369 Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED,
370 Uri.parse("file://" + path));
371 intent.putExtra("read-only", readOnly);
372 mMounted = true;
373 mContext.sendBroadcast(intent);
374 }
375
376 /**
377 * Broadcasts the media shared event to all clients.
378 */
379 void notifyMediaShared(String path) {
380 Intent intent = new Intent();
381 intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class);
382 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
383 setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title,
384 com.android.internal.R.string.usb_storage_stop_notification_message,
385 com.android.internal.R.drawable.stat_sys_warning,
386 false, true, pi);
387 handlePossibleExplicitUnmountBroadcast(path);
388 intent = new Intent(Intent.ACTION_MEDIA_SHARED,
389 Uri.parse("file://" + path));
390 mContext.sendBroadcast(intent);
391 }
392
393 /**
394 * Broadcasts the media bad removal event to all clients.
395 */
396 void notifyMediaBadRemoval(String path) {
397 updateUsbMassStorageNotification(true, false);
398 setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title,
399 com.android.internal.R.string.ext_media_badremoval_notification_message,
400 com.android.internal.R.drawable.stat_sys_warning,
401 true, true, null);
402
403 handlePossibleExplicitUnmountBroadcast(path);
404 Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL,
405 Uri.parse("file://" + path));
406 mContext.sendBroadcast(intent);
407
408 intent = new Intent(Intent.ACTION_MEDIA_REMOVED,
409 Uri.parse("file://" + path));
410 mContext.sendBroadcast(intent);
411 }
412
413 /**
414 * Broadcasts the media unmountable event to all clients.
415 */
416 void notifyMediaUnmountable(String path) {
417 Intent intent = new Intent();
418 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
419 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
420
421 setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title,
422 com.android.internal.R.string.ext_media_unmountable_notification_message,
423 com.android.internal.R.drawable.stat_sys_no_sim,
424 true, false, pi);
425 updateUsbMassStorageNotification(false, false);
426
427 handlePossibleExplicitUnmountBroadcast(path);
428
429 intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE,
430 Uri.parse("file://" + path));
431 mContext.sendBroadcast(intent);
432 }
433
434 /**
435 * Broadcasts the media eject event to all clients.
436 */
437 void notifyMediaEject(String path) {
438 Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT,
439 Uri.parse("file://" + path));
440 mContext.sendBroadcast(intent);
441 }
442
443 /**
444 * Sets the USB storage notification.
445 */
446 private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible,
447 PendingIntent pi) {
448
449 if (!visible && mUsbStorageNotification == null) {
450 return;
451 }
452
453 NotificationManager notificationManager = (NotificationManager) mContext
454 .getSystemService(Context.NOTIFICATION_SERVICE);
455
456 if (notificationManager == null) {
457 return;
458 }
459
460 if (visible) {
461 Resources r = Resources.getSystem();
462 CharSequence title = r.getText(titleId);
463 CharSequence message = r.getText(messageId);
464
465 if (mUsbStorageNotification == null) {
466 mUsbStorageNotification = new Notification();
467 mUsbStorageNotification.icon = icon;
468 mUsbStorageNotification.when = 0;
469 }
470
471 if (sound && mPlaySounds) {
472 mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
473 } else {
474 mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
475 }
476
477 mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
478
479 mUsbStorageNotification.tickerText = title;
480 if (pi == null) {
481 Intent intent = new Intent();
482 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
483 }
484
485 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
486 }
487
488 final int notificationId = mUsbStorageNotification.icon;
489 if (visible) {
490 notificationManager.notify(notificationId, mUsbStorageNotification);
491 } else {
492 notificationManager.cancel(notificationId);
493 }
494 }
495
496 private synchronized boolean getMediaStorageNotificationDismissable() {
497 if ((mMediaStorageNotification != null) &&
498 ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
499 Notification.FLAG_AUTO_CANCEL))
500 return true;
501
502 return false;
503 }
504
505 /**
506 * Sets the media storage notification.
507 */
508 private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
509 boolean dismissable, PendingIntent pi) {
510
511 if (!visible && mMediaStorageNotification == null) {
512 return;
513 }
514
515 NotificationManager notificationManager = (NotificationManager) mContext
516 .getSystemService(Context.NOTIFICATION_SERVICE);
517
518 if (notificationManager == null) {
519 return;
520 }
521
522 if (mMediaStorageNotification != null && visible) {
523 /*
524 * Dismiss the previous notification - we're about to
525 * re-use it.
526 */
527 final int notificationId = mMediaStorageNotification.icon;
528 notificationManager.cancel(notificationId);
529 }
530
531 if (visible) {
532 Resources r = Resources.getSystem();
533 CharSequence title = r.getText(titleId);
534 CharSequence message = r.getText(messageId);
535
536 if (mMediaStorageNotification == null) {
537 mMediaStorageNotification = new Notification();
538 mMediaStorageNotification.when = 0;
539 }
540
541 if (mPlaySounds) {
542 mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND;
543 } else {
544 mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
545 }
546
547 if (dismissable) {
548 mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
549 } else {
550 mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
551 }
552
553 mMediaStorageNotification.tickerText = title;
554 if (pi == null) {
555 Intent intent = new Intent();
556 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
557 }
558
559 mMediaStorageNotification.icon = icon;
560 mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
561 }
562
563 final int notificationId = mMediaStorageNotification.icon;
564 if (visible) {
565 notificationManager.notify(notificationId, mMediaStorageNotification);
566 } else {
567 notificationManager.cancel(notificationId);
568 }
569 }
570}
571