blob: 204389e8601da2a9a835767fb30115d7d59d5419 [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);
Mike Lockwood95174432009-08-26 09:44:09 -0700234 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
236 setUsbStorageNotification(
237 com.android.internal.R.string.usb_storage_notification_title,
238 com.android.internal.R.string.usb_storage_notification_message,
239 com.android.internal.R.drawable.stat_sys_data_usb,
240 sound, true, pi);
241 } else {
242 setUsbStorageNotification(0, 0, 0, false, false, null);
243 }
244 } catch (RemoteException e) {
245 // Nothing to do
246 }
247 }
248
249 void handlePossibleExplicitUnmountBroadcast(String path) {
250 if (mMounted) {
251 mMounted = false;
252 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
253 Uri.parse("file://" + path));
254 mContext.sendBroadcast(intent);
255 }
256 }
257
258 /**
259 * Broadcasts the USB mass storage connected event to all clients.
260 */
261 void notifyUmsConnected() {
262 String storageState = Environment.getExternalStorageState();
263 if (!storageState.equals(Environment.MEDIA_REMOVED) &&
264 !storageState.equals(Environment.MEDIA_BAD_REMOVAL) &&
265 !storageState.equals(Environment.MEDIA_CHECKING)) {
266
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700267 if (mAutoStartUms) {
268 try {
269 setMassStorageEnabled(true);
270 } catch (RemoteException e) {
271 }
272 } else {
273 updateUsbMassStorageNotification(false, true);
274 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 }
276
277 Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED);
278 mContext.sendBroadcast(intent);
279 }
280
281 /**
282 * Broadcasts the USB mass storage disconnected event to all clients.
283 */
284 void notifyUmsDisconnected() {
285 updateUsbMassStorageNotification(false, false);
286 Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
287 mContext.sendBroadcast(intent);
288 }
289
290 /**
291 * Broadcasts the media removed event to all clients.
292 */
293 void notifyMediaRemoved(String path) {
294 updateUsbMassStorageNotification(true, false);
295
296 setMediaStorageNotification(
297 com.android.internal.R.string.ext_media_nomedia_notification_title,
298 com.android.internal.R.string.ext_media_nomedia_notification_message,
Mike Lockwooda7ef2692009-09-10 11:15:26 -0400299 com.android.internal.R.drawable.stat_notify_sdcard_usb,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 true, false, null);
301 handlePossibleExplicitUnmountBroadcast(path);
302
303 Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED,
304 Uri.parse("file://" + path));
305 mContext.sendBroadcast(intent);
306 }
307
308 /**
309 * Broadcasts the media unmounted event to all clients.
310 */
311 void notifyMediaUnmounted(String path) {
312 if (mShowSafeUnmountNotificationWhenUnmounted) {
313 setMediaStorageNotification(
314 com.android.internal.R.string.ext_media_safe_unmount_notification_title,
315 com.android.internal.R.string.ext_media_safe_unmount_notification_message,
Mike Lockwoodde46acd2009-09-30 19:30:56 -0400316 com.android.internal.R.drawable.stat_notify_sdcard,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800317 true, true, null);
318 mShowSafeUnmountNotificationWhenUnmounted = false;
319 } else {
320 setMediaStorageNotification(0, 0, 0, false, false, null);
321 }
322 updateUsbMassStorageNotification(false, false);
323
324 Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
325 Uri.parse("file://" + path));
326 mContext.sendBroadcast(intent);
327 }
328
329 /**
330 * Broadcasts the media checking event to all clients.
331 */
332 void notifyMediaChecking(String path) {
333 setMediaStorageNotification(
334 com.android.internal.R.string.ext_media_checking_notification_title,
335 com.android.internal.R.string.ext_media_checking_notification_message,
Mike Lockwoodde46acd2009-09-30 19:30:56 -0400336 com.android.internal.R.drawable.stat_notify_sdcard_prepare,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 true, false, null);
338
339 updateUsbMassStorageNotification(true, false);
340 Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING,
341 Uri.parse("file://" + path));
342 mContext.sendBroadcast(intent);
343 }
344
345 /**
346 * Broadcasts the media nofs event to all clients.
347 */
348 void notifyMediaNoFs(String path) {
349
350 Intent intent = new Intent();
351 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
352 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
353
354 setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title,
355 com.android.internal.R.string.ext_media_nofs_notification_message,
Mike Lockwooda7ef2692009-09-10 11:15:26 -0400356 com.android.internal.R.drawable.stat_notify_sdcard_usb,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 true, false, pi);
358 updateUsbMassStorageNotification(false, false);
359 intent = new Intent(Intent.ACTION_MEDIA_NOFS,
360 Uri.parse("file://" + path));
361 mContext.sendBroadcast(intent);
362 }
363
364 /**
365 * Broadcasts the media mounted event to all clients.
366 */
367 void notifyMediaMounted(String path, boolean readOnly) {
368 setMediaStorageNotification(0, 0, 0, false, false, null);
369 updateUsbMassStorageNotification(false, false);
370 Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED,
371 Uri.parse("file://" + path));
372 intent.putExtra("read-only", readOnly);
373 mMounted = true;
374 mContext.sendBroadcast(intent);
375 }
376
377 /**
378 * Broadcasts the media shared event to all clients.
379 */
380 void notifyMediaShared(String path) {
381 Intent intent = new Intent();
382 intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class);
383 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
384 setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title,
385 com.android.internal.R.string.usb_storage_stop_notification_message,
386 com.android.internal.R.drawable.stat_sys_warning,
387 false, true, pi);
388 handlePossibleExplicitUnmountBroadcast(path);
389 intent = new Intent(Intent.ACTION_MEDIA_SHARED,
390 Uri.parse("file://" + path));
391 mContext.sendBroadcast(intent);
392 }
393
394 /**
395 * Broadcasts the media bad removal event to all clients.
396 */
397 void notifyMediaBadRemoval(String path) {
398 updateUsbMassStorageNotification(true, false);
399 setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title,
400 com.android.internal.R.string.ext_media_badremoval_notification_message,
401 com.android.internal.R.drawable.stat_sys_warning,
402 true, true, null);
403
404 handlePossibleExplicitUnmountBroadcast(path);
405 Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL,
406 Uri.parse("file://" + path));
407 mContext.sendBroadcast(intent);
408
409 intent = new Intent(Intent.ACTION_MEDIA_REMOVED,
410 Uri.parse("file://" + path));
411 mContext.sendBroadcast(intent);
412 }
413
414 /**
415 * Broadcasts the media unmountable event to all clients.
416 */
417 void notifyMediaUnmountable(String path) {
418 Intent intent = new Intent();
419 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
420 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
421
422 setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title,
423 com.android.internal.R.string.ext_media_unmountable_notification_message,
Mike Lockwooda7ef2692009-09-10 11:15:26 -0400424 com.android.internal.R.drawable.stat_notify_sdcard_usb,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 true, false, pi);
426 updateUsbMassStorageNotification(false, false);
427
428 handlePossibleExplicitUnmountBroadcast(path);
429
430 intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE,
431 Uri.parse("file://" + path));
432 mContext.sendBroadcast(intent);
433 }
434
435 /**
436 * Broadcasts the media eject event to all clients.
437 */
438 void notifyMediaEject(String path) {
439 Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT,
440 Uri.parse("file://" + path));
441 mContext.sendBroadcast(intent);
442 }
443
444 /**
445 * Sets the USB storage notification.
446 */
447 private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible,
448 PendingIntent pi) {
449
450 if (!visible && mUsbStorageNotification == null) {
451 return;
452 }
453
454 NotificationManager notificationManager = (NotificationManager) mContext
455 .getSystemService(Context.NOTIFICATION_SERVICE);
456
457 if (notificationManager == null) {
458 return;
459 }
460
461 if (visible) {
462 Resources r = Resources.getSystem();
463 CharSequence title = r.getText(titleId);
464 CharSequence message = r.getText(messageId);
465
466 if (mUsbStorageNotification == null) {
467 mUsbStorageNotification = new Notification();
468 mUsbStorageNotification.icon = icon;
469 mUsbStorageNotification.when = 0;
470 }
471
472 if (sound && mPlaySounds) {
473 mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
474 } else {
475 mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
476 }
477
478 mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
479
480 mUsbStorageNotification.tickerText = title;
481 if (pi == null) {
482 Intent intent = new Intent();
483 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
484 }
485
486 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
487 }
488
489 final int notificationId = mUsbStorageNotification.icon;
490 if (visible) {
491 notificationManager.notify(notificationId, mUsbStorageNotification);
492 } else {
493 notificationManager.cancel(notificationId);
494 }
495 }
496
497 private synchronized boolean getMediaStorageNotificationDismissable() {
498 if ((mMediaStorageNotification != null) &&
499 ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
500 Notification.FLAG_AUTO_CANCEL))
501 return true;
502
503 return false;
504 }
505
506 /**
507 * Sets the media storage notification.
508 */
509 private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
510 boolean dismissable, PendingIntent pi) {
511
512 if (!visible && mMediaStorageNotification == null) {
513 return;
514 }
515
516 NotificationManager notificationManager = (NotificationManager) mContext
517 .getSystemService(Context.NOTIFICATION_SERVICE);
518
519 if (notificationManager == null) {
520 return;
521 }
522
523 if (mMediaStorageNotification != null && visible) {
524 /*
525 * Dismiss the previous notification - we're about to
526 * re-use it.
527 */
528 final int notificationId = mMediaStorageNotification.icon;
529 notificationManager.cancel(notificationId);
530 }
531
532 if (visible) {
533 Resources r = Resources.getSystem();
534 CharSequence title = r.getText(titleId);
535 CharSequence message = r.getText(messageId);
536
537 if (mMediaStorageNotification == null) {
538 mMediaStorageNotification = new Notification();
539 mMediaStorageNotification.when = 0;
540 }
541
542 if (mPlaySounds) {
543 mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND;
544 } else {
545 mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
546 }
547
548 if (dismissable) {
549 mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
550 } else {
551 mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
552 }
553
554 mMediaStorageNotification.tickerText = title;
555 if (pi == null) {
556 Intent intent = new Intent();
557 pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
558 }
559
560 mMediaStorageNotification.icon = icon;
561 mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
562 }
563
564 final int notificationId = mMediaStorageNotification.icon;
565 if (visible) {
566 notificationManager.notify(notificationId, mMediaStorageNotification);
567 } else {
568 notificationManager.cancel(notificationId);
569 }
570 }
571}
572