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