MountService.java revision c2a39471642e31d7350910612e40d078b825173a
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 = null;
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                    if (state != null) {
573                        updatePublicVolumeState(path, state);
574                    }
575                } catch (Exception e) {
576                    Log.e(TAG, "Error processing initial volume state", e);
577                    updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
578                }
579
580                try {
581                    boolean avail = getShareAvailable("ums");
582                    notifyShareAvailabilityChange("ums", avail);
583                } catch (Exception ex) {
584                    Log.w(TAG, "Failed to get share availability");
585                }
586            }
587        }.start();
588    }
589
590    /**
591     *
592     * Callback from NativeDaemonConnector
593     */
594    public boolean onEvent(int code, String raw, String[] cooked) {
595        // Log.d(TAG, "event {" + raw + "}");
596        if (code == VoldResponseCode.VolumeStateChange) {
597            // FMT: NNN Volume <label> <mountpoint> state changed
598            // from <old_#> (<old_str>) to <new_#> (<new_str>)
599            notifyVolumeStateChange(
600                    cooked[2], cooked[3], Integer.parseInt(cooked[7]),
601                            Integer.parseInt(cooked[10]));
602        } else if (code == VoldResponseCode.VolumeMountFailedBlank) {
603            // FMT: NNN Volume <label> <mountpoint> mount failed - no supported file-systems
604            notifyMediaNoFs(cooked[3]);
605            // FMT: NNN Volume <label> <mountpoint> mount failed - no media
606        } else if (code == VoldResponseCode.VolumeMountFailedNoMedia) {
607            notifyMediaRemoved(cooked[3]);
608        } else if (code == VoldResponseCode.VolumeMountFailedDamaged) {
609            // FMT: NNN Volume <label> <mountpoint> mount failed - filesystem check failed
610            notifyMediaUnmountable(cooked[3]);
611        } else if (code == VoldResponseCode.ShareAvailabilityChange) {
612            // FMT: NNN Share method <method> now <available|unavailable>
613            boolean avail = false;
614            if (cooked[5].equals("available")) {
615                avail = true;
616            }
617            notifyShareAvailabilityChange(cooked[3], avail);
618        } else if (code == VoldResponseCode.VolumeDiskInserted) {
619            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
620            notifyMediaInserted(cooked[3]);
621        } else if (code == VoldResponseCode.VolumeDiskRemoved) {
622            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
623            notifyMediaRemoved(cooked[3]);
624        } else if (code == VoldResponseCode.VolumeBadRemoval) {
625            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
626            notifyMediaBadRemoval(cooked[3]);
627        } else {
628            return false;
629        }
630       return true;
631    }
632
633    void notifyVolumeStateChange(String label, String mountPoint, int oldState,
634                                 int newState) throws IllegalStateException {
635        String vs = getVolumeState(mountPoint);
636
637        if (newState == VolumeState.Init) {
638        } else if (newState == VolumeState.NoMedia) {
639            // NoMedia is handled via Disk Remove events
640        } else if (newState == VolumeState.Idle) {
641            /*
642             * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
643             * if we're in the process of enabling UMS
644             */
645            if (!vs.equals(Environment.MEDIA_BAD_REMOVAL) &&
646                !vs.equals(Environment.MEDIA_NOFS) &&
647                !vs.equals(Environment.MEDIA_UNMOUNTABLE) &&
648                !mUmsEnabling) {
649                notifyMediaUnmounted(mountPoint);
650            }
651        } else if (newState == VolumeState.Pending) {
652        } else if (newState == VolumeState.Checking) {
653            notifyMediaChecking(mountPoint);
654        } else if (newState == VolumeState.Mounted) {
655            notifyMediaMounted(mountPoint, false);
656        } else if (newState == VolumeState.Unmounting) {
657            notifyMediaUnmounting(mountPoint);
658        } else if (newState == VolumeState.Formatting) {
659        } else if (newState == VolumeState.Shared) {
660            notifyMediaShared(mountPoint, false);
661        } else if (newState == VolumeState.SharedMnt) {
662            notifyMediaShared(mountPoint, true);
663        } else {
664            Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
665        }
666    }
667
668
669    /**
670     * Broadcasts the USB mass storage connected event to all clients.
671     */
672    void notifyUmsConnected() {
673        mUmsConnected = true;
674
675        String storageState = Environment.getExternalStorageState();
676        if (!storageState.equals(Environment.MEDIA_REMOVED) &&
677            !storageState.equals(Environment.MEDIA_BAD_REMOVAL) &&
678            !storageState.equals(Environment.MEDIA_CHECKING)) {
679
680            if (mAutoStartUms) {
681                try {
682                    setMassStorageEnabled(true);
683                } catch (IllegalStateException e) {
684                }
685            } else if (mPromptUms) {
686                updateUsbMassStorageNotification(false, true);
687            }
688        }
689
690        Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED);
691        mContext.sendBroadcast(intent);
692    }
693
694    void notifyShareAvailabilityChange(String method, final boolean avail) {
695        if (!method.equals("ums")) {
696           Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
697           return;
698        }
699
700        /*
701         * Notification needs to run in a different thread as
702         * it may need to call back into vold
703         */
704        new Thread() {
705            public void run() {
706                try {
707                    if (avail) {
708                        notifyUmsConnected();
709                    } else {
710                        notifyUmsDisconnected();
711                    }
712                } catch (Exception ex) {
713                    Log.w(TAG, "Failed to mount media on insertion");
714                }
715            }
716        }.start();
717    }
718
719    /**
720     * Broadcasts the USB mass storage disconnected event to all clients.
721     */
722    void notifyUmsDisconnected() {
723        mUmsConnected = false;
724        if (mUmsEnabled) {
725            try {
726                Log.w(TAG, "UMS disconnected while enabled!");
727                setMassStorageEnabled(false);
728            } catch (Exception ex) {
729                Log.e(TAG, "Error disabling UMS on unsafe UMS disconnect", ex);
730            }
731        }
732        updateUsbMassStorageNotification(false, false);
733        Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
734        mContext.sendBroadcast(intent);
735    }
736
737    void notifyMediaInserted(final String path) throws IllegalStateException {
738        new Thread() {
739            public void run() {
740                try {
741                    mountVolume(path);
742                } catch (Exception ex) {
743                    Log.w(TAG, "Failed to mount media on insertion", ex);
744                }
745            }
746        }.start();
747    }
748
749    /**
750     * Broadcasts the media removed event to all clients.
751     */
752    void notifyMediaRemoved(String path) throws IllegalStateException {
753
754        // Suppress this on bad removal
755        if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
756            return;
757        }
758
759        updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
760
761        updateUsbMassStorageNotification(true, false);
762
763        setMediaStorageNotification(
764            com.android.internal.R.string.ext_media_nomedia_notification_title,
765            com.android.internal.R.string.ext_media_nomedia_notification_message,
766            com.android.internal.R.drawable.stat_notify_sdcard_usb,
767            true, false, null);
768        handlePossibleExplicitUnmountBroadcast(path);
769
770        Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED,
771                Uri.parse("file://" + path));
772        mContext.sendBroadcast(intent);
773    }
774
775    /**
776     * Broadcasts the media unmounted event to all clients.
777     */
778    void notifyMediaUnmounted(String path) {
779
780        updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
781
782        // Update media status on PackageManagerService to unmount packages on sdcard
783        mPms.updateExternalMediaStatus(false);
784        if (mShowSafeUnmountNotificationWhenUnmounted) {
785            setMediaStorageNotification(
786                    com.android.internal.R.string.ext_media_safe_unmount_notification_title,
787                    com.android.internal.R.string.ext_media_safe_unmount_notification_message,
788                    com.android.internal.R.drawable.stat_notify_sdcard,
789                    true, true, null);
790            mShowSafeUnmountNotificationWhenUnmounted = false;
791        } else {
792            setMediaStorageNotification(0, 0, 0, false, false, null);
793        }
794        updateUsbMassStorageNotification(false, false);
795
796        Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
797                Uri.parse("file://" + path));
798        mContext.sendBroadcast(intent);
799    }
800
801    /**
802     * Broadcasts the media checking event to all clients.
803     */
804    void notifyMediaChecking(String path) {
805        updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
806
807        setMediaStorageNotification(
808                com.android.internal.R.string.ext_media_checking_notification_title,
809                com.android.internal.R.string.ext_media_checking_notification_message,
810                com.android.internal.R.drawable.stat_notify_sdcard_prepare,
811                true, false, null);
812
813        updateUsbMassStorageNotification(true, false);
814        Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING,
815                Uri.parse("file://" + path));
816        mContext.sendBroadcast(intent);
817    }
818
819    /**
820     * Broadcasts the media nofs event to all clients.
821     */
822    void notifyMediaNoFs(String path) {
823        updatePublicVolumeState(path, Environment.MEDIA_NOFS);
824
825        Intent intent = new Intent();
826        intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
827        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
828
829        setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title,
830                                    com.android.internal.R.string.ext_media_nofs_notification_message,
831                                    com.android.internal.R.drawable.stat_notify_sdcard_usb,
832                                    true, false, pi);
833        updateUsbMassStorageNotification(false, false);
834        intent = new Intent(Intent.ACTION_MEDIA_NOFS,
835                Uri.parse("file://" + path));
836        mContext.sendBroadcast(intent);
837    }
838
839    /**
840     * Broadcasts the media mounted event to all clients.
841     */
842    void notifyMediaMounted(String path, boolean readOnly) {
843        updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
844
845        // Update media status on PackageManagerService to mount packages on sdcard
846        mPms.updateExternalMediaStatus(true);
847        setMediaStorageNotification(0, 0, 0, false, false, null);
848        updateUsbMassStorageNotification(false, false);
849        Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED,
850                Uri.parse("file://" + path));
851        intent.putExtra("read-only", readOnly);
852        mMounted = true;
853        mContext.sendBroadcast(intent);
854    }
855
856    /**
857     * Broadcasts the media shared event to all clients.
858     */
859    void notifyMediaShared(String path, boolean mounted) {
860        if (mounted) {
861            Log.e(TAG, "Live shared mounts not supported yet!");
862            return;
863        }
864
865        updatePublicVolumeState(path, Environment.MEDIA_SHARED);
866
867        if (mUmsActiveNotify) {
868            Intent intent = new Intent();
869            intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class);
870            PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
871            setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title,
872                    com.android.internal.R.string.usb_storage_stop_notification_message,
873                    com.android.internal.R.drawable.stat_sys_warning,
874                    false, true, pi);
875        }
876        handlePossibleExplicitUnmountBroadcast(path);
877        Intent intent = new Intent(Intent.ACTION_MEDIA_SHARED,
878                Uri.parse("file://" + path));
879        mContext.sendBroadcast(intent);
880    }
881
882    /**
883     * Broadcasts the media bad removal event to all clients.
884     */
885    void notifyMediaBadRemoval(String path) {
886        updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
887
888        updateUsbMassStorageNotification(true, false);
889        setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title,
890                                    com.android.internal.R.string.ext_media_badremoval_notification_message,
891                                    com.android.internal.R.drawable.stat_sys_warning,
892                                    true, true, null);
893
894        handlePossibleExplicitUnmountBroadcast(path);
895        Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL,
896                Uri.parse("file://" + path));
897        mContext.sendBroadcast(intent);
898    }
899
900    /**
901     * Broadcasts the media unmountable event to all clients.
902     */
903    void notifyMediaUnmountable(String path) {
904        updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
905
906        Intent intent = new Intent();
907        intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
908        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
909
910        setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title,
911                                    com.android.internal.R.string.ext_media_unmountable_notification_message,
912                                    com.android.internal.R.drawable.stat_notify_sdcard_usb,
913                                    true, false, pi);
914        updateUsbMassStorageNotification(false, false);
915
916        handlePossibleExplicitUnmountBroadcast(path);
917
918        intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE,
919                Uri.parse("file://" + path));
920        mContext.sendBroadcast(intent);
921    }
922
923    /**
924     * Broadcasts the media eject event to all clients.
925     */
926    void notifyMediaUnmounting(String path) {
927        Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT,
928                Uri.parse("file://" + path));
929        mContext.sendBroadcast(intent);
930    }
931
932    /**
933     * Sets the USB storage notification.
934     */
935    private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible,
936                                                        PendingIntent pi) {
937
938        if (!visible && mUsbStorageNotification == null) {
939            return;
940        }
941
942        NotificationManager notificationManager = (NotificationManager) mContext
943                .getSystemService(Context.NOTIFICATION_SERVICE);
944
945        if (notificationManager == null) {
946            return;
947        }
948
949        if (visible) {
950            Resources r = Resources.getSystem();
951            CharSequence title = r.getText(titleId);
952            CharSequence message = r.getText(messageId);
953
954            if (mUsbStorageNotification == null) {
955                mUsbStorageNotification = new Notification();
956                mUsbStorageNotification.icon = icon;
957                mUsbStorageNotification.when = 0;
958            }
959
960            if (sound && mPlaySounds) {
961                mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
962            } else {
963                mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
964            }
965
966            mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
967
968            mUsbStorageNotification.tickerText = title;
969            if (pi == null) {
970                Intent intent = new Intent();
971                pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
972            }
973
974            mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
975        }
976
977        final int notificationId = mUsbStorageNotification.icon;
978        if (visible) {
979            notificationManager.notify(notificationId, mUsbStorageNotification);
980        } else {
981            notificationManager.cancel(notificationId);
982        }
983    }
984
985    private synchronized boolean getMediaStorageNotificationDismissable() {
986        if ((mMediaStorageNotification != null) &&
987            ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
988                    Notification.FLAG_AUTO_CANCEL))
989            return true;
990
991        return false;
992    }
993
994    /**
995     * Sets the media storage notification.
996     */
997    private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
998                                                          boolean dismissable, PendingIntent pi) {
999
1000        if (!visible && mMediaStorageNotification == null) {
1001            return;
1002        }
1003
1004        NotificationManager notificationManager = (NotificationManager) mContext
1005                .getSystemService(Context.NOTIFICATION_SERVICE);
1006
1007        if (notificationManager == null) {
1008            return;
1009        }
1010
1011        if (mMediaStorageNotification != null && visible) {
1012            /*
1013             * Dismiss the previous notification - we're about to
1014             * re-use it.
1015             */
1016            final int notificationId = mMediaStorageNotification.icon;
1017            notificationManager.cancel(notificationId);
1018        }
1019
1020        if (visible) {
1021            Resources r = Resources.getSystem();
1022            CharSequence title = r.getText(titleId);
1023            CharSequence message = r.getText(messageId);
1024
1025            if (mMediaStorageNotification == null) {
1026                mMediaStorageNotification = new Notification();
1027                mMediaStorageNotification.when = 0;
1028            }
1029
1030            if (mPlaySounds) {
1031                mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND;
1032            } else {
1033                mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
1034            }
1035
1036            if (dismissable) {
1037                mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
1038            } else {
1039                mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
1040            }
1041
1042            mMediaStorageNotification.tickerText = title;
1043            if (pi == null) {
1044                Intent intent = new Intent();
1045                pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
1046            }
1047
1048            mMediaStorageNotification.icon = icon;
1049            mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
1050        }
1051
1052        final int notificationId = mMediaStorageNotification.icon;
1053        if (visible) {
1054            notificationManager.notify(notificationId, mMediaStorageNotification);
1055        } else {
1056            notificationManager.cancel(notificationId);
1057        }
1058    }
1059
1060    public String[] getSecureContainerList() throws IllegalStateException {
1061        return mConnector.doListCommand("list_asec", VoldResponseCode.AsecListResult);
1062    }
1063
1064    public String createSecureContainer(String id, int sizeMb, String fstype,
1065                                    String key, int ownerUid) throws IllegalStateException {
1066        String cmd = String.format("create_asec %s %d %s %s %d",
1067                                   id, sizeMb, fstype, key, ownerUid);
1068        mConnector.doCommand(cmd);
1069        return getSecureContainerPath(id);
1070    }
1071
1072    public void finalizeSecureContainer(String id) throws IllegalStateException {
1073        mConnector.doCommand(String.format("finalize_asec %s", id));
1074    }
1075
1076    public void destroySecureContainer(String id) throws IllegalStateException {
1077        mConnector.doCommand(String.format("destroy_asec %s", id));
1078    }
1079
1080    public String mountSecureContainer(String id, String key,
1081                                       int ownerUid) throws IllegalStateException {
1082        String cmd = String.format("mount_asec %s %s %d",
1083                                   id, key, ownerUid);
1084        mConnector.doCommand(cmd);
1085        return getSecureContainerPath(id);
1086    }
1087
1088    public void unmountSecureContainer(String id) throws IllegalStateException {
1089        String cmd = String.format("unmount_asec %s", id);
1090        mConnector.doCommand(cmd);
1091    }
1092
1093    public void renameSecureContainer(String oldId, String newId) throws IllegalStateException {
1094        String cmd = String.format("rename_asec %s %s", oldId, newId);
1095        mConnector.doCommand(cmd);
1096    }
1097
1098    public String getSecureContainerPath(String id) throws IllegalStateException {
1099        ArrayList<String> rsp = mConnector.doCommand("asec_path " + id);
1100
1101        for (String line : rsp) {
1102            String []tok = line.split(" ");
1103            int code = Integer.parseInt(tok[0]);
1104            if (code == VoldResponseCode.AsecPathResult) {
1105                return tok[1];
1106            } else {
1107                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1108            }
1109        }
1110        throw new IllegalStateException("Got an empty response");
1111    }
1112}
1113
1114