blob: ddf7c56d1e05448b58a7ad0b1b263558307179e9 [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) {
San Mehat7ebf0172010-01-12 07:57:42 -0800111 String action = intent.getAction();
112
113 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 Thread thread = new Thread(mListener, MountListener.class.getName());
115 thread.start();
116 }
117 }
118 };
119
San Mehat7ebf0172010-01-12 07:57:42 -0800120 public void shutdown() {
121 if (mContext.checkCallingOrSelfPermission(
122 android.Manifest.permission.SHUTDOWN)
123 != PackageManager.PERMISSION_GRANTED) {
124 throw new SecurityException("Requires SHUTDOWN permission");
125 }
126
127 Log.d(TAG, "Shutting down");
128 String state = Environment.getExternalStorageState();
129
130 if (state.equals(Environment.MEDIA_SHARED)) {
131 /*
132 * If the media is currently shared, unshare it.
133 * XXX: This is still dangerous!. We should not
134 * be rebooting at *all* if UMS is enabled, since
135 * the UMS host could have dirty FAT cache entries
136 * yet to flush.
137 */
138 try {
139 setMassStorageEnabled(false);
140 } catch (Exception e) {
141 Log.e(TAG, "ums disable failed", e);
142 }
143 } else if (state.equals(Environment.MEDIA_CHECKING)) {
144 /*
145 * If the media is being checked, then we need to wait for
146 * it to complete before being able to proceed.
147 */
148 // XXX: @hackbod - Should we disable the ANR timer here?
149 int retries = 30;
150 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
151 try {
152 Thread.sleep(1000);
153 } catch (InterruptedException iex) {
154 Log.e(TAG, "Interrupted while waiting for media", iex);
155 break;
156 }
157 state = Environment.getExternalStorageState();
158 }
159 if (retries == 0) {
160 Log.e(TAG, "Timed out waiting for media to check");
161 }
162 }
163
164 if (state.equals(Environment.MEDIA_MOUNTED)) {
165 /*
166 * If the media is mounted, then gracefully unmount it.
167 */
168 try {
169 String m = Environment.getExternalStorageDirectory().toString();
170 unmountMedia(m);
171
172 int retries = 12;
173 while (!state.equals(Environment.MEDIA_UNMOUNTED) && (retries-- >=0)) {
174 try {
175 Thread.sleep(1000);
176 } catch (InterruptedException iex) {
177 Log.e(TAG, "Interrupted while waiting for media", iex);
178 break;
179 }
180 state = Environment.getExternalStorageState();
181 }
182 if (retries == 0) {
183 Log.e(TAG, "Timed out waiting for media to unmount");
184 }
185 } catch (Exception e) {
186 Log.e(TAG, "external storage unmount failed", e);
187 }
188 }
189 }
190
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 /**
192 * @return true if USB mass storage support is enabled.
193 */
194 public boolean getMassStorageEnabled() throws RemoteException {
195 return mListener.getMassStorageEnabled();
196 }
197
198 /**
199 * Enables or disables USB mass storage support.
200 *
201 * @param enable true to enable USB mass storage support
202 */
203 public void setMassStorageEnabled(boolean enable) throws RemoteException {
204 mListener.setMassStorageEnabled(enable);
205 }
206
207 /**
208 * @return true if USB mass storage is connected.
209 */
210 public boolean getMassStorageConnected() throws RemoteException {
211 return mListener.getMassStorageConnected();
212 }
213
214 /**
215 * Attempt to mount external media
216 */
217 public void mountMedia(String mountPath) throws RemoteException {
218 if (mContext.checkCallingOrSelfPermission(
219 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
220 != PackageManager.PERMISSION_GRANTED) {
221 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
222 }
223 mListener.mountMedia(mountPath);
224 }
225
226 /**
227 * Attempt to unmount external media to prepare for eject
228 */
229 public void unmountMedia(String mountPath) throws RemoteException {
230 if (mContext.checkCallingOrSelfPermission(
231 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
232 != PackageManager.PERMISSION_GRANTED) {
233 throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
234 }
235
236 // Set a flag so that when we get the unmounted event, we know
237 // to display the notification
238 mShowSafeUnmountNotificationWhenUnmounted = true;
239
240 // tell mountd to unmount the media
241 mListener.ejectMedia(mountPath);
242 }
243
244 /**
245 * Attempt to format external media
246 */
247 public void formatMedia(String formatPath) throws RemoteException {
248 if (mContext.checkCallingOrSelfPermission(
249 android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
250 != PackageManager.PERMISSION_GRANTED) {
251 throw new SecurityException("Requires MOUNT_FORMAT_FILESYSTEMS permission");
252 }
253
254 mListener.formatMedia(formatPath);
255 }
256
257 /**
258 * Returns true if we're playing media notification sounds.
259 */
260 public boolean getPlayNotificationSounds() {
261 return mPlaySounds;
262 }
263
264 /**
265 * Set whether or not we're playing media notification sounds.
266 */
267 public void setPlayNotificationSounds(boolean enabled) {
268 if (mContext.checkCallingOrSelfPermission(
269 android.Manifest.permission.WRITE_SETTINGS)
270 != PackageManager.PERMISSION_GRANTED) {
271 throw new SecurityException("Requires WRITE_SETTINGS permission");
272 }
273 mPlaySounds = enabled;
274 SystemProperties.set("persist.service.mount.playsnd", (enabled ? "1" : "0"));
275 }
276
277 /**
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700278 * Returns true if we auto-start UMS on cable insertion.
279 */
280 public boolean getAutoStartUms() {
281 return mAutoStartUms;
282 }
283
284 /**
285 * Set whether or not we're playing media notification sounds.
286 */
287 public void setAutoStartUms(boolean enabled) {
288 if (mContext.checkCallingOrSelfPermission(
289 android.Manifest.permission.WRITE_SETTINGS)
290 != PackageManager.PERMISSION_GRANTED) {
291 throw new SecurityException("Requires WRITE_SETTINGS permission");
292 }
293 mAutoStartUms = enabled;
294 SystemProperties.set("persist.service.mount.umsauto", (enabled ? "1" : "0"));
295 }
296
297 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 * Update the state of the USB mass storage notification
299 */
300 void updateUsbMassStorageNotification(boolean suppressIfConnected, boolean sound) {
301
302 try {
303
304 if (getMassStorageConnected() && !suppressIfConnected) {
305 Intent intent = new Intent();
306 intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);
Mike Lockwood95174432009-08-26 09:44:09 -0700307 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
309 setUsbStorageNotification(
310 com.android.internal.R.string.usb_storage_notification_title,
311 com.android.internal.R.string.usb_storage_notification_message,
312 com.android.internal.R.drawable.stat_sys_data_usb,
313 sound, true, pi);
314 } else {
315 setUsbStorageNotification(0, 0, 0, false, false, null);
316 }
317 } catch (RemoteException e) {
318 // Nothing to do
319 }
320 }
321
322 void handlePossibleExplicitUnmountBroadcast(String path) {
323 if (mMounted) {
324 mMounted = false;
325 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
326 Uri.parse("file://" + path));
327 mContext.sendBroadcast(intent);
328 }
329 }
330
331 /**
332 * Broadcasts the USB mass storage connected event to all clients.
333 */
334 void notifyUmsConnected() {
335 String storageState = Environment.getExternalStorageState();
336 if (!storageState.equals(Environment.MEDIA_REMOVED) &&
337 !storageState.equals(Environment.MEDIA_BAD_REMOVAL) &&
338 !storageState.equals(Environment.MEDIA_CHECKING)) {
339
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700340 if (mAutoStartUms) {
341 try {
342 setMassStorageEnabled(true);
343 } catch (RemoteException e) {
344 }
345 } else {
346 updateUsbMassStorageNotification(false, true);
347 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 }
349
350 Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED);
351 mContext.sendBroadcast(intent);
352 }
353
354 /**
355 * Broadcasts the USB mass storage disconnected event to all clients.
356 */
357 void notifyUmsDisconnected() {
358 updateUsbMassStorageNotification(false, false);
359 Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
360 mContext.sendBroadcast(intent);
361 }
362
363 /**
364 * Broadcasts the media removed event to all clients.
365 */
366 void notifyMediaRemoved(String path) {
367 updateUsbMassStorageNotification(true, false);
368
369 setMediaStorageNotification(
370 com.android.internal.R.string.ext_media_nomedia_notification_title,
371 com.android.internal.R.string.ext_media_nomedia_notification_message,
Mike Lockwooda7ef2692009-09-10 11:15:26 -0400372 com.android.internal.R.drawable.stat_notify_sdcard_usb,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373 true, false, null);
374 handlePossibleExplicitUnmountBroadcast(path);
375
376 Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED,
377 Uri.parse("file://" + path));
378 mContext.sendBroadcast(intent);
379 }
380
381 /**
382 * Broadcasts the media unmounted event to all clients.
383 */
384 void notifyMediaUnmounted(String path) {
385 if (mShowSafeUnmountNotificationWhenUnmounted) {
386 setMediaStorageNotification(
387 com.android.internal.R.string.ext_media_safe_unmount_notification_title,
388 com.android.internal.R.string.ext_media_safe_unmount_notification_message,
Mike Lockwoodde46acdd2009-09-30 19:30:56 -0400389 com.android.internal.R.drawable.stat_notify_sdcard,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800390 true, true, null);
391 mShowSafeUnmountNotificationWhenUnmounted = false;
392 } else {
393 setMediaStorageNotification(0, 0, 0, false, false, null);
394 }
395 updateUsbMassStorageNotification(false, false);
396
397 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
398 Uri.parse("file://" + path));
399 mContext.sendBroadcast(intent);
400 }
401
402 /**
403 * Broadcasts the media checking event to all clients.
404 */
405 void notifyMediaChecking(String path) {
406 setMediaStorageNotification(
407 com.android.internal.R.string.ext_media_checking_notification_title,
408 com.android.internal.R.string.ext_media_checking_notification_message,
Mike Lockwoodde46acdd2009-09-30 19:30:56 -0400409 com.android.internal.R.drawable.stat_notify_sdcard_prepare,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 true, false, null);
411
412 updateUsbMassStorageNotification(true, false);
413 Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING,
414 Uri.parse("file://" + path));
415 mContext.sendBroadcast(intent);
416 }
417
418 /**
419 * Broadcasts the media nofs event to all clients.
420 */
421 void notifyMediaNoFs(String path) {
422
423 Intent intent = new Intent();
424 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
425 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
426
427 setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title,
428 com.android.internal.R.string.ext_media_nofs_notification_message,
Mike Lockwooda7ef2692009-09-10 11:15:26 -0400429 com.android.internal.R.drawable.stat_notify_sdcard_usb,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 true, false, pi);
431 updateUsbMassStorageNotification(false, false);
432 intent = new Intent(Intent.ACTION_MEDIA_NOFS,
433 Uri.parse("file://" + path));
434 mContext.sendBroadcast(intent);
435 }
436
437 /**
438 * Broadcasts the media mounted event to all clients.
439 */
440 void notifyMediaMounted(String path, boolean readOnly) {
441 setMediaStorageNotification(0, 0, 0, false, false, null);
442 updateUsbMassStorageNotification(false, false);
443 Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED,
444 Uri.parse("file://" + path));
445 intent.putExtra("read-only", readOnly);
446 mMounted = true;
447 mContext.sendBroadcast(intent);
448 }
449
450 /**
451 * Broadcasts the media shared event to all clients.
452 */
453 void notifyMediaShared(String path) {
454 Intent intent = new Intent();
455 intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class);
456 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
457 setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title,
458 com.android.internal.R.string.usb_storage_stop_notification_message,
459 com.android.internal.R.drawable.stat_sys_warning,
460 false, true, pi);
461 handlePossibleExplicitUnmountBroadcast(path);
462 intent = new Intent(Intent.ACTION_MEDIA_SHARED,
463 Uri.parse("file://" + path));
464 mContext.sendBroadcast(intent);
465 }
466
467 /**
468 * Broadcasts the media bad removal event to all clients.
469 */
470 void notifyMediaBadRemoval(String path) {
471 updateUsbMassStorageNotification(true, false);
472 setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title,
473 com.android.internal.R.string.ext_media_badremoval_notification_message,
474 com.android.internal.R.drawable.stat_sys_warning,
475 true, true, null);
476
477 handlePossibleExplicitUnmountBroadcast(path);
478 Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL,
479 Uri.parse("file://" + path));
480 mContext.sendBroadcast(intent);
481
482 intent = new Intent(Intent.ACTION_MEDIA_REMOVED,
483 Uri.parse("file://" + path));
484 mContext.sendBroadcast(intent);
485 }
486
487 /**
488 * Broadcasts the media unmountable event to all clients.
489 */
490 void notifyMediaUnmountable(String path) {
491 Intent intent = new Intent();
492 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
493 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
494
495 setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title,
496 com.android.internal.R.string.ext_media_unmountable_notification_message,
Mike Lockwooda7ef2692009-09-10 11:15:26 -0400497 com.android.internal.R.drawable.stat_notify_sdcard_usb,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 true, false, pi);
499 updateUsbMassStorageNotification(false, false);
500
501 handlePossibleExplicitUnmountBroadcast(path);
502
503 intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE,
504 Uri.parse("file://" + path));
505 mContext.sendBroadcast(intent);
506 }
507
508 /**
509 * Broadcasts the media eject event to all clients.
510 */
511 void notifyMediaEject(String path) {
512 Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT,
513 Uri.parse("file://" + path));
514 mContext.sendBroadcast(intent);
515 }
516
517 /**
518 * Sets the USB storage notification.
519 */
520 private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible,
521 PendingIntent pi) {
522
523 if (!visible && mUsbStorageNotification == null) {
524 return;
525 }
526
527 NotificationManager notificationManager = (NotificationManager) mContext
528 .getSystemService(Context.NOTIFICATION_SERVICE);
529
530 if (notificationManager == null) {
531 return;
532 }
533
534 if (visible) {
535 Resources r = Resources.getSystem();
536 CharSequence title = r.getText(titleId);
537 CharSequence message = r.getText(messageId);
538
539 if (mUsbStorageNotification == null) {
540 mUsbStorageNotification = new Notification();
541 mUsbStorageNotification.icon = icon;
542 mUsbStorageNotification.when = 0;
543 }
544
545 if (sound && mPlaySounds) {
546 mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
547 } else {
548 mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
549 }
550
551 mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
552
553 mUsbStorageNotification.tickerText = title;
554 if (pi == null) {
555 Intent intent = new Intent();
556 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
557 }
558
559 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
560 }
561
562 final int notificationId = mUsbStorageNotification.icon;
563 if (visible) {
564 notificationManager.notify(notificationId, mUsbStorageNotification);
565 } else {
566 notificationManager.cancel(notificationId);
567 }
568 }
569
570 private synchronized boolean getMediaStorageNotificationDismissable() {
571 if ((mMediaStorageNotification != null) &&
572 ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
573 Notification.FLAG_AUTO_CANCEL))
574 return true;
575
576 return false;
577 }
578
579 /**
580 * Sets the media storage notification.
581 */
582 private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
583 boolean dismissable, PendingIntent pi) {
584
585 if (!visible && mMediaStorageNotification == null) {
586 return;
587 }
588
589 NotificationManager notificationManager = (NotificationManager) mContext
590 .getSystemService(Context.NOTIFICATION_SERVICE);
591
592 if (notificationManager == null) {
593 return;
594 }
595
596 if (mMediaStorageNotification != null && visible) {
597 /*
598 * Dismiss the previous notification - we're about to
599 * re-use it.
600 */
601 final int notificationId = mMediaStorageNotification.icon;
602 notificationManager.cancel(notificationId);
603 }
604
605 if (visible) {
606 Resources r = Resources.getSystem();
607 CharSequence title = r.getText(titleId);
608 CharSequence message = r.getText(messageId);
609
610 if (mMediaStorageNotification == null) {
611 mMediaStorageNotification = new Notification();
612 mMediaStorageNotification.when = 0;
613 }
614
615 if (mPlaySounds) {
616 mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND;
617 } else {
618 mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
619 }
620
621 if (dismissable) {
622 mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
623 } else {
624 mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
625 }
626
627 mMediaStorageNotification.tickerText = title;
628 if (pi == null) {
629 Intent intent = new Intent();
630 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
631 }
632
633 mMediaStorageNotification.icon = icon;
634 mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
635 }
636
637 final int notificationId = mMediaStorageNotification.icon;
638 if (visible) {
639 notificationManager.notify(notificationId, mMediaStorageNotification);
640 } else {
641 notificationManager.cancel(notificationId);
642 }
643 }
644}
645