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