MountService.java revision 59443a673a736978361dc341f41ce4e9dae053a0
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.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.pm.PackageManager;
24import android.content.res.Resources;
25import android.net.Uri;
26import android.os.storage.IMountService;
27import android.os.storage.IMountServiceListener;
28import android.os.storage.StorageResultCode;
29import android.os.RemoteException;
30import android.os.IBinder;
31import android.os.Environment;
32import android.os.ServiceManager;
33import android.os.SystemClock;
34import android.os.SystemProperties;
35import android.os.UEventObserver;
36import android.os.Handler;
37import android.text.TextUtils;
38import android.util.Log;
39import java.util.ArrayList;
40
41import java.io.File;
42import java.io.FileReader;
43
44/**
45 * MountService implements back-end services for platform storage
46 * management.
47 * @hide - Applications should use android.os.storage.StorageManager
48 * to access the MountService.
49 */
50class MountService extends IMountService.Stub
51        implements INativeDaemonConnectorCallbacks {
52    private static final boolean LOCAL_LOGD = false;
53
54    private static final String TAG = "MountService";
55
56    /*
57     * Internal vold volume state constants
58     */
59    class VolumeState {
60        public static final int Init       = -1;
61        public static final int NoMedia    = 0;
62        public static final int Idle       = 1;
63        public static final int Pending    = 2;
64        public static final int Checking   = 3;
65        public static final int Mounted    = 4;
66        public static final int Unmounting = 5;
67        public static final int Formatting = 6;
68        public static final int Shared     = 7;
69        public static final int SharedMnt  = 8;
70    }
71
72    /*
73     * Internal vold response code constants
74     */
75    class VoldResponseCode {
76        /*
77         * 100 series - Requestion action was initiated; expect another reply
78         *              before proceeding with a new command.
79         */
80        public static final int VolumeListResult               = 110;
81        public static final int AsecListResult                 = 111;
82
83        /*
84         * 200 series - Requestion action has been successfully completed.
85         */
86        public static final int ShareStatusResult              = 210;
87        public static final int AsecPathResult                 = 211;
88        public static final int ShareEnabledResult             = 212;
89
90        /*
91         * 400 series - Command was accepted, but the requested action
92         *              did not take place.
93         */
94        public static final int OpFailedNoMedia                = 401;
95        public static final int OpFailedMediaBlank             = 402;
96        public static final int OpFailedMediaCorrupt           = 403;
97        public static final int OpFailedVolNotMounted          = 404;
98        public static final int OpFailedVolBusy                = 405;
99
100        /*
101         * 600 series - Unsolicited broadcasts.
102         */
103        public static final int VolumeStateChange              = 605;
104        public static final int ShareAvailabilityChange        = 620;
105        public static final int VolumeDiskInserted             = 630;
106        public static final int VolumeDiskRemoved              = 631;
107        public static final int VolumeBadRemoval               = 632;
108    }
109
110    private Context                               mContext;
111    private NativeDaemonConnector                 mConnector;
112    private String                                mLegacyState = Environment.MEDIA_REMOVED;
113    private PackageManagerService                 mPms;
114    private boolean                               mUmsEnabling;
115    private ArrayList<MountServiceBinderListener> mListeners;
116    private boolean                               mBooted;
117    private boolean                               mReady;
118
119    private void waitForReady() {
120        while (mReady == false) {
121            for (int retries = 5; retries > 0; retries--) {
122                if (mReady) {
123                    return;
124                }
125                SystemClock.sleep(1000);
126            }
127            Log.w(TAG, "Waiting too long for mReady!");
128        }
129    }
130
131    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
132        public void onReceive(Context context, Intent intent) {
133            String action = intent.getAction();
134
135            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
136                mBooted = true;
137
138                String path = Environment.getExternalStorageDirectory().getPath();
139                if (getVolumeState(path).equals(Environment.MEDIA_UNMOUNTED)) {
140                    int rc = doMountVolume(path);
141                    if (rc != StorageResultCode.OperationSucceeded) {
142                        Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
143                    }
144                }
145            }
146        }
147    };
148
149    private final class MountServiceBinderListener implements IBinder.DeathRecipient {
150        final IMountServiceListener mListener;
151
152        MountServiceBinderListener(IMountServiceListener listener) {
153            mListener = listener;
154
155        }
156
157        public void binderDied() {
158            if (LOCAL_LOGD) Log.d(TAG, "An IMountServiceListener has died!");
159            synchronized(mListeners) {
160                mListeners.remove(this);
161                mListener.asBinder().unlinkToDeath(this, 0);
162            }
163        }
164    }
165
166    private int doShareUnshareVolume(String path, String method, boolean enable) {
167        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
168
169        // TODO: Add support for multiple share methods
170        if (!method.equals("ums")) {
171            throw new IllegalArgumentException(String.format("Method %s not supported", method));
172        }
173
174        /*
175         * If the volume is mounted and we're enabling then unmount it
176         */
177        String vs = getVolumeState(path);
178        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
179            mUmsEnabling = enable; // Override for isUsbMassStorageEnabled()
180            int rc = doUnmountVolume(path);
181            mUmsEnabling = false; // Clear override
182            if (rc != StorageResultCode.OperationSucceeded) {
183                Log.e(TAG, String.format("Failed to unmount before enabling UMS (%d)", rc));
184                return rc;
185            }
186        }
187
188        try {
189            mConnector.doCommand(String.format(
190                    "volume %sshare %s %s", (enable ? "" : "un"), path, method));
191        } catch (NativeDaemonConnectorException e) {
192            Log.e(TAG, "Failed to share/unshare", e);
193            return StorageResultCode.OperationFailedInternalError;
194        }
195
196        /*
197         * If we disabled UMS then mount the volume
198         */
199        if (!enable) {
200            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
201                Log.e(TAG, String.format(
202                        "Failed to remount %s after disabling share method %s", path, method));
203                /*
204                 * Even though the mount failed, the unshare didn't so don't indicate an error.
205                 * The mountVolume() call will have set the storage state and sent the necessary
206                 * broadcasts.
207                 */
208            }
209        }
210
211        return StorageResultCode.OperationSucceeded;
212    }
213
214    private void updatePublicVolumeState(String path, String state) {
215        if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
216            Log.w(TAG, "Multiple volumes not currently supported");
217            return;
218        }
219
220        if (mLegacyState.equals(state)) {
221            Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
222            return;
223        }
224
225        String oldState = mLegacyState;
226        mLegacyState = state;
227
228        synchronized (mListeners) {
229            for (int i = mListeners.size() -1; i >= 0; i--) {
230                MountServiceBinderListener bl = mListeners.get(i);
231                try {
232                    bl.mListener.onStorageStateChanged(path, oldState, state);
233                } catch (RemoteException rex) {
234                    Log.e(TAG, "Listener dead");
235                    mListeners.remove(i);
236                } catch (Exception ex) {
237                    Log.e(TAG, "Listener failed", ex);
238                }
239            }
240        }
241    }
242
243    /**
244     *
245     * Callback from NativeDaemonConnector
246     */
247    public void onDaemonConnected() {
248        /*
249         * Since we'll be calling back into the NativeDaemonConnector,
250         * we need to do our work in a new thread.
251         */
252        new Thread() {
253            public void run() {
254                /**
255                 * Determine media state and UMS detection status
256                 */
257                String path = Environment.getExternalStorageDirectory().getPath();
258                String state = Environment.MEDIA_REMOVED;
259
260                try {
261                    String[] vols = mConnector.doListCommand(
262                        "volume list", VoldResponseCode.VolumeListResult);
263                    for (String volstr : vols) {
264                        String[] tok = volstr.split(" ");
265                        // FMT: <label> <mountpoint> <state>
266                        if (!tok[1].equals(path)) {
267                            Log.w(TAG, String.format(
268                                    "Skipping unknown volume '%s'",tok[1]));
269                            continue;
270                        }
271                        int st = Integer.parseInt(tok[2]);
272                        if (st == VolumeState.NoMedia) {
273                            state = Environment.MEDIA_REMOVED;
274                        } else if (st == VolumeState.Idle) {
275                            state = Environment.MEDIA_UNMOUNTED;
276                        } else if (st == VolumeState.Mounted) {
277                            state = Environment.MEDIA_MOUNTED;
278                            Log.i(TAG, "Media already mounted on daemon connection");
279                        } else if (st == VolumeState.Shared) {
280                            state = Environment.MEDIA_SHARED;
281                            Log.i(TAG, "Media shared on daemon connection");
282                        } else {
283                            throw new Exception(String.format("Unexpected state %d", st));
284                        }
285                    }
286                    if (state != null) {
287                        updatePublicVolumeState(path, state);
288                    }
289                } catch (Exception e) {
290                    Log.e(TAG, "Error processing initial volume state", e);
291                    updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
292                }
293
294                try {
295                    boolean avail = doGetShareMethodAvailable("ums");
296                    notifyShareAvailabilityChange("ums", avail);
297                } catch (Exception ex) {
298                    Log.w(TAG, "Failed to get share availability");
299                }
300                /*
301                 * Now that we've done our initialization, release
302                 * the hounds!
303                 */
304                mReady = true;
305            }
306        }.start();
307    }
308
309    /**
310     * Callback from NativeDaemonConnector
311     */
312    public boolean onEvent(int code, String raw, String[] cooked) {
313        Intent in = null;
314
315        if (code == VoldResponseCode.VolumeStateChange) {
316            /*
317             * One of the volumes we're managing has changed state.
318             * Format: "NNN Volume <label> <path> state changed
319             * from <old_#> (<old_str>) to <new_#> (<new_str>)"
320             */
321            notifyVolumeStateChange(
322                    cooked[2], cooked[3], Integer.parseInt(cooked[7]),
323                            Integer.parseInt(cooked[10]));
324        } else if (code == VoldResponseCode.ShareAvailabilityChange) {
325            // FMT: NNN Share method <method> now <available|unavailable>
326            boolean avail = false;
327            if (cooked[5].equals("available")) {
328                avail = true;
329            }
330            notifyShareAvailabilityChange(cooked[3], avail);
331        } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
332                   (code == VoldResponseCode.VolumeDiskRemoved) ||
333                   (code == VoldResponseCode.VolumeBadRemoval)) {
334            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
335            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
336            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
337            final String label = cooked[2];
338            final String path = cooked[3];
339            int major = -1;
340            int minor = -1;
341
342            try {
343                String devComp = cooked[6].substring(1, cooked[6].length() -1);
344                String[] devTok = devComp.split(":");
345                major = Integer.parseInt(devTok[0]);
346                minor = Integer.parseInt(devTok[1]);
347            } catch (Exception ex) {
348                Log.e(TAG, "Failed to parse major/minor", ex);
349            }
350
351            if (code == VoldResponseCode.VolumeDiskInserted) {
352                new Thread() {
353                    public void run() {
354                        try {
355                            int rc;
356                            if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
357                                Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
358                            }
359                        } catch (Exception ex) {
360                            Log.w(TAG, "Failed to mount media on insertion", ex);
361                        }
362                    }
363                }.start();
364            } else if (code == VoldResponseCode.VolumeDiskRemoved) {
365                /*
366                 * This event gets trumped if we're already in BAD_REMOVAL state
367                 */
368                if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
369                    return true;
370                }
371                /* Send the media unmounted event first */
372                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
373                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
374                mContext.sendBroadcast(in);
375
376                updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
377                in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
378            } else if (code == VoldResponseCode.VolumeBadRemoval) {
379                /* Send the media unmounted event first */
380                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
381                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
382                mContext.sendBroadcast(in);
383
384                updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
385                in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
386            } else {
387                Log.e(TAG, String.format("Unknown code {%d}", code));
388            }
389        } else {
390            return false;
391        }
392
393        if (in != null) {
394            mContext.sendBroadcast(in);
395	}
396       return true;
397    }
398
399    private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
400        String vs = getVolumeState(path);
401
402        Intent in = null;
403
404        if (newState == VolumeState.Init) {
405        } else if (newState == VolumeState.NoMedia) {
406            // NoMedia is handled via Disk Remove events
407        } else if (newState == VolumeState.Idle) {
408            /*
409             * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
410             * if we're in the process of enabling UMS
411             */
412            if (!vs.equals(
413                    Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
414                            Environment.MEDIA_NOFS) && !vs.equals(
415                                    Environment.MEDIA_UNMOUNTABLE) && !mUmsEnabling) {
416                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
417                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
418            }
419        } else if (newState == VolumeState.Pending) {
420        } else if (newState == VolumeState.Checking) {
421            updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
422            in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
423        } else if (newState == VolumeState.Mounted) {
424            updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
425            // Update media status on PackageManagerService to mount packages on sdcard
426            mPms.updateExternalMediaStatus(true);
427            in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
428            in.putExtra("read-only", false);
429        } else if (newState == VolumeState.Unmounting) {
430            mPms.updateExternalMediaStatus(false);
431            in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
432        } else if (newState == VolumeState.Formatting) {
433        } else if (newState == VolumeState.Shared) {
434            /* Send the media unmounted event first */
435            updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
436            in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
437            mContext.sendBroadcast(in);
438
439            updatePublicVolumeState(path, Environment.MEDIA_SHARED);
440            in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
441        } else if (newState == VolumeState.SharedMnt) {
442            Log.e(TAG, "Live shared mounts not supported yet!");
443            return;
444        } else {
445            Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
446        }
447
448        if (in != null) {
449            mContext.sendBroadcast(in);
450        }
451    }
452
453    private boolean doGetShareMethodAvailable(String method) {
454        ArrayList<String> rsp = mConnector.doCommand("share status " + method);
455
456        for (String line : rsp) {
457            String []tok = line.split(" ");
458            int code;
459            try {
460                code = Integer.parseInt(tok[0]);
461            } catch (NumberFormatException nfe) {
462                Log.e(TAG, String.format("Error parsing code %s", tok[0]));
463                return false;
464            }
465            if (code == VoldResponseCode.ShareStatusResult) {
466                if (tok[2].equals("available"))
467                    return true;
468                return false;
469            } else {
470                Log.e(TAG, String.format("Unexpected response code %d", code));
471                return false;
472            }
473        }
474        Log.e(TAG, "Got an empty response");
475        return false;
476    }
477
478    private int doMountVolume(String path) {
479        int rc = StorageResultCode.OperationSucceeded;
480
481        try {
482            mConnector.doCommand(String.format("volume mount %s", path));
483        } catch (NativeDaemonConnectorException e) {
484            /*
485             * Mount failed for some reason
486             */
487            Intent in = null;
488            int code = e.getCode();
489            if (code == VoldResponseCode.OpFailedNoMedia) {
490                /*
491                 * Attempt to mount but no media inserted
492                 */
493                rc = StorageResultCode.OperationFailedNoMedia;
494            } else if (code == VoldResponseCode.OpFailedMediaBlank) {
495                /*
496                 * Media is blank or does not contain a supported filesystem
497                 */
498                updatePublicVolumeState(path, Environment.MEDIA_NOFS);
499                in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
500                rc = StorageResultCode.OperationFailedMediaBlank;
501            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
502                /*
503                 * Volume consistency check failed
504                 */
505                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
506                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
507                rc = StorageResultCode.OperationFailedMediaCorrupt;
508            } else {
509                rc = StorageResultCode.OperationFailedInternalError;
510            }
511
512            /*
513             * Send broadcast intent (if required for the failure)
514             */
515            if (in != null) {
516                mContext.sendBroadcast(in);
517            }
518        }
519
520        return rc;
521    }
522
523    private int doUnmountVolume(String path) {
524        if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
525            return VoldResponseCode.OpFailedVolNotMounted;
526        }
527
528        // Notify PackageManager of potential media removal and deal with
529        // return code later on. The caller of this api should be aware or have been
530        // notified that the applications installed on the media will be killed.
531        mPms.updateExternalMediaStatus(false);
532        try {
533            mConnector.doCommand(String.format("volume unmount %s", path));
534            return StorageResultCode.OperationSucceeded;
535        } catch (NativeDaemonConnectorException e) {
536            // Don't worry about mismatch in PackageManager since the
537            // call back will handle the status changes any way.
538            int code = e.getCode();
539            if (code == VoldResponseCode.OpFailedVolNotMounted) {
540                return StorageResultCode.OperationFailedVolumeNotMounted;
541            } else {
542                return StorageResultCode.OperationFailedInternalError;
543            }
544        }
545    }
546
547    private int doFormatVolume(String path) {
548        try {
549            String cmd = String.format("volume format %s", path);
550            mConnector.doCommand(cmd);
551            return StorageResultCode.OperationSucceeded;
552        } catch (NativeDaemonConnectorException e) {
553            int code = e.getCode();
554            if (code == VoldResponseCode.OpFailedNoMedia) {
555                return StorageResultCode.OperationFailedNoMedia;
556            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
557                return StorageResultCode.OperationFailedMediaCorrupt;
558            } else {
559                return StorageResultCode.OperationFailedInternalError;
560            }
561        }
562    }
563
564    private boolean doGetVolumeShared(String path, String method) {
565        String cmd = String.format("volume shared %s %s", path, method);
566        ArrayList<String> rsp = mConnector.doCommand(cmd);
567
568        for (String line : rsp) {
569            String []tok = line.split(" ");
570            int code;
571            try {
572                code = Integer.parseInt(tok[0]);
573            } catch (NumberFormatException nfe) {
574                Log.e(TAG, String.format("Error parsing code %s", tok[0]));
575                return false;
576            }
577            if (code == VoldResponseCode.ShareEnabledResult) {
578                if (tok[2].equals("enabled"))
579                    return true;
580                return false;
581            } else {
582                Log.e(TAG, String.format("Unexpected response code %d", code));
583                return false;
584            }
585        }
586        Log.e(TAG, "Got an empty response");
587        return false;
588    }
589
590    private void notifyShareAvailabilityChange(String method, final boolean avail) {
591        if (!method.equals("ums")) {
592           Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
593           return;
594        }
595
596        synchronized (mListeners) {
597            for (int i = mListeners.size() -1; i >= 0; i--) {
598                MountServiceBinderListener bl = mListeners.get(i);
599                try {
600                    bl.mListener.onUsbMassStorageConnectionChanged(avail);
601                } catch (RemoteException rex) {
602                    Log.e(TAG, "Listener dead");
603                    mListeners.remove(i);
604                } catch (Exception ex) {
605                    Log.e(TAG, "Listener failed", ex);
606                }
607            }
608        }
609
610        if (mBooted == true) {
611            Intent intent;
612            if (avail) {
613                intent = new Intent(Intent.ACTION_UMS_CONNECTED);
614            } else {
615                intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
616            }
617            mContext.sendBroadcast(intent);
618        }
619    }
620
621    private void validatePermission(String perm) {
622        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
623            throw new SecurityException(String.format("Requires %s permission", perm));
624        }
625    }
626
627    /**
628     * Constructs a new MountService instance
629     *
630     * @param context  Binder context for this service
631     */
632    public MountService(Context context) {
633        mContext = context;
634
635        /*
636         * Vold does not run in the simulator, so fake out a mounted
637         * event to trigger MediaScanner
638         */
639        if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
640            updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED);
641            return;
642        }
643
644        // XXX: This will go away soon in favor of IMountServiceObserver
645        mPms = (PackageManagerService) ServiceManager.getService("package");
646
647        mContext.registerReceiver(mBroadcastReceiver,
648                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
649
650        mListeners = new ArrayList<MountServiceBinderListener>();
651
652        mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
653        mReady = false;
654        Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
655        thread.start();
656    }
657
658    /**
659     * Exposed API calls below here
660     */
661
662    public void registerListener(IMountServiceListener listener) {
663        synchronized (mListeners) {
664            MountServiceBinderListener bl = new MountServiceBinderListener(listener);
665            try {
666                listener.asBinder().linkToDeath(bl, 0);
667                mListeners.add(bl);
668            } catch (RemoteException rex) {
669                Log.e(TAG, "Failed to link to listener death");
670            }
671        }
672    }
673
674    public void unregisterListener(IMountServiceListener listener) {
675        synchronized (mListeners) {
676            for(MountServiceBinderListener bl : mListeners) {
677                if (bl.mListener == listener) {
678                    mListeners.remove(mListeners.indexOf(bl));
679                    return;
680                }
681            }
682        }
683    }
684
685    public void shutdown() {
686        validatePermission(android.Manifest.permission.SHUTDOWN);
687
688        Log.i(TAG, "Shutting down");
689
690        String path = Environment.getExternalStorageDirectory().getPath();
691        String state = getVolumeState(path);
692
693        if (state.equals(Environment.MEDIA_SHARED)) {
694            /*
695             * If the media is currently shared, unshare it.
696             * XXX: This is still dangerous!. We should not
697             * be rebooting at *all* if UMS is enabled, since
698             * the UMS host could have dirty FAT cache entries
699             * yet to flush.
700             */
701            if (setUsbMassStorageEnabled(false) != StorageResultCode.OperationSucceeded) {
702                Log.e(TAG, "UMS disable on shutdown failed");
703            }
704        } else if (state.equals(Environment.MEDIA_CHECKING)) {
705            /*
706             * If the media is being checked, then we need to wait for
707             * it to complete before being able to proceed.
708             */
709            // XXX: @hackbod - Should we disable the ANR timer here?
710            int retries = 30;
711            while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
712                try {
713                    Thread.sleep(1000);
714                } catch (InterruptedException iex) {
715                    Log.e(TAG, "Interrupted while waiting for media", iex);
716                    break;
717                }
718                state = Environment.getExternalStorageState();
719            }
720            if (retries == 0) {
721                Log.e(TAG, "Timed out waiting for media to check");
722            }
723        }
724
725        if (state.equals(Environment.MEDIA_MOUNTED)) {
726            /*
727             * If the media is mounted, then gracefully unmount it.
728             */
729            if (doUnmountVolume(path) != StorageResultCode.OperationSucceeded) {
730                Log.e(TAG, "Failed to unmount media for shutdown");
731            }
732        }
733    }
734
735    public boolean isUsbMassStorageConnected() {
736        waitForReady();
737
738        if (mUmsEnabling) {
739            return true;
740        }
741        return doGetShareMethodAvailable("ums");
742    }
743
744    public int setUsbMassStorageEnabled(boolean enable) {
745        waitForReady();
746
747        return doShareUnshareVolume(Environment.getExternalStorageDirectory().getPath(), "ums", enable);
748    }
749
750    public boolean isUsbMassStorageEnabled() {
751        waitForReady();
752        return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
753    }
754
755    /**
756     * @return state of the volume at the specified mount point
757     */
758    public String getVolumeState(String mountPoint) {
759        /*
760         * XXX: Until we have multiple volume discovery, just hardwire
761         * this to /sdcard
762         */
763        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
764            Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
765            throw new IllegalArgumentException();
766        }
767
768        return mLegacyState;
769    }
770
771    public int mountVolume(String path) {
772        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
773
774        waitForReady();
775        return doMountVolume(path);
776    }
777
778    public int unmountVolume(String path) {
779        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
780        waitForReady();
781
782        return doUnmountVolume(path);
783    }
784
785    public int formatVolume(String path) {
786        validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
787        waitForReady();
788
789        return doFormatVolume(path);
790    }
791
792    private void warnOnNotMounted() {
793        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
794            Log.w(TAG, "getSecureContainerList() called when storage not mounted");
795        }
796    }
797
798    public String[] getSecureContainerList() {
799        validatePermission(android.Manifest.permission.ASEC_ACCESS);
800        waitForReady();
801        warnOnNotMounted();
802
803        try {
804            return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
805        } catch (NativeDaemonConnectorException e) {
806            return new String[0];
807        }
808    }
809
810    public int createSecureContainer(String id, int sizeMb, String fstype,
811                                    String key, int ownerUid) {
812        validatePermission(android.Manifest.permission.ASEC_CREATE);
813        waitForReady();
814        warnOnNotMounted();
815
816        int rc = StorageResultCode.OperationSucceeded;
817        String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
818        try {
819            mConnector.doCommand(cmd);
820        } catch (NativeDaemonConnectorException e) {
821            rc = StorageResultCode.OperationFailedInternalError;
822        }
823        return rc;
824    }
825
826    public int finalizeSecureContainer(String id) {
827        validatePermission(android.Manifest.permission.ASEC_CREATE);
828        warnOnNotMounted();
829
830        int rc = StorageResultCode.OperationSucceeded;
831        try {
832            mConnector.doCommand(String.format("asec finalize %s", id));
833        } catch (NativeDaemonConnectorException e) {
834            rc = StorageResultCode.OperationFailedInternalError;
835        }
836        return rc;
837    }
838
839    public int destroySecureContainer(String id) {
840        validatePermission(android.Manifest.permission.ASEC_DESTROY);
841        waitForReady();
842        warnOnNotMounted();
843
844        int rc = StorageResultCode.OperationSucceeded;
845        try {
846            mConnector.doCommand(String.format("asec destroy %s", id));
847        } catch (NativeDaemonConnectorException e) {
848            rc = StorageResultCode.OperationFailedInternalError;
849        }
850        return rc;
851    }
852
853    public int mountSecureContainer(String id, String key, int ownerUid) {
854        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
855        waitForReady();
856        warnOnNotMounted();
857
858        int rc = StorageResultCode.OperationSucceeded;
859        String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
860        try {
861            mConnector.doCommand(cmd);
862        } catch (NativeDaemonConnectorException e) {
863            rc = StorageResultCode.OperationFailedInternalError;
864        }
865        return rc;
866    }
867
868    public int unmountSecureContainer(String id) {
869        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
870        waitForReady();
871        warnOnNotMounted();
872
873        int rc = StorageResultCode.OperationSucceeded;
874        String cmd = String.format("asec unmount %s", id);
875        try {
876            mConnector.doCommand(cmd);
877        } catch (NativeDaemonConnectorException e) {
878            rc = StorageResultCode.OperationFailedInternalError;
879        }
880        return rc;
881    }
882
883    public int renameSecureContainer(String oldId, String newId) {
884        validatePermission(android.Manifest.permission.ASEC_RENAME);
885        waitForReady();
886        warnOnNotMounted();
887
888        int rc = StorageResultCode.OperationSucceeded;
889        String cmd = String.format("asec rename %s %s", oldId, newId);
890        try {
891            mConnector.doCommand(cmd);
892        } catch (NativeDaemonConnectorException e) {
893            rc = StorageResultCode.OperationFailedInternalError;
894        }
895        return rc;
896    }
897
898    public String getSecureContainerPath(String id) {
899        validatePermission(android.Manifest.permission.ASEC_ACCESS);
900        waitForReady();
901        warnOnNotMounted();
902
903        ArrayList<String> rsp = mConnector.doCommand("asec path " + id);
904
905        for (String line : rsp) {
906            String []tok = line.split(" ");
907            int code = Integer.parseInt(tok[0]);
908            if (code == VoldResponseCode.AsecPathResult) {
909                return tok[1];
910            } else {
911                Log.e(TAG, String.format("Unexpected response code %d", code));
912                return "";
913            }
914        }
915
916        Log.e(TAG, "Got an empty response");
917        return "";
918    }
919}
920
921