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