MountService.java revision 1f6301e1ff1a8ba04bc2b9c55fe6ceb883ce43bf
1/*
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.SystemProperties;
32import android.os.UEventObserver;
33import android.os.Handler;
34import android.text.TextUtils;
35import android.util.Log;
36
37import android.provider.Settings;
38import android.content.ContentResolver;
39import android.database.ContentObserver;
40
41import java.io.File;
42import java.io.FileReader;
43import java.lang.IllegalStateException;
44
45/**
46 * MountService implements an to the mount service daemon
47 * @hide
48 */
49class MountService extends IMountService.Stub {
50
51    private static final String TAG = "MountService";
52
53    class VolumeState {
54        public static final int Init       = -1;
55        public static final int NoMedia    = 0;
56        public static final int Idle       = 1;
57        public static final int Pending    = 2;
58        public static final int Checking   = 3;
59        public static final int Mounted    = 4;
60        public static final int Unmounting = 5;
61        public static final int Formatting = 6;
62        public static final int Shared     = 7;
63        public static final int SharedMnt  = 8;
64    }
65
66    /**
67     * Binder context for this service
68     */
69    private Context mContext;
70
71    /**
72     * listener object for communicating with the mount service daemon
73     */
74    private MountListener mListener;
75
76    /**
77     * The notification that is shown when a USB mass storage host
78     * is connected.
79     * <p>
80     * This is lazily created, so use {@link #setUsbStorageNotification()}.
81     */
82    private Notification mUsbStorageNotification;
83
84
85    /**
86     * The notification that is shown when the following media events occur:
87     *     - Media is being checked
88     *     - Media is blank (or unknown filesystem)
89     *     - Media is corrupt
90     *     - Media is safe to unmount
91     *     - Media is missing
92     * <p>
93     * This is lazily created, so use {@link #setMediaStorageNotification()}.
94     */
95    private Notification mMediaStorageNotification;
96
97    private boolean mShowSafeUnmountNotificationWhenUnmounted;
98
99    private boolean mPlaySounds;
100
101    private boolean mMounted;
102
103    private SettingsWatcher mSettingsWatcher;
104    private boolean mAutoStartUms;
105    private boolean mPromptUms;
106    private boolean mUmsActiveNotify;
107
108    private boolean mUmsConnected = false;
109    private boolean mUmsEnabled = false;
110
111    private String  mLegacyState = Environment.MEDIA_REMOVED;
112
113    /**
114     * Constructs a new MountService instance
115     *
116     * @param context  Binder context for this service
117     */
118    public MountService(Context context) {
119        mContext = context;
120
121        // Register a BOOT_COMPLETED handler so that we can start
122        // MountListener. We defer the startup so that we don't
123        // start processing events before we ought-to
124        mContext.registerReceiver(mBroadcastReceiver,
125                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
126
127        mListener =  new MountListener(this);
128        mShowSafeUnmountNotificationWhenUnmounted = false;
129
130        mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1");
131
132        ContentResolver cr = mContext.getContentResolver();
133        mAutoStartUms = (Settings.Secure.getInt(
134                cr, Settings.Secure.MOUNT_UMS_AUTOSTART, 0) == 1);
135        mPromptUms = (Settings.Secure.getInt(
136                cr, Settings.Secure.MOUNT_UMS_PROMPT, 1) == 1);
137        mUmsActiveNotify = (Settings.Secure.getInt(
138                cr, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED, 1) == 1);
139
140        mSettingsWatcher = new SettingsWatcher(new Handler());
141    }
142
143    private class SettingsWatcher extends ContentObserver {
144        public SettingsWatcher(Handler handler) {
145            super(handler);
146            ContentResolver cr = mContext.getContentResolver();
147            cr.registerContentObserver(Settings.System.getUriFor(
148                    Settings.Secure.MOUNT_PLAY_NOTIFICATION_SND), false, this);
149            cr.registerContentObserver(Settings.Secure.getUriFor(
150                    Settings.Secure.MOUNT_UMS_AUTOSTART), false, this);
151            cr.registerContentObserver(Settings.Secure.getUriFor(
152                    Settings.Secure.MOUNT_UMS_PROMPT), false, this);
153            cr.registerContentObserver(Settings.Secure.getUriFor(
154                    Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED), false, this);
155        }
156
157        public void onChange(boolean selfChange) {
158            super.onChange(selfChange);
159            ContentResolver cr = mContext.getContentResolver();
160
161            boolean newPlayNotificationSounds = (Settings.Secure.getInt(
162                    cr, Settings.Secure.MOUNT_PLAY_NOTIFICATION_SND, 1) == 1);
163
164            boolean newUmsAutostart = (Settings.Secure.getInt(
165                    cr, Settings.Secure.MOUNT_UMS_AUTOSTART, 0) == 1);
166
167            if (newUmsAutostart != mAutoStartUms) {
168                Log.d(TAG, "Changing UMS autostart to " + newUmsAutostart);
169                mAutoStartUms = newUmsAutostart;
170            }
171
172            boolean newUmsPrompt = (Settings.Secure.getInt(
173                    cr, Settings.Secure.MOUNT_UMS_PROMPT, 1) == 1);
174
175            if (newUmsPrompt != mPromptUms) {
176                Log.d(TAG, "Changing UMS prompt to " + newUmsPrompt);
177                mPromptUms = newUmsAutostart;
178            }
179
180            boolean newUmsNotifyEnabled = (Settings.Secure.getInt(
181                    cr, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED, 1) == 1);
182
183            Log.d(TAG, "new notify enabled = " + newUmsNotifyEnabled);
184            if (mUmsEnabled) {
185                if (newUmsNotifyEnabled) {
186                    Intent intent = new Intent();
187                    intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class);
188                    PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
189                    setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title,
190                            com.android.internal.R.string.usb_storage_stop_notification_message,
191                            com.android.internal.R.drawable.stat_sys_warning,
192                            false, true, pi);
193                } else {
194                    setUsbStorageNotification(0, 0, 0, false, false, null);
195                }
196            }
197            if (newUmsNotifyEnabled != mUmsActiveNotify) {
198                Log.d(TAG, "Changing UMS active notification to " + newUmsNotifyEnabled);
199                mUmsActiveNotify = newUmsNotifyEnabled;
200            }
201        }
202    }
203
204    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
205        public void onReceive(Context context, Intent intent) {
206            String action = intent.getAction();
207
208            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
209                Thread thread = new Thread(mListener, MountListener.class.getName());
210                thread.start();
211            }
212        }
213    };
214
215    public void shutdown() {
216        if (mContext.checkCallingOrSelfPermission(
217                android.Manifest.permission.SHUTDOWN)
218                != PackageManager.PERMISSION_GRANTED) {
219            throw new SecurityException("Requires SHUTDOWN permission");
220        }
221
222        Log.d(TAG, "Shutting down");
223        String state = Environment.getExternalStorageState();
224
225        if (state.equals(Environment.MEDIA_SHARED)) {
226            /*
227             * If the media is currently shared, unshare it.
228             * XXX: This is still dangerous!. We should not
229             * be rebooting at *all* if UMS is enabled, since
230             * the UMS host could have dirty FAT cache entries
231             * yet to flush.
232             */
233            try {
234               setMassStorageEnabled(false);
235            } catch (Exception e) {
236                Log.e(TAG, "ums disable failed", e);
237            }
238        } else if (state.equals(Environment.MEDIA_CHECKING)) {
239            /*
240             * If the media is being checked, then we need to wait for
241             * it to complete before being able to proceed.
242             */
243            // XXX: @hackbod - Should we disable the ANR timer here?
244            int retries = 30;
245            while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
246                try {
247                    Thread.sleep(1000);
248                } catch (InterruptedException iex) {
249                    Log.e(TAG, "Interrupted while waiting for media", iex);
250                    break;
251                }
252                state = Environment.getExternalStorageState();
253            }
254            if (retries == 0) {
255                Log.e(TAG, "Timed out waiting for media to check");
256            }
257        }
258
259        if (state.equals(Environment.MEDIA_MOUNTED)) {
260            /*
261             * If the media is mounted, then gracefully unmount it.
262             */
263            try {
264                String m = Environment.getExternalStorageDirectory().toString();
265                unmountMedia(m);
266            } catch (Exception e) {
267                Log.e(TAG, "external storage unmount failed", e);
268            }
269        }
270    }
271
272    /**
273     * @return true if USB mass storage support is enabled.
274     */
275    public boolean getMassStorageEnabled() {
276        return mUmsEnabled;
277    }
278
279    /**
280     * Enables or disables USB mass storage support.
281     *
282     * @param enable  true to enable USB mass storage support
283     */
284    public void setMassStorageEnabled(boolean enable) throws IllegalStateException {
285        if (mContext.checkCallingOrSelfPermission(
286                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
287                != PackageManager.PERMISSION_GRANTED) {
288            throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
289        }
290        try {
291            String vp = Environment.getExternalStorageDirectory().getPath();
292            String vs = getVolumeState(vp);
293
294            if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
295                mListener.unmountVolume(vp);
296                updateUsbMassStorageNotification(true, false);
297            }
298
299            mListener.setShareMethodEnabled(Environment
300                                            .getExternalStorageDirectory()
301                                            .getPath(),
302                                            "ums", enable);
303            mUmsEnabled = enable;
304            if (!enable) {
305                mountMedia(vp);
306                if (mPromptUms) {
307                    updateUsbMassStorageNotification(false, false);
308                } else {
309                    updateUsbMassStorageNotification(true, false);
310                }
311            }
312        } catch (IllegalStateException rex) {
313            Log.e(TAG, "Failed to set ums enable {" + enable + "}");
314            return;
315        }
316    }
317
318    /**
319     * @return true if USB mass storage is connected.
320     */
321    public boolean getMassStorageConnected() {
322        return mUmsConnected;
323    }
324
325    /**
326     * @return state of the volume at the specified mount point
327     */
328    public String getVolumeState(String mountPoint) throws IllegalStateException {
329        /*
330         * XXX: Until we have multiple volume discovery, just hardwire
331         * this to /sdcard
332         */
333        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
334            Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
335            throw new IllegalArgumentException();
336        }
337
338        return mLegacyState;
339    }
340
341
342    /**
343     * Attempt to mount external media
344     */
345    public void mountMedia(String mountPath) throws IllegalStateException {
346        if (mContext.checkCallingOrSelfPermission(
347                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
348                != PackageManager.PERMISSION_GRANTED) {
349            throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
350        }
351        mListener.mountVolume(mountPath);
352    }
353
354    /**
355     * Attempt to unmount external media to prepare for eject
356     */
357    public void unmountMedia(String mountPath) throws IllegalStateException {
358        if (mContext.checkCallingOrSelfPermission(
359                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
360                != PackageManager.PERMISSION_GRANTED) {
361            throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
362        }
363
364        // Set a flag so that when we get the unmounted event, we know
365        // to display the notification
366        mShowSafeUnmountNotificationWhenUnmounted = true;
367
368        // tell mountd to unmount the media
369        mListener.unmountVolume(mountPath);
370    }
371
372    /**
373     * Attempt to format external media
374     */
375    public void formatMedia(String formatPath) throws IllegalStateException {
376        if (mContext.checkCallingOrSelfPermission(
377                android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
378                != PackageManager.PERMISSION_GRANTED) {
379            throw new SecurityException("Requires MOUNT_FORMAT_FILESYSTEMS permission");
380        }
381
382        mListener.formatVolume(formatPath);
383    }
384
385    /**
386     * Returns true if we're playing media notification sounds.
387     */
388    public boolean getPlayNotificationSounds() {
389        return mPlaySounds;
390    }
391
392    /**
393     * Set whether or not we're playing media notification sounds.
394     */
395    public void setPlayNotificationSounds(boolean enabled) {
396        if (mContext.checkCallingOrSelfPermission(
397                android.Manifest.permission.WRITE_SETTINGS)
398                != PackageManager.PERMISSION_GRANTED) {
399            throw new SecurityException("Requires WRITE_SETTINGS permission");
400        }
401        mPlaySounds = enabled;
402        SystemProperties.set("persist.service.mount.playsnd", (enabled ? "1" : "0"));
403    }
404
405    void updatePublicVolumeState(String mountPoint, String state) {
406        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
407            Log.w(TAG, "Multiple volumes not currently supported");
408            return;
409        }
410        Log.w(TAG, "State for {" + mountPoint + "} = {" + state + "}");
411        mLegacyState = state;
412    }
413
414    /**
415     * Update the state of the USB mass storage notification
416     */
417    void updateUsbMassStorageNotification(boolean suppressIfConnected, boolean sound) {
418
419        try {
420
421            if (getMassStorageConnected() && !suppressIfConnected) {
422                Intent intent = new Intent();
423                intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);
424                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
425                PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
426                setUsbStorageNotification(
427                        com.android.internal.R.string.usb_storage_notification_title,
428                        com.android.internal.R.string.usb_storage_notification_message,
429                        com.android.internal.R.drawable.stat_sys_data_usb,
430                        sound, true, pi);
431            } else {
432                setUsbStorageNotification(0, 0, 0, false, false, null);
433            }
434        } catch (IllegalStateException e) {
435            // Nothing to do
436        }
437    }
438
439    void handlePossibleExplicitUnmountBroadcast(String path) {
440        if (mMounted) {
441            mMounted = false;
442            Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
443                    Uri.parse("file://" + path));
444            mContext.sendBroadcast(intent);
445        }
446    }
447
448    void onVoldConnected() {
449        new Thread() {
450            public void run() {
451                try {
452                    if (!getVolumeState(Environment.getExternalStorageDirectory().getPath())
453                                 .equals(Environment.MEDIA_MOUNTED)) {
454                        try {
455                            mountMedia(Environment.getExternalStorageDirectory().getPath());
456                            Log.d(TAG, "Connection-mount suceeded");
457                        } catch (Exception ex) {
458                            Log.w(TAG, "Connection-mount failed");
459                        }
460                    } else {
461                        Log.d(TAG, "Skipping connection-mount; already mounted");
462                    }
463                } catch (IllegalStateException rex) {
464                    Log.e(TAG, "Exception while handling connection mount ", rex);
465                }
466
467                try {
468                    boolean avail = mListener.getShareAvailable("ums");
469                    notifyShareAvailabilityChange("ums", avail);
470                } catch (Exception ex) {
471                    Log.w(TAG, "Failed to get share availability");
472                }
473            }
474        }.start();
475    }
476
477    void notifyVolumeStateChange(String label, String mountPoint, int oldState,
478                                 int newState) throws IllegalStateException {
479        String vs = getVolumeState(mountPoint);
480
481        if (newState == VolumeState.Init) {
482        } else if (newState == VolumeState.NoMedia) {
483            // NoMedia is handled via Disk Remove events
484        } else if (newState == VolumeState.Idle) {
485            // Don't notify if we're in BAD_REMOVAL, NOFS, or UNMOUNTABLE
486            if (!vs.equals(Environment.MEDIA_BAD_REMOVAL) &&
487                !vs.equals(Environment.MEDIA_NOFS) &&
488                !vs.equals(Environment.MEDIA_UNMOUNTABLE)) {
489                notifyMediaUnmounted(mountPoint);
490            }
491        } else if (newState == VolumeState.Pending) {
492        } else if (newState == VolumeState.Checking) {
493            notifyMediaChecking(mountPoint);
494        } else if (newState == VolumeState.Mounted) {
495            notifyMediaMounted(mountPoint, false);
496        } else if (newState == VolumeState.Unmounting) {
497            notifyMediaUnmounting(mountPoint);
498        } else if (newState == VolumeState.Formatting) {
499        } else if (newState == VolumeState.Shared) {
500            notifyMediaShared(mountPoint, false);
501        } else if (newState == VolumeState.SharedMnt) {
502            notifyMediaShared(mountPoint, true);
503        } else {
504            Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
505        }
506    }
507
508
509    /**
510     * Broadcasts the USB mass storage connected event to all clients.
511     */
512    void notifyUmsConnected() {
513        mUmsConnected = true;
514
515        String storageState = Environment.getExternalStorageState();
516        if (!storageState.equals(Environment.MEDIA_REMOVED) &&
517            !storageState.equals(Environment.MEDIA_BAD_REMOVAL) &&
518            !storageState.equals(Environment.MEDIA_CHECKING)) {
519
520            if (mAutoStartUms) {
521                try {
522                    setMassStorageEnabled(true);
523                } catch (IllegalStateException e) {
524                }
525            } else if (mPromptUms) {
526                updateUsbMassStorageNotification(false, true);
527            }
528        }
529
530        Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED);
531        mContext.sendBroadcast(intent);
532    }
533
534    void notifyShareAvailabilityChange(String method, final boolean avail) {
535        if (!method.equals("ums")) {
536           Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
537           return;
538        }
539
540        /*
541         * Notification needs to run in a different thread as
542         * it may need to call back into vold
543         */
544        new Thread() {
545            public void run() {
546                try {
547                    if (avail) {
548                        notifyUmsConnected();
549                    } else {
550                        notifyUmsDisconnected();
551                    }
552                } catch (Exception ex) {
553                    Log.w(TAG, "Failed to mount media on insertion");
554                }
555            }
556        }.start();
557    }
558
559    /**
560     * Broadcasts the USB mass storage disconnected event to all clients.
561     */
562    void notifyUmsDisconnected() {
563        mUmsConnected = false;
564        if (mUmsEnabled) {
565            try {
566                Log.w(TAG, "UMS disconnected while enabled!");
567                setMassStorageEnabled(false);
568            } catch (Exception ex) {
569                Log.e(TAG, "Error disabling UMS on unsafe UMS disconnect", ex);
570            }
571        }
572        updateUsbMassStorageNotification(false, false);
573        Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
574        mContext.sendBroadcast(intent);
575    }
576
577    void notifyMediaInserted(final String path) throws IllegalStateException {
578        new Thread() {
579            public void run() {
580                try {
581                    Log.d(TAG, "Mounting media after insertion");
582                    mountMedia(path);
583                } catch (Exception ex) {
584                    Log.w(TAG, "Failed to mount media on insertion", ex);
585                }
586            }
587        }.start();
588    }
589
590    /**
591     * Broadcasts the media removed event to all clients.
592     */
593    void notifyMediaRemoved(String path) throws IllegalStateException {
594
595        // Suppress this on bad removal
596        if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
597            return;
598        }
599
600        updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
601
602        updateUsbMassStorageNotification(true, false);
603
604        setMediaStorageNotification(
605            com.android.internal.R.string.ext_media_nomedia_notification_title,
606            com.android.internal.R.string.ext_media_nomedia_notification_message,
607            com.android.internal.R.drawable.stat_notify_sdcard_usb,
608            true, false, null);
609        handlePossibleExplicitUnmountBroadcast(path);
610
611        Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED,
612                Uri.parse("file://" + path));
613        mContext.sendBroadcast(intent);
614    }
615
616    /**
617     * Broadcasts the media unmounted event to all clients.
618     */
619    void notifyMediaUnmounted(String path) {
620
621        updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
622
623        if (mShowSafeUnmountNotificationWhenUnmounted) {
624            setMediaStorageNotification(
625                    com.android.internal.R.string.ext_media_safe_unmount_notification_title,
626                    com.android.internal.R.string.ext_media_safe_unmount_notification_message,
627                    com.android.internal.R.drawable.stat_notify_sdcard,
628                    true, true, null);
629            mShowSafeUnmountNotificationWhenUnmounted = false;
630        } else {
631            setMediaStorageNotification(0, 0, 0, false, false, null);
632        }
633        updateUsbMassStorageNotification(false, false);
634
635        Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
636                Uri.parse("file://" + path));
637        mContext.sendBroadcast(intent);
638    }
639
640    /**
641     * Broadcasts the media checking event to all clients.
642     */
643    void notifyMediaChecking(String path) {
644        updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
645
646        setMediaStorageNotification(
647                com.android.internal.R.string.ext_media_checking_notification_title,
648                com.android.internal.R.string.ext_media_checking_notification_message,
649                com.android.internal.R.drawable.stat_notify_sdcard_prepare,
650                true, false, null);
651
652        updateUsbMassStorageNotification(true, false);
653        Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING,
654                Uri.parse("file://" + path));
655        mContext.sendBroadcast(intent);
656    }
657
658    /**
659     * Broadcasts the media nofs event to all clients.
660     */
661    void notifyMediaNoFs(String path) {
662        updatePublicVolumeState(path, Environment.MEDIA_NOFS);
663
664        Intent intent = new Intent();
665        intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
666        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
667
668        setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title,
669                                    com.android.internal.R.string.ext_media_nofs_notification_message,
670                                    com.android.internal.R.drawable.stat_notify_sdcard_usb,
671                                    true, false, pi);
672        updateUsbMassStorageNotification(false, false);
673        intent = new Intent(Intent.ACTION_MEDIA_NOFS,
674                Uri.parse("file://" + path));
675        mContext.sendBroadcast(intent);
676    }
677
678    /**
679     * Broadcasts the media mounted event to all clients.
680     */
681    void notifyMediaMounted(String path, boolean readOnly) {
682        updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
683
684        setMediaStorageNotification(0, 0, 0, false, false, null);
685        updateUsbMassStorageNotification(false, false);
686        Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED,
687                Uri.parse("file://" + path));
688        intent.putExtra("read-only", readOnly);
689        mMounted = true;
690        mContext.sendBroadcast(intent);
691    }
692
693    /**
694     * Broadcasts the media shared event to all clients.
695     */
696    void notifyMediaShared(String path, boolean mounted) {
697        if (mounted) {
698            Log.e(TAG, "Live shared mounts not supported yet!");
699            return;
700        }
701
702        updatePublicVolumeState(path, Environment.MEDIA_SHARED);
703
704        if (mUmsActiveNotify) {
705            Intent intent = new Intent();
706            intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class);
707            PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
708            setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title,
709                    com.android.internal.R.string.usb_storage_stop_notification_message,
710                    com.android.internal.R.drawable.stat_sys_warning,
711                    false, true, pi);
712        }
713        handlePossibleExplicitUnmountBroadcast(path);
714        Intent intent = new Intent(Intent.ACTION_MEDIA_SHARED,
715                Uri.parse("file://" + path));
716        mContext.sendBroadcast(intent);
717    }
718
719    /**
720     * Broadcasts the media bad removal event to all clients.
721     */
722    void notifyMediaBadRemoval(String path) {
723        updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
724
725        updateUsbMassStorageNotification(true, false);
726        setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title,
727                                    com.android.internal.R.string.ext_media_badremoval_notification_message,
728                                    com.android.internal.R.drawable.stat_sys_warning,
729                                    true, true, null);
730
731        handlePossibleExplicitUnmountBroadcast(path);
732        Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL,
733                Uri.parse("file://" + path));
734        mContext.sendBroadcast(intent);
735    }
736
737    /**
738     * Broadcasts the media unmountable event to all clients.
739     */
740    void notifyMediaUnmountable(String path) {
741        updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
742
743        Intent intent = new Intent();
744        intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
745        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
746
747        setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title,
748                                    com.android.internal.R.string.ext_media_unmountable_notification_message,
749                                    com.android.internal.R.drawable.stat_notify_sdcard_usb,
750                                    true, false, pi);
751        updateUsbMassStorageNotification(false, false);
752
753        handlePossibleExplicitUnmountBroadcast(path);
754
755        intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE,
756                Uri.parse("file://" + path));
757        mContext.sendBroadcast(intent);
758    }
759
760    /**
761     * Broadcasts the media eject event to all clients.
762     */
763    void notifyMediaUnmounting(String path) {
764        Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT,
765                Uri.parse("file://" + path));
766        mContext.sendBroadcast(intent);
767    }
768
769    /**
770     * Sets the USB storage notification.
771     */
772    private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible,
773                                                        PendingIntent pi) {
774
775        if (!visible && mUsbStorageNotification == null) {
776            return;
777        }
778
779        NotificationManager notificationManager = (NotificationManager) mContext
780                .getSystemService(Context.NOTIFICATION_SERVICE);
781
782        if (notificationManager == null) {
783            return;
784        }
785
786        if (visible) {
787            Resources r = Resources.getSystem();
788            CharSequence title = r.getText(titleId);
789            CharSequence message = r.getText(messageId);
790
791            if (mUsbStorageNotification == null) {
792                mUsbStorageNotification = new Notification();
793                mUsbStorageNotification.icon = icon;
794                mUsbStorageNotification.when = 0;
795            }
796
797            if (sound && mPlaySounds) {
798                mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
799            } else {
800                mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
801            }
802
803            mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
804
805            mUsbStorageNotification.tickerText = title;
806            if (pi == null) {
807                Intent intent = new Intent();
808                pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
809            }
810
811            mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
812        }
813
814        final int notificationId = mUsbStorageNotification.icon;
815        if (visible) {
816            notificationManager.notify(notificationId, mUsbStorageNotification);
817        } else {
818            notificationManager.cancel(notificationId);
819        }
820    }
821
822    private synchronized boolean getMediaStorageNotificationDismissable() {
823        if ((mMediaStorageNotification != null) &&
824            ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
825                    Notification.FLAG_AUTO_CANCEL))
826            return true;
827
828        return false;
829    }
830
831    /**
832     * Sets the media storage notification.
833     */
834    private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
835                                                          boolean dismissable, PendingIntent pi) {
836
837        if (!visible && mMediaStorageNotification == null) {
838            return;
839        }
840
841        NotificationManager notificationManager = (NotificationManager) mContext
842                .getSystemService(Context.NOTIFICATION_SERVICE);
843
844        if (notificationManager == null) {
845            return;
846        }
847
848        if (mMediaStorageNotification != null && visible) {
849            /*
850             * Dismiss the previous notification - we're about to
851             * re-use it.
852             */
853            final int notificationId = mMediaStorageNotification.icon;
854            notificationManager.cancel(notificationId);
855        }
856
857        if (visible) {
858            Resources r = Resources.getSystem();
859            CharSequence title = r.getText(titleId);
860            CharSequence message = r.getText(messageId);
861
862            if (mMediaStorageNotification == null) {
863                mMediaStorageNotification = new Notification();
864                mMediaStorageNotification.when = 0;
865            }
866
867            if (mPlaySounds) {
868                mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND;
869            } else {
870                mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
871            }
872
873            if (dismissable) {
874                mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
875            } else {
876                mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
877            }
878
879            mMediaStorageNotification.tickerText = title;
880            if (pi == null) {
881                Intent intent = new Intent();
882                pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
883            }
884
885            mMediaStorageNotification.icon = icon;
886            mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
887        }
888
889        final int notificationId = mMediaStorageNotification.icon;
890        if (visible) {
891            notificationManager.notify(notificationId, mMediaStorageNotification);
892        } else {
893            notificationManager.cancel(notificationId);
894        }
895    }
896
897    public String[] getSecureCacheList() throws IllegalStateException {
898        return mListener.listAsec();
899    }
900
901    public String createSecureCache(String id, int sizeMb, String fstype,
902                                    String key, int ownerUid) throws IllegalStateException {
903        return mListener.createAsec(id, sizeMb, fstype, key, ownerUid);
904    }
905
906    public void finalizeSecureCache(String id) throws IllegalStateException {
907        mListener.finalizeAsec(id);
908    }
909
910    public void destroySecureCache(String id) throws IllegalStateException {
911        mListener.destroyAsec(id);
912    }
913
914    public String mountSecureCache(String id, String key, int ownerUid) throws IllegalStateException {
915        return mListener.mountAsec(id, key, ownerUid);
916    }
917
918    public String getSecureCachePath(String id) throws IllegalStateException {
919        return mListener.getAsecPath(id);
920    }
921
922}
923
924