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