MountService.java revision 2d66cef77e450ec4a4d725b89ae68c5e6b167bee
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 com.android.server.am.ActivityManagerService;
20
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.pm.PackageManager;
26import android.net.Uri;
27import android.os.storage.IMountService;
28import android.os.storage.IMountServiceListener;
29import android.os.storage.IMountShutdownObserver;
30import android.os.storage.StorageResultCode;
31import android.os.Handler;
32import android.os.HandlerThread;
33import android.os.Looper;
34import android.os.Message;
35import android.os.RemoteException;
36import android.os.IBinder;
37import android.os.Environment;
38import android.os.ServiceManager;
39import android.os.SystemClock;
40import android.os.SystemProperties;
41import android.util.Log;
42import java.util.ArrayList;
43import java.util.HashSet;
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    private static final boolean DEBUG_UNMOUNT = false;
55    private static final boolean DEBUG_EVENTS = false;
56
57    private static final String TAG = "MountService";
58
59    /*
60     * Internal vold volume state constants
61     */
62    class VolumeState {
63        public static final int Init       = -1;
64        public static final int NoMedia    = 0;
65        public static final int Idle       = 1;
66        public static final int Pending    = 2;
67        public static final int Checking   = 3;
68        public static final int Mounted    = 4;
69        public static final int Unmounting = 5;
70        public static final int Formatting = 6;
71        public static final int Shared     = 7;
72        public static final int SharedMnt  = 8;
73    }
74
75    /*
76     * Internal vold response code constants
77     */
78    class VoldResponseCode {
79        /*
80         * 100 series - Requestion action was initiated; expect another reply
81         *              before proceeding with a new command.
82         */
83        public static final int VolumeListResult               = 110;
84        public static final int AsecListResult                 = 111;
85        public static final int StorageUsersListResult         = 112;
86
87        /*
88         * 200 series - Requestion action has been successfully completed.
89         */
90        public static final int ShareStatusResult              = 210;
91        public static final int AsecPathResult                 = 211;
92        public static final int ShareEnabledResult             = 212;
93
94        /*
95         * 400 series - Command was accepted, but the requested action
96         *              did not take place.
97         */
98        public static final int OpFailedNoMedia                = 401;
99        public static final int OpFailedMediaBlank             = 402;
100        public static final int OpFailedMediaCorrupt           = 403;
101        public static final int OpFailedVolNotMounted          = 404;
102        public static final int OpFailedStorageBusy            = 405;
103        public static final int OpFailedStorageNotFound        = 406;
104
105        /*
106         * 600 series - Unsolicited broadcasts.
107         */
108        public static final int VolumeStateChange              = 605;
109        public static final int ShareAvailabilityChange        = 620;
110        public static final int VolumeDiskInserted             = 630;
111        public static final int VolumeDiskRemoved              = 631;
112        public static final int VolumeBadRemoval               = 632;
113    }
114
115    private Context                               mContext;
116    private NativeDaemonConnector                 mConnector;
117    private String                                mLegacyState = Environment.MEDIA_REMOVED;
118    private PackageManagerService                 mPms;
119    private boolean                               mUmsEnabling;
120    // Used as a lock for methods that register/unregister listeners.
121    final private ArrayList<MountServiceBinderListener> mListeners =
122            new ArrayList<MountServiceBinderListener>();
123    private boolean                               mBooted = false;
124    private boolean                               mReady = false;
125    private boolean                               mSendUmsConnectedOnBoot = false;
126
127    /**
128     * Private hash of currently mounted secure containers.
129     * Used as a lock in methods to manipulate secure containers.
130     */
131    final private HashSet<String> mAsecMountSet = new HashSet<String>();
132
133    private static final int H_UNMOUNT_PM_UPDATE = 1;
134    private static final int H_UNMOUNT_PM_DONE = 2;
135    private static final int H_UNMOUNT_MS = 3;
136    private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
137    private static final int MAX_UNMOUNT_RETRIES = 4;
138
139    class UnmountCallBack {
140        String path;
141        int retries;
142        boolean force;
143
144        UnmountCallBack(String path, boolean force) {
145            retries = 0;
146            this.path = path;
147            this.force = force;
148        }
149
150        void handleFinished() {
151            if (DEBUG_UNMOUNT) Log.i(TAG, "Unmounting " + path);
152            doUnmountVolume(path, true);
153        }
154    }
155
156    class UmsEnableCallBack extends UnmountCallBack {
157        String method;
158
159        UmsEnableCallBack(String path, String method, boolean force) {
160            super(path, force);
161            this.method = method;
162        }
163
164        @Override
165        void handleFinished() {
166            super.handleFinished();
167            doShareUnshareVolume(path, method, true);
168        }
169    }
170
171    class ShutdownCallBack extends UnmountCallBack {
172        IMountShutdownObserver observer;
173        ShutdownCallBack(String path, IMountShutdownObserver observer) {
174            super(path, true);
175            this.observer = observer;
176        }
177
178        @Override
179        void handleFinished() {
180            int ret = doUnmountVolume(path, true);
181            if (observer != null) {
182                try {
183                    observer.onShutDownComplete(ret);
184                } catch (RemoteException e) {
185                    Log.w(TAG, "RemoteException when shutting down");
186                }
187            }
188        }
189    }
190
191    class MountServiceHandler extends Handler {
192        ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
193        boolean mUpdatingStatus = false;
194
195        MountServiceHandler(Looper l) {
196            super(l);
197        }
198
199        public void handleMessage(Message msg) {
200            switch (msg.what) {
201                case H_UNMOUNT_PM_UPDATE: {
202                    if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_PM_UPDATE");
203                    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
204                    mForceUnmounts.add(ucb);
205                    if (DEBUG_UNMOUNT) Log.i(TAG, " registered = " + mUpdatingStatus);
206                    // Register only if needed.
207                    if (!mUpdatingStatus) {
208                        if (DEBUG_UNMOUNT) Log.i(TAG, "Updating external media status on PackageManager");
209                        mUpdatingStatus = true;
210                        mPms.updateExternalMediaStatus(false, true);
211                    }
212                    break;
213                }
214                case H_UNMOUNT_PM_DONE: {
215                    if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_PM_DONE");
216                    if (!mUpdatingStatus) {
217                        // Does not correspond to unmount's status update.
218                        return;
219                    }
220                    if (DEBUG_UNMOUNT) Log.i(TAG, "Updated status. Processing requests");
221                    mUpdatingStatus = false;
222                    int size = mForceUnmounts.size();
223                    int sizeArr[] = new int[size];
224                    int sizeArrN = 0;
225                    for (int i = 0; i < size; i++) {
226                        UnmountCallBack ucb = mForceUnmounts.get(i);
227                        String path = ucb.path;
228                        boolean done = false;
229                        if (!ucb.force) {
230                            done = true;
231                        } else {
232                            int pids[] = getStorageUsers(path);
233                            if (pids == null || pids.length == 0) {
234                                done = true;
235                            } else {
236                                // Kill processes holding references first
237                                ActivityManagerService ams = (ActivityManagerService)
238                                ServiceManager.getService("activity");
239                                // Eliminate system process here?
240                                boolean ret = ams.killPids(pids, "Unmount media");
241                                if (ret) {
242                                    // Confirm if file references have been freed.
243                                    pids = getStorageUsers(path);
244                                    if (pids == null || pids.length == 0) {
245                                        done = true;
246                                    }
247                                }
248                            }
249                        }
250                        if (done) {
251                            sizeArr[sizeArrN++] = i;
252                            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
253                                    ucb));
254                        } else {
255                            if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
256                                Log.i(TAG, "Cannot unmount media inspite of " +
257                                        MAX_UNMOUNT_RETRIES + " retries");
258                                // Send final broadcast indicating failure to unmount.
259                            } else {
260                                mHandler.sendMessageDelayed(
261                                        mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
262                                                ucb.retries++),
263                                        RETRY_UNMOUNT_DELAY);
264                            }
265                        }
266                    }
267                    // Remove already processed elements from list.
268                    for (int i = (sizeArrN-1); i >= 0; i--) {
269                        mForceUnmounts.remove(sizeArr[i]);
270                    }
271                    break;
272                }
273                case H_UNMOUNT_MS : {
274                    if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_MS");
275                    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
276                    ucb.handleFinished();
277                    break;
278                }
279            }
280        }
281    };
282    final private HandlerThread mHandlerThread;
283    final private Handler mHandler;
284
285    private void waitForReady() {
286        while (mReady == false) {
287            for (int retries = 5; retries > 0; retries--) {
288                if (mReady) {
289                    return;
290                }
291                SystemClock.sleep(1000);
292            }
293            Log.w(TAG, "Waiting too long for mReady!");
294        }
295    }
296
297    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
298        public void onReceive(Context context, Intent intent) {
299            String action = intent.getAction();
300
301            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
302                mBooted = true;
303
304                /*
305                 * In the simulator, we need to broadcast a volume mounted event
306                 * to make the media scanner run.
307                 */
308                if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
309                    notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted);
310                    return;
311                }
312                new Thread() {
313                    public void run() {
314                        try {
315                            String path = Environment.getExternalStorageDirectory().getPath();
316                            String state = getVolumeState(path);
317
318                            if (state.equals(Environment.MEDIA_UNMOUNTED)) {
319                                int rc = doMountVolume(path);
320                                if (rc != StorageResultCode.OperationSucceeded) {
321                                    Log.e(TAG, String.format("Boot-time mount failed (%d)", rc));
322                                }
323                            } else if (state.equals(Environment.MEDIA_SHARED)) {
324                                /*
325                                 * Bootstrap UMS enabled state since vold indicates
326                                 * the volume is shared (runtime restart while ums enabled)
327                                 */
328                                notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared);
329                            }
330
331                            /*
332                             * If UMS was connected on boot, send the connected event
333                             * now that we're up.
334                             */
335                            if (mSendUmsConnectedOnBoot) {
336                                sendUmsIntent(true);
337                                mSendUmsConnectedOnBoot = false;
338                            }
339                        } catch (Exception ex) {
340                            Log.e(TAG, "Boot-time mount exception", ex);
341                        }
342                    }
343                }.start();
344            }
345        }
346    };
347
348    private final class MountServiceBinderListener implements IBinder.DeathRecipient {
349        final IMountServiceListener mListener;
350
351        MountServiceBinderListener(IMountServiceListener listener) {
352            mListener = listener;
353
354        }
355
356        public void binderDied() {
357            if (LOCAL_LOGD) Log.d(TAG, "An IMountServiceListener has died!");
358            synchronized(mListeners) {
359                mListeners.remove(this);
360                mListener.asBinder().unlinkToDeath(this, 0);
361            }
362        }
363    }
364
365    private void doShareUnshareVolume(String path, String method, boolean enable) {
366        // TODO: Add support for multiple share methods
367        if (!method.equals("ums")) {
368            throw new IllegalArgumentException(String.format("Method %s not supported", method));
369        }
370
371        try {
372            mConnector.doCommand(String.format(
373                    "volume %sshare %s %s", (enable ? "" : "un"), path, method));
374        } catch (NativeDaemonConnectorException e) {
375            Log.e(TAG, "Failed to share/unshare", e);
376        }
377    }
378
379    private void updatePublicVolumeState(String path, String state) {
380        if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
381            Log.w(TAG, "Multiple volumes not currently supported");
382            return;
383        }
384
385        if (mLegacyState.equals(state)) {
386            Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
387            return;
388        }
389        // Update state on PackageManager
390        if (Environment.MEDIA_UNMOUNTED.equals(state)) {
391            mPms.updateExternalMediaStatus(false, false);
392        } else if (Environment.MEDIA_MOUNTED.equals(state)) {
393            mPms.updateExternalMediaStatus(true, false);
394        }
395        String oldState = mLegacyState;
396        mLegacyState = state;
397
398        synchronized (mListeners) {
399            for (int i = mListeners.size() -1; i >= 0; i--) {
400                MountServiceBinderListener bl = mListeners.get(i);
401                try {
402                    bl.mListener.onStorageStateChanged(path, oldState, state);
403                } catch (RemoteException rex) {
404                    Log.e(TAG, "Listener dead");
405                    mListeners.remove(i);
406                } catch (Exception ex) {
407                    Log.e(TAG, "Listener failed", ex);
408                }
409            }
410        }
411    }
412
413    /**
414     *
415     * Callback from NativeDaemonConnector
416     */
417    public void onDaemonConnected() {
418        /*
419         * Since we'll be calling back into the NativeDaemonConnector,
420         * we need to do our work in a new thread.
421         */
422        new Thread() {
423            public void run() {
424                /**
425                 * Determine media state and UMS detection status
426                 */
427                String path = Environment.getExternalStorageDirectory().getPath();
428                String state = Environment.MEDIA_REMOVED;
429
430                try {
431                    String[] vols = mConnector.doListCommand(
432                        "volume list", VoldResponseCode.VolumeListResult);
433                    for (String volstr : vols) {
434                        String[] tok = volstr.split(" ");
435                        // FMT: <label> <mountpoint> <state>
436                        if (!tok[1].equals(path)) {
437                            Log.w(TAG, String.format(
438                                    "Skipping unknown volume '%s'",tok[1]));
439                            continue;
440                        }
441                        int st = Integer.parseInt(tok[2]);
442                        if (st == VolumeState.NoMedia) {
443                            state = Environment.MEDIA_REMOVED;
444                        } else if (st == VolumeState.Idle) {
445                            state = Environment.MEDIA_UNMOUNTED;
446                        } else if (st == VolumeState.Mounted) {
447                            state = Environment.MEDIA_MOUNTED;
448                            Log.i(TAG, "Media already mounted on daemon connection");
449                        } else if (st == VolumeState.Shared) {
450                            state = Environment.MEDIA_SHARED;
451                            Log.i(TAG, "Media shared on daemon connection");
452                        } else {
453                            throw new Exception(String.format("Unexpected state %d", st));
454                        }
455                    }
456                    if (state != null) {
457                        if (DEBUG_EVENTS) Log.i(TAG, "Updating valid state " + state);
458                        updatePublicVolumeState(path, state);
459                    }
460                } catch (Exception e) {
461                    Log.e(TAG, "Error processing initial volume state", e);
462                    updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
463                }
464
465                try {
466                    boolean avail = doGetShareMethodAvailable("ums");
467                    notifyShareAvailabilityChange("ums", avail);
468                } catch (Exception ex) {
469                    Log.w(TAG, "Failed to get share availability");
470                }
471                /*
472                 * Now that we've done our initialization, release
473                 * the hounds!
474                 */
475                mReady = true;
476            }
477        }.start();
478    }
479
480    /**
481     * Callback from NativeDaemonConnector
482     */
483    public boolean onEvent(int code, String raw, String[] cooked) {
484        Intent in = null;
485
486        if (DEBUG_EVENTS) {
487            StringBuilder builder = new StringBuilder();
488            builder.append("onEvent::");
489            builder.append(" raw= " + raw);
490            if (cooked != null) {
491                builder.append(" cooked = " );
492                for (String str : cooked) {
493                    builder.append(" " + str);
494                }
495            }
496            Log.i(TAG, builder.toString());
497        }
498        if (code == VoldResponseCode.VolumeStateChange) {
499            /*
500             * One of the volumes we're managing has changed state.
501             * Format: "NNN Volume <label> <path> state changed
502             * from <old_#> (<old_str>) to <new_#> (<new_str>)"
503             */
504            notifyVolumeStateChange(
505                    cooked[2], cooked[3], Integer.parseInt(cooked[7]),
506                            Integer.parseInt(cooked[10]));
507        } else if (code == VoldResponseCode.ShareAvailabilityChange) {
508            // FMT: NNN Share method <method> now <available|unavailable>
509            boolean avail = false;
510            if (cooked[5].equals("available")) {
511                avail = true;
512            }
513            notifyShareAvailabilityChange(cooked[3], avail);
514        } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
515                   (code == VoldResponseCode.VolumeDiskRemoved) ||
516                   (code == VoldResponseCode.VolumeBadRemoval)) {
517            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
518            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
519            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
520            final String label = cooked[2];
521            final String path = cooked[3];
522            int major = -1;
523            int minor = -1;
524
525            try {
526                String devComp = cooked[6].substring(1, cooked[6].length() -1);
527                String[] devTok = devComp.split(":");
528                major = Integer.parseInt(devTok[0]);
529                minor = Integer.parseInt(devTok[1]);
530            } catch (Exception ex) {
531                Log.e(TAG, "Failed to parse major/minor", ex);
532            }
533
534            if (code == VoldResponseCode.VolumeDiskInserted) {
535                new Thread() {
536                    public void run() {
537                        try {
538                            int rc;
539                            if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
540                                Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
541                            }
542                        } catch (Exception ex) {
543                            Log.w(TAG, "Failed to mount media on insertion", ex);
544                        }
545                    }
546                }.start();
547            } else if (code == VoldResponseCode.VolumeDiskRemoved) {
548                /*
549                 * This event gets trumped if we're already in BAD_REMOVAL state
550                 */
551                if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
552                    return true;
553                }
554                /* Send the media unmounted event first */
555                if (DEBUG_EVENTS) Log.i(TAG, "Sending unmounted event first");
556                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
557                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
558                mContext.sendBroadcast(in);
559
560                if (DEBUG_EVENTS) Log.i(TAG, "Sending media removed");
561                updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
562                in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
563            } else if (code == VoldResponseCode.VolumeBadRemoval) {
564                if (DEBUG_EVENTS) Log.i(TAG, "Sending unmounted event first");
565                /* Send the media unmounted event first */
566                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
567                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
568                mContext.sendBroadcast(in);
569
570                if (DEBUG_EVENTS) Log.i(TAG, "Sending media bad removal");
571                updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
572                in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
573            } else {
574                Log.e(TAG, String.format("Unknown code {%d}", code));
575            }
576        } else {
577            return false;
578        }
579
580        if (in != null) {
581            mContext.sendBroadcast(in);
582        }
583        return true;
584    }
585
586    private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
587        String vs = getVolumeState(path);
588        if (DEBUG_EVENTS) Log.i(TAG, "notifyVolumeStateChanged::" + vs);
589
590        Intent in = null;
591
592        if (oldState == VolumeState.Shared && newState != oldState) {
593            if (LOCAL_LOGD) Log.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
594            mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_UNSHARED,
595                                                Uri.parse("file://" + path)));
596        }
597
598        if (newState == VolumeState.Init) {
599        } else if (newState == VolumeState.NoMedia) {
600            // NoMedia is handled via Disk Remove events
601        } else if (newState == VolumeState.Idle) {
602            /*
603             * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
604             * if we're in the process of enabling UMS
605             */
606            if (!vs.equals(
607                    Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
608                            Environment.MEDIA_NOFS) && !vs.equals(
609                                    Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
610                if (DEBUG_EVENTS) Log.i(TAG, "updating volume state for media bad removal nofs and unmountable");
611                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
612                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
613            }
614        } else if (newState == VolumeState.Pending) {
615        } else if (newState == VolumeState.Checking) {
616            if (DEBUG_EVENTS) Log.i(TAG, "updating volume state checking");
617            updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
618            in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
619        } else if (newState == VolumeState.Mounted) {
620            if (DEBUG_EVENTS) Log.i(TAG, "updating volume state mounted");
621            updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
622            in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
623            in.putExtra("read-only", false);
624        } else if (newState == VolumeState.Unmounting) {
625            in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
626        } else if (newState == VolumeState.Formatting) {
627        } else if (newState == VolumeState.Shared) {
628            if (DEBUG_EVENTS) Log.i(TAG, "Updating volume state media mounted");
629            /* Send the media unmounted event first */
630            updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
631            in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
632            mContext.sendBroadcast(in);
633
634            if (DEBUG_EVENTS) Log.i(TAG, "Updating media shared");
635            updatePublicVolumeState(path, Environment.MEDIA_SHARED);
636            in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
637            if (LOCAL_LOGD) Log.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
638        } else if (newState == VolumeState.SharedMnt) {
639            Log.e(TAG, "Live shared mounts not supported yet!");
640            return;
641        } else {
642            Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
643        }
644
645        if (in != null) {
646            mContext.sendBroadcast(in);
647        }
648    }
649
650    private boolean doGetShareMethodAvailable(String method) {
651        ArrayList<String> rsp = mConnector.doCommand("share status " + method);
652
653        for (String line : rsp) {
654            String []tok = line.split(" ");
655            int code;
656            try {
657                code = Integer.parseInt(tok[0]);
658            } catch (NumberFormatException nfe) {
659                Log.e(TAG, String.format("Error parsing code %s", tok[0]));
660                return false;
661            }
662            if (code == VoldResponseCode.ShareStatusResult) {
663                if (tok[2].equals("available"))
664                    return true;
665                return false;
666            } else {
667                Log.e(TAG, String.format("Unexpected response code %d", code));
668                return false;
669            }
670        }
671        Log.e(TAG, "Got an empty response");
672        return false;
673    }
674
675    private int doMountVolume(String path) {
676        int rc = StorageResultCode.OperationSucceeded;
677
678        if (DEBUG_EVENTS) Log.i(TAG, "doMountVolume: Mouting " + path);
679        try {
680            mConnector.doCommand(String.format("volume mount %s", path));
681        } catch (NativeDaemonConnectorException e) {
682            /*
683             * Mount failed for some reason
684             */
685            Intent in = null;
686            int code = e.getCode();
687            if (code == VoldResponseCode.OpFailedNoMedia) {
688                /*
689                 * Attempt to mount but no media inserted
690                 */
691                rc = StorageResultCode.OperationFailedNoMedia;
692            } else if (code == VoldResponseCode.OpFailedMediaBlank) {
693                if (DEBUG_EVENTS) Log.i(TAG, " updating volume state :: media nofs");
694                /*
695                 * Media is blank or does not contain a supported filesystem
696                 */
697                updatePublicVolumeState(path, Environment.MEDIA_NOFS);
698                in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
699                rc = StorageResultCode.OperationFailedMediaBlank;
700            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
701                if (DEBUG_EVENTS) Log.i(TAG, "updating volume state media corrupt");
702                /*
703                 * Volume consistency check failed
704                 */
705                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
706                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
707                rc = StorageResultCode.OperationFailedMediaCorrupt;
708            } else {
709                rc = StorageResultCode.OperationFailedInternalError;
710            }
711
712            /*
713             * Send broadcast intent (if required for the failure)
714             */
715            if (in != null) {
716                mContext.sendBroadcast(in);
717            }
718        }
719
720        return rc;
721    }
722
723    /*
724     * If force is not set, we do not unmount if there are
725     * processes holding references to the volume about to be unmounted.
726     * If force is set, all the processes holding references need to be
727     * killed via the ActivityManager before actually unmounting the volume.
728     * This might even take a while and might be retried after timed delays
729     * to make sure we dont end up in an instable state and kill some core
730     * processes.
731     */
732    private int doUnmountVolume(String path, boolean force) {
733        if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
734            return VoldResponseCode.OpFailedVolNotMounted;
735        }
736        // Redundant probably. But no harm in updating state again.
737        mPms.updateExternalMediaStatus(false, false);
738        try {
739            mConnector.doCommand(String.format(
740                    "volume unmount %s%s", path, (force ? " force" : "")));
741            // We unmounted the volume. None of the asec containers are available now.
742            synchronized (mAsecMountSet) {
743                mAsecMountSet.clear();
744            }
745            return StorageResultCode.OperationSucceeded;
746        } catch (NativeDaemonConnectorException e) {
747            // Don't worry about mismatch in PackageManager since the
748            // call back will handle the status changes any way.
749            int code = e.getCode();
750            if (code == VoldResponseCode.OpFailedVolNotMounted) {
751                return StorageResultCode.OperationFailedStorageNotMounted;
752            } else if (code == VoldResponseCode.OpFailedStorageBusy) {
753                return StorageResultCode.OperationFailedStorageBusy;
754            } else {
755                return StorageResultCode.OperationFailedInternalError;
756            }
757        }
758    }
759
760    private int doFormatVolume(String path) {
761        try {
762            String cmd = String.format("volume format %s", path);
763            mConnector.doCommand(cmd);
764            return StorageResultCode.OperationSucceeded;
765        } catch (NativeDaemonConnectorException e) {
766            int code = e.getCode();
767            if (code == VoldResponseCode.OpFailedNoMedia) {
768                return StorageResultCode.OperationFailedNoMedia;
769            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
770                return StorageResultCode.OperationFailedMediaCorrupt;
771            } else {
772                return StorageResultCode.OperationFailedInternalError;
773            }
774        }
775    }
776
777    private boolean doGetVolumeShared(String path, String method) {
778        String cmd = String.format("volume shared %s %s", path, method);
779        ArrayList<String> rsp = mConnector.doCommand(cmd);
780
781        for (String line : rsp) {
782            String []tok = line.split(" ");
783            int code;
784            try {
785                code = Integer.parseInt(tok[0]);
786            } catch (NumberFormatException nfe) {
787                Log.e(TAG, String.format("Error parsing code %s", tok[0]));
788                return false;
789            }
790            if (code == VoldResponseCode.ShareEnabledResult) {
791                if (tok[2].equals("enabled"))
792                    return true;
793                return false;
794            } else {
795                Log.e(TAG, String.format("Unexpected response code %d", code));
796                return false;
797            }
798        }
799        Log.e(TAG, "Got an empty response");
800        return false;
801    }
802
803    private void notifyShareAvailabilityChange(String method, final boolean avail) {
804        if (!method.equals("ums")) {
805           Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
806           return;
807        }
808
809        synchronized (mListeners) {
810            for (int i = mListeners.size() -1; i >= 0; i--) {
811                MountServiceBinderListener bl = mListeners.get(i);
812                try {
813                    bl.mListener.onUsbMassStorageConnectionChanged(avail);
814                } catch (RemoteException rex) {
815                    Log.e(TAG, "Listener dead");
816                    mListeners.remove(i);
817                } catch (Exception ex) {
818                    Log.e(TAG, "Listener failed", ex);
819                }
820            }
821        }
822
823        if (mBooted == true) {
824            sendUmsIntent(avail);
825        } else {
826            mSendUmsConnectedOnBoot = avail;
827        }
828
829        final String path = Environment.getExternalStorageDirectory().getPath();
830        if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) {
831            /*
832             * USB mass storage disconnected while enabled
833             */
834            new Thread() {
835                public void run() {
836                    try {
837                        int rc;
838                        Log.w(TAG, "Disabling UMS after cable disconnect");
839                        doShareUnshareVolume(path, "ums", false);
840                        if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
841                            Log.e(TAG, String.format(
842                                    "Failed to remount {%s} on UMS enabled-disconnect (%d)",
843                                            path, rc));
844                        }
845                    } catch (Exception ex) {
846                        Log.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
847                    }
848                }
849            }.start();
850        }
851    }
852
853    private void sendUmsIntent(boolean c) {
854        mContext.sendBroadcast(
855                new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)));
856    }
857
858    private void validatePermission(String perm) {
859        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
860            throw new SecurityException(String.format("Requires %s permission", perm));
861        }
862    }
863
864    /**
865     * Constructs a new MountService instance
866     *
867     * @param context  Binder context for this service
868     */
869    public MountService(Context context) {
870        mContext = context;
871
872        // XXX: This will go away soon in favor of IMountServiceObserver
873        mPms = (PackageManagerService) ServiceManager.getService("package");
874
875        mContext.registerReceiver(mBroadcastReceiver,
876                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
877
878        mHandlerThread = new HandlerThread("MountService");
879        mHandlerThread.start();
880        mHandler = new MountServiceHandler(mHandlerThread.getLooper());
881
882        /*
883         * Vold does not run in the simulator, so pretend the connector thread
884         * ran and did its thing.
885         */
886        if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
887            mReady = true;
888            mUmsEnabling = true;
889            return;
890        }
891
892        mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
893        mReady = false;
894        Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
895        thread.start();
896    }
897
898    /**
899     * Exposed API calls below here
900     */
901
902    public void registerListener(IMountServiceListener listener) {
903        synchronized (mListeners) {
904            MountServiceBinderListener bl = new MountServiceBinderListener(listener);
905            try {
906                listener.asBinder().linkToDeath(bl, 0);
907                mListeners.add(bl);
908            } catch (RemoteException rex) {
909                Log.e(TAG, "Failed to link to listener death");
910            }
911        }
912    }
913
914    public void unregisterListener(IMountServiceListener listener) {
915        synchronized (mListeners) {
916            for(MountServiceBinderListener bl : mListeners) {
917                if (bl.mListener == listener) {
918                    mListeners.remove(mListeners.indexOf(bl));
919                    return;
920                }
921            }
922        }
923    }
924
925    public void shutdown(final IMountShutdownObserver observer) {
926        validatePermission(android.Manifest.permission.SHUTDOWN);
927
928        Log.i(TAG, "Shutting down");
929
930        String path = Environment.getExternalStorageDirectory().getPath();
931        String state = getVolumeState(path);
932
933        if (state.equals(Environment.MEDIA_SHARED)) {
934            /*
935             * If the media is currently shared, unshare it.
936             * XXX: This is still dangerous!. We should not
937             * be rebooting at *all* if UMS is enabled, since
938             * the UMS host could have dirty FAT cache entries
939             * yet to flush.
940             */
941            setUsbMassStorageEnabled(false);
942        } else if (state.equals(Environment.MEDIA_CHECKING)) {
943            /*
944             * If the media is being checked, then we need to wait for
945             * it to complete before being able to proceed.
946             */
947            // XXX: @hackbod - Should we disable the ANR timer here?
948            int retries = 30;
949            while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
950                try {
951                    Thread.sleep(1000);
952                } catch (InterruptedException iex) {
953                    Log.e(TAG, "Interrupted while waiting for media", iex);
954                    break;
955                }
956                state = Environment.getExternalStorageState();
957            }
958            if (retries == 0) {
959                Log.e(TAG, "Timed out waiting for media to check");
960            }
961        }
962
963        if (state.equals(Environment.MEDIA_MOUNTED)) {
964            // Post a unmount message.
965            ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
966            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
967        }
968    }
969
970    private boolean getUmsEnabling() {
971        synchronized (mListeners) {
972            return mUmsEnabling;
973        }
974    }
975
976    private void setUmsEnabling(boolean enable) {
977        synchronized (mListeners) {
978            mUmsEnabling = true;
979        }
980    }
981
982    public boolean isUsbMassStorageConnected() {
983        waitForReady();
984
985        if (getUmsEnabling()) {
986            return true;
987        }
988        return doGetShareMethodAvailable("ums");
989    }
990
991    public void setUsbMassStorageEnabled(boolean enable) {
992        waitForReady();
993        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
994
995        // TODO: Add support for multiple share methods
996
997        /*
998         * If the volume is mounted and we're enabling then unmount it
999         */
1000        String path = Environment.getExternalStorageDirectory().getPath();
1001        String vs = getVolumeState(path);
1002        String method = "ums";
1003        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
1004            // Override for isUsbMassStorageEnabled()
1005            setUmsEnabling(enable);
1006            UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
1007            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
1008            // Clear override
1009            setUmsEnabling(false);
1010        }
1011        /*
1012         * If we disabled UMS then mount the volume
1013         */
1014        if (!enable) {
1015            doShareUnshareVolume(path, method, enable);
1016            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
1017                Log.e(TAG, "Failed to remount " + path +
1018                        " after disabling share method " + method);
1019                /*
1020                 * Even though the mount failed, the unshare didn't so don't indicate an error.
1021                 * The mountVolume() call will have set the storage state and sent the necessary
1022                 * broadcasts.
1023                 */
1024            }
1025        }
1026    }
1027
1028    public boolean isUsbMassStorageEnabled() {
1029        waitForReady();
1030        return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
1031    }
1032
1033    /**
1034     * @return state of the volume at the specified mount point
1035     */
1036    public String getVolumeState(String mountPoint) {
1037        /*
1038         * XXX: Until we have multiple volume discovery, just hardwire
1039         * this to /sdcard
1040         */
1041        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
1042            Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
1043            throw new IllegalArgumentException();
1044        }
1045
1046        return mLegacyState;
1047    }
1048
1049    public int mountVolume(String path) {
1050        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1051
1052        waitForReady();
1053        return doMountVolume(path);
1054    }
1055
1056    public void unmountVolume(String path, boolean force) {
1057        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1058        waitForReady();
1059
1060        String volState = getVolumeState(path);
1061        if (DEBUG_UNMOUNT) Log.i(TAG, "Unmounting " + path + " force = " + force);
1062        if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
1063                Environment.MEDIA_REMOVED.equals(volState) ||
1064                Environment.MEDIA_SHARED.equals(volState) ||
1065                Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
1066            // Media already unmounted or cannot be unmounted.
1067            // TODO return valid return code when adding observer call back.
1068            return;
1069        }
1070        UnmountCallBack ucb = new UnmountCallBack(path, force);
1071        mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
1072    }
1073
1074    public int formatVolume(String path) {
1075        validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
1076        waitForReady();
1077
1078        return doFormatVolume(path);
1079    }
1080
1081    public int []getStorageUsers(String path) {
1082        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1083        waitForReady();
1084        try {
1085            String[] r = mConnector.doListCommand(
1086                    String.format("storage users %s", path),
1087                            VoldResponseCode.StorageUsersListResult);
1088            // FMT: <pid> <process name>
1089            int[] data = new int[r.length];
1090            for (int i = 0; i < r.length; i++) {
1091                String []tok = r[i].split(" ");
1092                try {
1093                    data[i] = Integer.parseInt(tok[0]);
1094                } catch (NumberFormatException nfe) {
1095                    Log.e(TAG, String.format("Error parsing pid %s", tok[0]));
1096                    return new int[0];
1097                }
1098            }
1099            return data;
1100        } catch (NativeDaemonConnectorException e) {
1101            Log.e(TAG, "Failed to retrieve storage users list", e);
1102            return new int[0];
1103        }
1104    }
1105
1106    private void warnOnNotMounted() {
1107        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1108            Log.w(TAG, "getSecureContainerList() called when storage not mounted");
1109        }
1110    }
1111
1112    public String[] getSecureContainerList() {
1113        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1114        waitForReady();
1115        warnOnNotMounted();
1116
1117        try {
1118            return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
1119        } catch (NativeDaemonConnectorException e) {
1120            return new String[0];
1121        }
1122    }
1123
1124    public int createSecureContainer(String id, int sizeMb, String fstype,
1125                                    String key, int ownerUid) {
1126        validatePermission(android.Manifest.permission.ASEC_CREATE);
1127        waitForReady();
1128        warnOnNotMounted();
1129
1130        int rc = StorageResultCode.OperationSucceeded;
1131        String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
1132        try {
1133            mConnector.doCommand(cmd);
1134        } catch (NativeDaemonConnectorException e) {
1135            rc = StorageResultCode.OperationFailedInternalError;
1136        }
1137
1138        if (rc == StorageResultCode.OperationSucceeded) {
1139            synchronized (mAsecMountSet) {
1140                mAsecMountSet.add(id);
1141            }
1142        }
1143        return rc;
1144    }
1145
1146    public int finalizeSecureContainer(String id) {
1147        validatePermission(android.Manifest.permission.ASEC_CREATE);
1148        warnOnNotMounted();
1149
1150        int rc = StorageResultCode.OperationSucceeded;
1151        try {
1152            mConnector.doCommand(String.format("asec finalize %s", id));
1153            /*
1154             * Finalization does a remount, so no need
1155             * to update mAsecMountSet
1156             */
1157        } catch (NativeDaemonConnectorException e) {
1158            rc = StorageResultCode.OperationFailedInternalError;
1159        }
1160        return rc;
1161    }
1162
1163    public int destroySecureContainer(String id, boolean force) {
1164        validatePermission(android.Manifest.permission.ASEC_DESTROY);
1165        waitForReady();
1166        warnOnNotMounted();
1167
1168        int rc = StorageResultCode.OperationSucceeded;
1169        try {
1170            mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : "")));
1171        } catch (NativeDaemonConnectorException e) {
1172            int code = e.getCode();
1173            if (code == VoldResponseCode.OpFailedStorageBusy) {
1174                rc = StorageResultCode.OperationFailedStorageBusy;
1175            } else {
1176                rc = StorageResultCode.OperationFailedInternalError;
1177            }
1178        }
1179
1180        if (rc == StorageResultCode.OperationSucceeded) {
1181            synchronized (mAsecMountSet) {
1182                if (mAsecMountSet.contains(id)) {
1183                    mAsecMountSet.remove(id);
1184                }
1185            }
1186        }
1187
1188        return rc;
1189    }
1190
1191    public int mountSecureContainer(String id, String key, int ownerUid) {
1192        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1193        waitForReady();
1194        warnOnNotMounted();
1195
1196        synchronized (mAsecMountSet) {
1197            if (mAsecMountSet.contains(id)) {
1198                return StorageResultCode.OperationFailedStorageMounted;
1199            }
1200        }
1201
1202        int rc = StorageResultCode.OperationSucceeded;
1203        String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
1204        try {
1205            mConnector.doCommand(cmd);
1206        } catch (NativeDaemonConnectorException e) {
1207            int code = e.getCode();
1208            if (code != VoldResponseCode.OpFailedStorageBusy) {
1209                rc = StorageResultCode.OperationFailedInternalError;
1210            }
1211        }
1212
1213        if (rc == StorageResultCode.OperationSucceeded) {
1214            synchronized (mAsecMountSet) {
1215                mAsecMountSet.add(id);
1216            }
1217        }
1218        return rc;
1219    }
1220
1221    public int unmountSecureContainer(String id, boolean force) {
1222        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1223        waitForReady();
1224        warnOnNotMounted();
1225
1226        synchronized (mAsecMountSet) {
1227            if (!mAsecMountSet.contains(id)) {
1228                return StorageResultCode.OperationFailedStorageNotMounted;
1229            }
1230         }
1231
1232        int rc = StorageResultCode.OperationSucceeded;
1233        String cmd = String.format("asec unmount %s%s", id, (force ? " force" : ""));
1234        try {
1235            mConnector.doCommand(cmd);
1236        } catch (NativeDaemonConnectorException e) {
1237            int code = e.getCode();
1238            if (code == VoldResponseCode.OpFailedStorageBusy) {
1239                rc = StorageResultCode.OperationFailedStorageBusy;
1240            } else {
1241                rc = StorageResultCode.OperationFailedInternalError;
1242            }
1243        }
1244
1245        if (rc == StorageResultCode.OperationSucceeded) {
1246            synchronized (mAsecMountSet) {
1247                mAsecMountSet.remove(id);
1248            }
1249        }
1250        return rc;
1251    }
1252
1253    public boolean isSecureContainerMounted(String id) {
1254        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1255        waitForReady();
1256        warnOnNotMounted();
1257
1258        synchronized (mAsecMountSet) {
1259            return mAsecMountSet.contains(id);
1260        }
1261    }
1262
1263    public int renameSecureContainer(String oldId, String newId) {
1264        validatePermission(android.Manifest.permission.ASEC_RENAME);
1265        waitForReady();
1266        warnOnNotMounted();
1267
1268        synchronized (mAsecMountSet) {
1269            /*
1270             * Because a mounted container has active internal state which cannot be
1271             * changed while active, we must ensure both ids are not currently mounted.
1272             */
1273            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
1274                return StorageResultCode.OperationFailedStorageMounted;
1275            }
1276        }
1277
1278        int rc = StorageResultCode.OperationSucceeded;
1279        String cmd = String.format("asec rename %s %s", oldId, newId);
1280        try {
1281            mConnector.doCommand(cmd);
1282        } catch (NativeDaemonConnectorException e) {
1283            rc = StorageResultCode.OperationFailedInternalError;
1284        }
1285
1286        return rc;
1287    }
1288
1289    public String getSecureContainerPath(String id) {
1290        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1291        waitForReady();
1292        warnOnNotMounted();
1293
1294        try {
1295            ArrayList<String> rsp = mConnector.doCommand(String.format("asec path %s", id));
1296            String []tok = rsp.get(0).split(" ");
1297            int code = Integer.parseInt(tok[0]);
1298            if (code != VoldResponseCode.AsecPathResult) {
1299                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1300            }
1301            return tok[1];
1302        } catch (NativeDaemonConnectorException e) {
1303            int code = e.getCode();
1304            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1305                throw new IllegalArgumentException(String.format("Container '%s' not found", id));
1306            } else {
1307                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1308            }
1309        }
1310    }
1311
1312    public void finishMediaUpdate() {
1313        mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
1314    }
1315}
1316
1317