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