MountService.java revision bf0cb26a1c6305f2a7795c2498591b6189cc5b79
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.internal.app.IMediaContainerService;
20import com.android.server.am.ActivityManagerService;
21
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.ServiceConnection;
28import android.content.pm.PackageManager;
29import android.content.res.ObbInfo;
30import android.net.Uri;
31import android.os.Binder;
32import android.os.Environment;
33import android.os.Handler;
34import android.os.HandlerThread;
35import android.os.IBinder;
36import android.os.Looper;
37import android.os.Message;
38import android.os.RemoteException;
39import android.os.ServiceManager;
40import android.os.SystemClock;
41import android.os.SystemProperties;
42import android.os.storage.IMountService;
43import android.os.storage.IMountServiceListener;
44import android.os.storage.IMountShutdownObserver;
45import android.os.storage.IObbActionListener;
46import android.os.storage.StorageResultCode;
47import android.util.Slog;
48
49import java.util.ArrayList;
50import java.util.HashMap;
51import java.util.HashSet;
52import java.util.LinkedList;
53import java.util.List;
54import java.util.Map;
55
56/**
57 * MountService implements back-end services for platform storage
58 * management.
59 * @hide - Applications should use android.os.storage.StorageManager
60 * to access the MountService.
61 */
62class MountService extends IMountService.Stub
63        implements INativeDaemonConnectorCallbacks {
64    private static final boolean LOCAL_LOGD = false;
65    private static final boolean DEBUG_UNMOUNT = false;
66    private static final boolean DEBUG_EVENTS = false;
67    private static final boolean DEBUG_OBB = true;
68
69    private static final String TAG = "MountService";
70
71    /*
72     * Internal vold volume state constants
73     */
74    class VolumeState {
75        public static final int Init       = -1;
76        public static final int NoMedia    = 0;
77        public static final int Idle       = 1;
78        public static final int Pending    = 2;
79        public static final int Checking   = 3;
80        public static final int Mounted    = 4;
81        public static final int Unmounting = 5;
82        public static final int Formatting = 6;
83        public static final int Shared     = 7;
84        public static final int SharedMnt  = 8;
85    }
86
87    /*
88     * Internal vold response code constants
89     */
90    class VoldResponseCode {
91        /*
92         * 100 series - Requestion action was initiated; expect another reply
93         *              before proceeding with a new command.
94         */
95        public static final int VolumeListResult               = 110;
96        public static final int AsecListResult                 = 111;
97        public static final int StorageUsersListResult         = 112;
98
99        /*
100         * 200 series - Requestion action has been successfully completed.
101         */
102        public static final int ShareStatusResult              = 210;
103        public static final int AsecPathResult                 = 211;
104        public static final int ShareEnabledResult             = 212;
105
106        /*
107         * 400 series - Command was accepted, but the requested action
108         *              did not take place.
109         */
110        public static final int OpFailedNoMedia                = 401;
111        public static final int OpFailedMediaBlank             = 402;
112        public static final int OpFailedMediaCorrupt           = 403;
113        public static final int OpFailedVolNotMounted          = 404;
114        public static final int OpFailedStorageBusy            = 405;
115        public static final int OpFailedStorageNotFound        = 406;
116
117        /*
118         * 600 series - Unsolicited broadcasts.
119         */
120        public static final int VolumeStateChange              = 605;
121        public static final int ShareAvailabilityChange        = 620;
122        public static final int VolumeDiskInserted             = 630;
123        public static final int VolumeDiskRemoved              = 631;
124        public static final int VolumeBadRemoval               = 632;
125    }
126
127    private Context                               mContext;
128    private NativeDaemonConnector                 mConnector;
129    private String                                mLegacyState = Environment.MEDIA_REMOVED;
130    private PackageManagerService                 mPms;
131    private boolean                               mUmsEnabling;
132    // Used as a lock for methods that register/unregister listeners.
133    final private ArrayList<MountServiceBinderListener> mListeners =
134            new ArrayList<MountServiceBinderListener>();
135    private boolean                               mBooted = false;
136    private boolean                               mReady = false;
137    private boolean                               mSendUmsConnectedOnBoot = false;
138    // true if we should fake MEDIA_MOUNTED state for external storage
139    private boolean                               mEmulateExternalStorage = false;
140
141    /**
142     * Private hash of currently mounted secure containers.
143     * Used as a lock in methods to manipulate secure containers.
144     */
145    final private HashSet<String> mAsecMountSet = new HashSet<String>();
146
147    /**
148     * Mounted OBB tracking information. Used to track the current state of all
149     * OBBs.
150     */
151    final private Map<IObbActionListener, ObbState> mObbMounts = new HashMap<IObbActionListener, ObbState>();
152    final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
153
154    class ObbState implements IBinder.DeathRecipient {
155        public ObbState(String filename, IObbActionListener token, int callerUid) {
156            this.filename = filename;
157            this.token = token;
158            this.callerUid = callerUid;
159            mounted = false;
160        }
161
162        // OBB source filename
163        String filename;
164
165        // Token of remote Binder caller
166        IObbActionListener token;
167
168        // Binder.callingUid()
169        public int callerUid;
170
171        // Whether this is mounted currently.
172        boolean mounted;
173
174        @Override
175        public void binderDied() {
176            ObbAction action = new UnmountObbAction(this, true);
177            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
178
179            removeObbState(this);
180
181            token.asBinder().unlinkToDeath(this, 0);
182        }
183    }
184
185    // OBB Action Handler
186    final private ObbActionHandler mObbActionHandler;
187
188    // OBB action handler messages
189    private static final int OBB_RUN_ACTION = 1;
190    private static final int OBB_MCS_BOUND = 2;
191    private static final int OBB_MCS_UNBIND = 3;
192    private static final int OBB_MCS_RECONNECT = 4;
193    private static final int OBB_MCS_GIVE_UP = 5;
194
195    /*
196     * Default Container Service information
197     */
198    static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
199            "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService");
200
201    final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
202
203    class DefaultContainerConnection implements ServiceConnection {
204        public void onServiceConnected(ComponentName name, IBinder service) {
205            if (DEBUG_OBB)
206                Slog.i(TAG, "onServiceConnected");
207            IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
208            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
209        }
210
211        public void onServiceDisconnected(ComponentName name) {
212            if (DEBUG_OBB)
213                Slog.i(TAG, "onServiceDisconnected");
214        }
215    };
216
217    // Used in the ObbActionHandler
218    private IMediaContainerService mContainerService = null;
219
220    // Handler messages
221    private static final int H_UNMOUNT_PM_UPDATE = 1;
222    private static final int H_UNMOUNT_PM_DONE = 2;
223    private static final int H_UNMOUNT_MS = 3;
224    private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
225    private static final int MAX_UNMOUNT_RETRIES = 4;
226
227    class UnmountCallBack {
228        String path;
229        int retries;
230        boolean force;
231
232        UnmountCallBack(String path, boolean force) {
233            retries = 0;
234            this.path = path;
235            this.force = force;
236        }
237
238        void handleFinished() {
239            if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path);
240            doUnmountVolume(path, true);
241        }
242    }
243
244    class UmsEnableCallBack extends UnmountCallBack {
245        String method;
246
247        UmsEnableCallBack(String path, String method, boolean force) {
248            super(path, force);
249            this.method = method;
250        }
251
252        @Override
253        void handleFinished() {
254            super.handleFinished();
255            doShareUnshareVolume(path, method, true);
256        }
257    }
258
259    class ShutdownCallBack extends UnmountCallBack {
260        IMountShutdownObserver observer;
261        ShutdownCallBack(String path, IMountShutdownObserver observer) {
262            super(path, true);
263            this.observer = observer;
264        }
265
266        @Override
267        void handleFinished() {
268            int ret = doUnmountVolume(path, true);
269            if (observer != null) {
270                try {
271                    observer.onShutDownComplete(ret);
272                } catch (RemoteException e) {
273                    Slog.w(TAG, "RemoteException when shutting down");
274                }
275            }
276        }
277    }
278
279    class MountServiceHandler extends Handler {
280        ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
281        boolean mUpdatingStatus = false;
282
283        MountServiceHandler(Looper l) {
284            super(l);
285        }
286
287        public void handleMessage(Message msg) {
288            switch (msg.what) {
289                case H_UNMOUNT_PM_UPDATE: {
290                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE");
291                    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
292                    mForceUnmounts.add(ucb);
293                    if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus);
294                    // Register only if needed.
295                    if (!mUpdatingStatus) {
296                        if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
297                        mUpdatingStatus = true;
298                        mPms.updateExternalMediaStatus(false, true);
299                    }
300                    break;
301                }
302                case H_UNMOUNT_PM_DONE: {
303                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE");
304                    if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests");
305                    mUpdatingStatus = false;
306                    int size = mForceUnmounts.size();
307                    int sizeArr[] = new int[size];
308                    int sizeArrN = 0;
309                    // Kill processes holding references first
310                    ActivityManagerService ams = (ActivityManagerService)
311                    ServiceManager.getService("activity");
312                    for (int i = 0; i < size; i++) {
313                        UnmountCallBack ucb = mForceUnmounts.get(i);
314                        String path = ucb.path;
315                        boolean done = false;
316                        if (!ucb.force) {
317                            done = true;
318                        } else {
319                            int pids[] = getStorageUsers(path);
320                            if (pids == null || pids.length == 0) {
321                                done = true;
322                            } else {
323                                // Eliminate system process here?
324                                ams.killPids(pids, "unmount media");
325                                // Confirm if file references have been freed.
326                                pids = getStorageUsers(path);
327                                if (pids == null || pids.length == 0) {
328                                    done = true;
329                                }
330                            }
331                        }
332                        if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
333                            // Retry again
334                            Slog.i(TAG, "Retrying to kill storage users again");
335                            mHandler.sendMessageDelayed(
336                                    mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
337                                            ucb.retries++),
338                                    RETRY_UNMOUNT_DELAY);
339                        } else {
340                            if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
341                                Slog.i(TAG, "Failed to unmount media inspite of " +
342                                        MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
343                            }
344                            sizeArr[sizeArrN++] = i;
345                            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
346                                    ucb));
347                        }
348                    }
349                    // Remove already processed elements from list.
350                    for (int i = (sizeArrN-1); i >= 0; i--) {
351                        mForceUnmounts.remove(sizeArr[i]);
352                    }
353                    break;
354                }
355                case H_UNMOUNT_MS : {
356                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS");
357                    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
358                    ucb.handleFinished();
359                    break;
360                }
361            }
362        }
363    };
364    final private HandlerThread mHandlerThread;
365    final private Handler mHandler;
366
367    private void waitForReady() {
368        while (mReady == false) {
369            for (int retries = 5; retries > 0; retries--) {
370                if (mReady) {
371                    return;
372                }
373                SystemClock.sleep(1000);
374            }
375            Slog.w(TAG, "Waiting too long for mReady!");
376        }
377    }
378
379    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
380        public void onReceive(Context context, Intent intent) {
381            String action = intent.getAction();
382
383            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
384                mBooted = true;
385
386                /*
387                 * In the simulator, we need to broadcast a volume mounted event
388                 * to make the media scanner run.
389                 */
390                if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
391                    notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted);
392                    return;
393                }
394                new Thread() {
395                    public void run() {
396                        try {
397                            String path = Environment.getExternalStorageDirectory().getPath();
398                            String state = getVolumeState(path);
399
400                            if (mEmulateExternalStorage) {
401                                notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Mounted);
402                            } else if (state.equals(Environment.MEDIA_UNMOUNTED)) {
403                                int rc = doMountVolume(path);
404                                if (rc != StorageResultCode.OperationSucceeded) {
405                                    Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc));
406                                }
407                            } else if (state.equals(Environment.MEDIA_SHARED)) {
408                                /*
409                                 * Bootstrap UMS enabled state since vold indicates
410                                 * the volume is shared (runtime restart while ums enabled)
411                                 */
412                                notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared);
413                            }
414
415                            /*
416                             * If UMS was connected on boot, send the connected event
417                             * now that we're up.
418                             */
419                            if (mSendUmsConnectedOnBoot) {
420                                sendUmsIntent(true);
421                                mSendUmsConnectedOnBoot = false;
422                            }
423                        } catch (Exception ex) {
424                            Slog.e(TAG, "Boot-time mount exception", ex);
425                        }
426                    }
427                }.start();
428            }
429        }
430    };
431
432    private final class MountServiceBinderListener implements IBinder.DeathRecipient {
433        final IMountServiceListener mListener;
434
435        MountServiceBinderListener(IMountServiceListener listener) {
436            mListener = listener;
437
438        }
439
440        public void binderDied() {
441            if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
442            synchronized (mListeners) {
443                mListeners.remove(this);
444                mListener.asBinder().unlinkToDeath(this, 0);
445            }
446        }
447    }
448
449    private void doShareUnshareVolume(String path, String method, boolean enable) {
450        // TODO: Add support for multiple share methods
451        if (!method.equals("ums")) {
452            throw new IllegalArgumentException(String.format("Method %s not supported", method));
453        }
454
455        try {
456            mConnector.doCommand(String.format(
457                    "volume %sshare %s %s", (enable ? "" : "un"), path, method));
458        } catch (NativeDaemonConnectorException e) {
459            Slog.e(TAG, "Failed to share/unshare", e);
460        }
461    }
462
463    private void updatePublicVolumeState(String path, String state) {
464        if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
465            Slog.w(TAG, "Multiple volumes not currently supported");
466            return;
467        }
468
469        if (mLegacyState.equals(state)) {
470            Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
471            return;
472        }
473        // Update state on PackageManager, but only of real events
474        if (!mEmulateExternalStorage) {
475            if (Environment.MEDIA_UNMOUNTED.equals(state)) {
476                mPms.updateExternalMediaStatus(false, false);
477            } else if (Environment.MEDIA_MOUNTED.equals(state)) {
478                mPms.updateExternalMediaStatus(true, false);
479            }
480        }
481        String oldState = mLegacyState;
482        mLegacyState = state;
483
484        synchronized (mListeners) {
485            for (int i = mListeners.size() -1; i >= 0; i--) {
486                MountServiceBinderListener bl = mListeners.get(i);
487                try {
488                    bl.mListener.onStorageStateChanged(path, oldState, state);
489                } catch (RemoteException rex) {
490                    Slog.e(TAG, "Listener dead");
491                    mListeners.remove(i);
492                } catch (Exception ex) {
493                    Slog.e(TAG, "Listener failed", ex);
494                }
495            }
496        }
497    }
498
499    /**
500     *
501     * Callback from NativeDaemonConnector
502     */
503    public void onDaemonConnected() {
504        /*
505         * Since we'll be calling back into the NativeDaemonConnector,
506         * we need to do our work in a new thread.
507         */
508        new Thread() {
509            public void run() {
510                /**
511                 * Determine media state and UMS detection status
512                 */
513                String path = Environment.getExternalStorageDirectory().getPath();
514                String state = Environment.MEDIA_REMOVED;
515
516                try {
517                    String[] vols = mConnector.doListCommand(
518                        "volume list", VoldResponseCode.VolumeListResult);
519                    for (String volstr : vols) {
520                        String[] tok = volstr.split(" ");
521                        // FMT: <label> <mountpoint> <state>
522                        if (!tok[1].equals(path)) {
523                            Slog.w(TAG, String.format(
524                                    "Skipping unknown volume '%s'",tok[1]));
525                            continue;
526                        }
527                        int st = Integer.parseInt(tok[2]);
528                        if (st == VolumeState.NoMedia) {
529                            state = Environment.MEDIA_REMOVED;
530                        } else if (st == VolumeState.Idle) {
531                            state = Environment.MEDIA_UNMOUNTED;
532                        } else if (st == VolumeState.Mounted) {
533                            state = Environment.MEDIA_MOUNTED;
534                            Slog.i(TAG, "Media already mounted on daemon connection");
535                        } else if (st == VolumeState.Shared) {
536                            state = Environment.MEDIA_SHARED;
537                            Slog.i(TAG, "Media shared on daemon connection");
538                        } else {
539                            throw new Exception(String.format("Unexpected state %d", st));
540                        }
541                    }
542                    if (state != null) {
543                        if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
544                        updatePublicVolumeState(path, state);
545                    }
546                } catch (Exception e) {
547                    Slog.e(TAG, "Error processing initial volume state", e);
548                    updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
549                }
550
551                try {
552                    boolean avail = doGetShareMethodAvailable("ums");
553                    notifyShareAvailabilityChange("ums", avail);
554                } catch (Exception ex) {
555                    Slog.w(TAG, "Failed to get share availability");
556                }
557                /*
558                 * Now that we've done our initialization, release
559                 * the hounds!
560                 */
561                mReady = true;
562            }
563        }.start();
564    }
565
566    /**
567     * Callback from NativeDaemonConnector
568     */
569    public boolean onEvent(int code, String raw, String[] cooked) {
570        Intent in = null;
571
572        if (DEBUG_EVENTS) {
573            StringBuilder builder = new StringBuilder();
574            builder.append("onEvent::");
575            builder.append(" raw= " + raw);
576            if (cooked != null) {
577                builder.append(" cooked = " );
578                for (String str : cooked) {
579                    builder.append(" " + str);
580                }
581            }
582            Slog.i(TAG, builder.toString());
583        }
584        if (code == VoldResponseCode.VolumeStateChange) {
585            /*
586             * One of the volumes we're managing has changed state.
587             * Format: "NNN Volume <label> <path> state changed
588             * from <old_#> (<old_str>) to <new_#> (<new_str>)"
589             */
590            notifyVolumeStateChange(
591                    cooked[2], cooked[3], Integer.parseInt(cooked[7]),
592                            Integer.parseInt(cooked[10]));
593        } else if (code == VoldResponseCode.ShareAvailabilityChange) {
594            // FMT: NNN Share method <method> now <available|unavailable>
595            boolean avail = false;
596            if (cooked[5].equals("available")) {
597                avail = true;
598            }
599            notifyShareAvailabilityChange(cooked[3], avail);
600        } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
601                   (code == VoldResponseCode.VolumeDiskRemoved) ||
602                   (code == VoldResponseCode.VolumeBadRemoval)) {
603            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
604            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
605            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
606            final String label = cooked[2];
607            final String path = cooked[3];
608            int major = -1;
609            int minor = -1;
610
611            try {
612                String devComp = cooked[6].substring(1, cooked[6].length() -1);
613                String[] devTok = devComp.split(":");
614                major = Integer.parseInt(devTok[0]);
615                minor = Integer.parseInt(devTok[1]);
616            } catch (Exception ex) {
617                Slog.e(TAG, "Failed to parse major/minor", ex);
618            }
619
620            if (code == VoldResponseCode.VolumeDiskInserted) {
621                new Thread() {
622                    public void run() {
623                        try {
624                            int rc;
625                            if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
626                                Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
627                            }
628                        } catch (Exception ex) {
629                            Slog.w(TAG, "Failed to mount media on insertion", ex);
630                        }
631                    }
632                }.start();
633            } else if (code == VoldResponseCode.VolumeDiskRemoved) {
634                /*
635                 * This event gets trumped if we're already in BAD_REMOVAL state
636                 */
637                if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
638                    return true;
639                }
640                /* Send the media unmounted event first */
641                if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
642                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
643                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
644                mContext.sendBroadcast(in);
645
646                if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
647                updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
648                in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
649            } else if (code == VoldResponseCode.VolumeBadRemoval) {
650                if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
651                /* Send the media unmounted event first */
652                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
653                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
654                mContext.sendBroadcast(in);
655
656                if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
657                updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
658                in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
659            } else {
660                Slog.e(TAG, String.format("Unknown code {%d}", code));
661            }
662        } else {
663            return false;
664        }
665
666        if (in != null) {
667            mContext.sendBroadcast(in);
668        }
669        return true;
670    }
671
672    private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
673        String vs = getVolumeState(path);
674        if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs);
675
676        Intent in = null;
677
678        if (oldState == VolumeState.Shared && newState != oldState) {
679            if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
680            mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_UNSHARED,
681                                                Uri.parse("file://" + path)));
682        }
683
684        if (newState == VolumeState.Init) {
685        } else if (newState == VolumeState.NoMedia) {
686            // NoMedia is handled via Disk Remove events
687        } else if (newState == VolumeState.Idle) {
688            /*
689             * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
690             * if we're in the process of enabling UMS
691             */
692            if (!vs.equals(
693                    Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
694                            Environment.MEDIA_NOFS) && !vs.equals(
695                                    Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
696                if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
697                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
698                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
699            }
700        } else if (newState == VolumeState.Pending) {
701        } else if (newState == VolumeState.Checking) {
702            if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
703            updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
704            in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
705        } else if (newState == VolumeState.Mounted) {
706            if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
707            updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
708            in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
709            in.putExtra("read-only", false);
710        } else if (newState == VolumeState.Unmounting) {
711            in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
712        } else if (newState == VolumeState.Formatting) {
713        } else if (newState == VolumeState.Shared) {
714            if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
715            /* Send the media unmounted event first */
716            updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
717            in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
718            mContext.sendBroadcast(in);
719
720            if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
721            updatePublicVolumeState(path, Environment.MEDIA_SHARED);
722            in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
723            if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
724        } else if (newState == VolumeState.SharedMnt) {
725            Slog.e(TAG, "Live shared mounts not supported yet!");
726            return;
727        } else {
728            Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
729        }
730
731        if (in != null) {
732            mContext.sendBroadcast(in);
733        }
734    }
735
736    private boolean doGetShareMethodAvailable(String method) {
737        ArrayList<String> rsp;
738        try {
739            rsp = mConnector.doCommand("share status " + method);
740        } catch (NativeDaemonConnectorException ex) {
741            Slog.e(TAG, "Failed to determine whether share method " + method + " is available.");
742            return false;
743        }
744
745        for (String line : rsp) {
746            String[] tok = line.split(" ");
747            if (tok.length < 3) {
748                Slog.e(TAG, "Malformed response to share status " + method);
749                return false;
750            }
751
752            int code;
753            try {
754                code = Integer.parseInt(tok[0]);
755            } catch (NumberFormatException nfe) {
756                Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
757                return false;
758            }
759            if (code == VoldResponseCode.ShareStatusResult) {
760                if (tok[2].equals("available"))
761                    return true;
762                return false;
763            } else {
764                Slog.e(TAG, String.format("Unexpected response code %d", code));
765                return false;
766            }
767        }
768        Slog.e(TAG, "Got an empty response");
769        return false;
770    }
771
772    private int doMountVolume(String path) {
773        int rc = StorageResultCode.OperationSucceeded;
774
775        if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
776        try {
777            mConnector.doCommand(String.format("volume mount %s", path));
778        } catch (NativeDaemonConnectorException e) {
779            /*
780             * Mount failed for some reason
781             */
782            Intent in = null;
783            int code = e.getCode();
784            if (code == VoldResponseCode.OpFailedNoMedia) {
785                /*
786                 * Attempt to mount but no media inserted
787                 */
788                rc = StorageResultCode.OperationFailedNoMedia;
789            } else if (code == VoldResponseCode.OpFailedMediaBlank) {
790                if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs");
791                /*
792                 * Media is blank or does not contain a supported filesystem
793                 */
794                updatePublicVolumeState(path, Environment.MEDIA_NOFS);
795                in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
796                rc = StorageResultCode.OperationFailedMediaBlank;
797            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
798                if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
799                /*
800                 * Volume consistency check failed
801                 */
802                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
803                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
804                rc = StorageResultCode.OperationFailedMediaCorrupt;
805            } else {
806                rc = StorageResultCode.OperationFailedInternalError;
807            }
808
809            /*
810             * Send broadcast intent (if required for the failure)
811             */
812            if (in != null) {
813                mContext.sendBroadcast(in);
814            }
815        }
816
817        return rc;
818    }
819
820    /*
821     * If force is not set, we do not unmount if there are
822     * processes holding references to the volume about to be unmounted.
823     * If force is set, all the processes holding references need to be
824     * killed via the ActivityManager before actually unmounting the volume.
825     * This might even take a while and might be retried after timed delays
826     * to make sure we dont end up in an instable state and kill some core
827     * processes.
828     */
829    private int doUnmountVolume(String path, boolean force) {
830        if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
831            return VoldResponseCode.OpFailedVolNotMounted;
832        }
833        // Redundant probably. But no harm in updating state again.
834        mPms.updateExternalMediaStatus(false, false);
835        try {
836            mConnector.doCommand(String.format(
837                    "volume unmount %s%s", path, (force ? " force" : "")));
838            // We unmounted the volume. None of the asec containers are available now.
839            synchronized (mAsecMountSet) {
840                mAsecMountSet.clear();
841            }
842            return StorageResultCode.OperationSucceeded;
843        } catch (NativeDaemonConnectorException e) {
844            // Don't worry about mismatch in PackageManager since the
845            // call back will handle the status changes any way.
846            int code = e.getCode();
847            if (code == VoldResponseCode.OpFailedVolNotMounted) {
848                return StorageResultCode.OperationFailedStorageNotMounted;
849            } else if (code == VoldResponseCode.OpFailedStorageBusy) {
850                return StorageResultCode.OperationFailedStorageBusy;
851            } else {
852                return StorageResultCode.OperationFailedInternalError;
853            }
854        }
855    }
856
857    private int doFormatVolume(String path) {
858        try {
859            String cmd = String.format("volume format %s", path);
860            mConnector.doCommand(cmd);
861            return StorageResultCode.OperationSucceeded;
862        } catch (NativeDaemonConnectorException e) {
863            int code = e.getCode();
864            if (code == VoldResponseCode.OpFailedNoMedia) {
865                return StorageResultCode.OperationFailedNoMedia;
866            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
867                return StorageResultCode.OperationFailedMediaCorrupt;
868            } else {
869                return StorageResultCode.OperationFailedInternalError;
870            }
871        }
872    }
873
874    private boolean doGetVolumeShared(String path, String method) {
875        String cmd = String.format("volume shared %s %s", path, method);
876        ArrayList<String> rsp;
877
878        try {
879            rsp = mConnector.doCommand(cmd);
880        } catch (NativeDaemonConnectorException ex) {
881            Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method);
882            return false;
883        }
884
885        for (String line : rsp) {
886            String[] tok = line.split(" ");
887            if (tok.length < 3) {
888                Slog.e(TAG, "Malformed response to volume shared " + path + " " + method + " command");
889                return false;
890            }
891
892            int code;
893            try {
894                code = Integer.parseInt(tok[0]);
895            } catch (NumberFormatException nfe) {
896                Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
897                return false;
898            }
899            if (code == VoldResponseCode.ShareEnabledResult) {
900                return "enabled".equals(tok[2]);
901            } else {
902                Slog.e(TAG, String.format("Unexpected response code %d", code));
903                return false;
904            }
905        }
906        Slog.e(TAG, "Got an empty response");
907        return false;
908    }
909
910    private void notifyShareAvailabilityChange(String method, final boolean avail) {
911        if (!method.equals("ums")) {
912           Slog.w(TAG, "Ignoring unsupported share method {" + method + "}");
913           return;
914        }
915
916        synchronized (mListeners) {
917            for (int i = mListeners.size() -1; i >= 0; i--) {
918                MountServiceBinderListener bl = mListeners.get(i);
919                try {
920                    bl.mListener.onUsbMassStorageConnectionChanged(avail);
921                } catch (RemoteException rex) {
922                    Slog.e(TAG, "Listener dead");
923                    mListeners.remove(i);
924                } catch (Exception ex) {
925                    Slog.e(TAG, "Listener failed", ex);
926                }
927            }
928        }
929
930        if (mBooted == true) {
931            sendUmsIntent(avail);
932        } else {
933            mSendUmsConnectedOnBoot = avail;
934        }
935
936        final String path = Environment.getExternalStorageDirectory().getPath();
937        if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) {
938            /*
939             * USB mass storage disconnected while enabled
940             */
941            new Thread() {
942                public void run() {
943                    try {
944                        int rc;
945                        Slog.w(TAG, "Disabling UMS after cable disconnect");
946                        doShareUnshareVolume(path, "ums", false);
947                        if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
948                            Slog.e(TAG, String.format(
949                                    "Failed to remount {%s} on UMS enabled-disconnect (%d)",
950                                            path, rc));
951                        }
952                    } catch (Exception ex) {
953                        Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
954                    }
955                }
956            }.start();
957        }
958    }
959
960    private void sendUmsIntent(boolean c) {
961        mContext.sendBroadcast(
962                new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)));
963    }
964
965    private void validatePermission(String perm) {
966        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
967            throw new SecurityException(String.format("Requires %s permission", perm));
968        }
969    }
970
971    /**
972     * Constructs a new MountService instance
973     *
974     * @param context  Binder context for this service
975     */
976    public MountService(Context context) {
977        mContext = context;
978
979        mEmulateExternalStorage = context.getResources().getBoolean(
980                com.android.internal.R.bool.config_emulateExternalStorage);
981        if (mEmulateExternalStorage) {
982            Slog.d(TAG, "using emulated external storage");
983            mLegacyState = Environment.MEDIA_MOUNTED;
984        }
985
986        // XXX: This will go away soon in favor of IMountServiceObserver
987        mPms = (PackageManagerService) ServiceManager.getService("package");
988
989        mContext.registerReceiver(mBroadcastReceiver,
990                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
991
992        mHandlerThread = new HandlerThread("MountService");
993        mHandlerThread.start();
994        mHandler = new MountServiceHandler(mHandlerThread.getLooper());
995
996        // Add OBB Action Handler to MountService thread.
997        mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
998
999        /*
1000         * Vold does not run in the simulator, so pretend the connector thread
1001         * ran and did its thing.
1002         */
1003        if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
1004            mReady = true;
1005            mUmsEnabling = true;
1006            return;
1007        }
1008
1009        mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
1010        mReady = false;
1011        Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
1012        thread.start();
1013    }
1014
1015    /**
1016     * Exposed API calls below here
1017     */
1018
1019    public void registerListener(IMountServiceListener listener) {
1020        synchronized (mListeners) {
1021            MountServiceBinderListener bl = new MountServiceBinderListener(listener);
1022            try {
1023                listener.asBinder().linkToDeath(bl, 0);
1024                mListeners.add(bl);
1025            } catch (RemoteException rex) {
1026                Slog.e(TAG, "Failed to link to listener death");
1027            }
1028        }
1029    }
1030
1031    public void unregisterListener(IMountServiceListener listener) {
1032        synchronized (mListeners) {
1033            for(MountServiceBinderListener bl : mListeners) {
1034                if (bl.mListener == listener) {
1035                    mListeners.remove(mListeners.indexOf(bl));
1036                    return;
1037                }
1038            }
1039        }
1040    }
1041
1042    public void shutdown(final IMountShutdownObserver observer) {
1043        validatePermission(android.Manifest.permission.SHUTDOWN);
1044
1045        Slog.i(TAG, "Shutting down");
1046
1047        String path = Environment.getExternalStorageDirectory().getPath();
1048        String state = getVolumeState(path);
1049
1050        if (state.equals(Environment.MEDIA_SHARED)) {
1051            /*
1052             * If the media is currently shared, unshare it.
1053             * XXX: This is still dangerous!. We should not
1054             * be rebooting at *all* if UMS is enabled, since
1055             * the UMS host could have dirty FAT cache entries
1056             * yet to flush.
1057             */
1058            setUsbMassStorageEnabled(false);
1059        } else if (state.equals(Environment.MEDIA_CHECKING)) {
1060            /*
1061             * If the media is being checked, then we need to wait for
1062             * it to complete before being able to proceed.
1063             */
1064            // XXX: @hackbod - Should we disable the ANR timer here?
1065            int retries = 30;
1066            while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
1067                try {
1068                    Thread.sleep(1000);
1069                } catch (InterruptedException iex) {
1070                    Slog.e(TAG, "Interrupted while waiting for media", iex);
1071                    break;
1072                }
1073                state = Environment.getExternalStorageState();
1074            }
1075            if (retries == 0) {
1076                Slog.e(TAG, "Timed out waiting for media to check");
1077            }
1078        }
1079
1080        if (state.equals(Environment.MEDIA_MOUNTED)) {
1081            // Post a unmount message.
1082            ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
1083            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
1084        }
1085    }
1086
1087    private boolean getUmsEnabling() {
1088        synchronized (mListeners) {
1089            return mUmsEnabling;
1090        }
1091    }
1092
1093    private void setUmsEnabling(boolean enable) {
1094        synchronized (mListeners) {
1095            mUmsEnabling = enable;
1096        }
1097    }
1098
1099    public boolean isUsbMassStorageConnected() {
1100        waitForReady();
1101
1102        if (getUmsEnabling()) {
1103            return true;
1104        }
1105        return doGetShareMethodAvailable("ums");
1106    }
1107
1108    public void setUsbMassStorageEnabled(boolean enable) {
1109        waitForReady();
1110        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1111
1112        // TODO: Add support for multiple share methods
1113
1114        /*
1115         * If the volume is mounted and we're enabling then unmount it
1116         */
1117        String path = Environment.getExternalStorageDirectory().getPath();
1118        String vs = getVolumeState(path);
1119        String method = "ums";
1120        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
1121            // Override for isUsbMassStorageEnabled()
1122            setUmsEnabling(enable);
1123            UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
1124            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
1125            // Clear override
1126            setUmsEnabling(false);
1127        }
1128        /*
1129         * If we disabled UMS then mount the volume
1130         */
1131        if (!enable) {
1132            doShareUnshareVolume(path, method, enable);
1133            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
1134                Slog.e(TAG, "Failed to remount " + path +
1135                        " after disabling share method " + method);
1136                /*
1137                 * Even though the mount failed, the unshare didn't so don't indicate an error.
1138                 * The mountVolume() call will have set the storage state and sent the necessary
1139                 * broadcasts.
1140                 */
1141            }
1142        }
1143    }
1144
1145    public boolean isUsbMassStorageEnabled() {
1146        waitForReady();
1147        return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
1148    }
1149
1150    /**
1151     * @return state of the volume at the specified mount point
1152     */
1153    public String getVolumeState(String mountPoint) {
1154        /*
1155         * XXX: Until we have multiple volume discovery, just hardwire
1156         * this to /sdcard
1157         */
1158        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
1159            Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
1160            throw new IllegalArgumentException();
1161        }
1162
1163        return mLegacyState;
1164    }
1165
1166    public int mountVolume(String path) {
1167        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1168
1169        waitForReady();
1170        return doMountVolume(path);
1171    }
1172
1173    public void unmountVolume(String path, boolean force) {
1174        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1175        waitForReady();
1176
1177        String volState = getVolumeState(path);
1178        if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path + " force = " + force);
1179        if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
1180                Environment.MEDIA_REMOVED.equals(volState) ||
1181                Environment.MEDIA_SHARED.equals(volState) ||
1182                Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
1183            // Media already unmounted or cannot be unmounted.
1184            // TODO return valid return code when adding observer call back.
1185            return;
1186        }
1187        UnmountCallBack ucb = new UnmountCallBack(path, force);
1188        mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
1189    }
1190
1191    public int formatVolume(String path) {
1192        validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
1193        waitForReady();
1194
1195        return doFormatVolume(path);
1196    }
1197
1198    public int []getStorageUsers(String path) {
1199        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1200        waitForReady();
1201        try {
1202            String[] r = mConnector.doListCommand(
1203                    String.format("storage users %s", path),
1204                            VoldResponseCode.StorageUsersListResult);
1205            // FMT: <pid> <process name>
1206            int[] data = new int[r.length];
1207            for (int i = 0; i < r.length; i++) {
1208                String []tok = r[i].split(" ");
1209                try {
1210                    data[i] = Integer.parseInt(tok[0]);
1211                } catch (NumberFormatException nfe) {
1212                    Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
1213                    return new int[0];
1214                }
1215            }
1216            return data;
1217        } catch (NativeDaemonConnectorException e) {
1218            Slog.e(TAG, "Failed to retrieve storage users list", e);
1219            return new int[0];
1220        }
1221    }
1222
1223    private void warnOnNotMounted() {
1224        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1225            Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
1226        }
1227    }
1228
1229    public String[] getSecureContainerList() {
1230        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1231        waitForReady();
1232        warnOnNotMounted();
1233
1234        try {
1235            return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
1236        } catch (NativeDaemonConnectorException e) {
1237            return new String[0];
1238        }
1239    }
1240
1241    public int createSecureContainer(String id, int sizeMb, String fstype,
1242                                    String key, int ownerUid) {
1243        validatePermission(android.Manifest.permission.ASEC_CREATE);
1244        waitForReady();
1245        warnOnNotMounted();
1246
1247        int rc = StorageResultCode.OperationSucceeded;
1248        String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
1249        try {
1250            mConnector.doCommand(cmd);
1251        } catch (NativeDaemonConnectorException e) {
1252            rc = StorageResultCode.OperationFailedInternalError;
1253        }
1254
1255        if (rc == StorageResultCode.OperationSucceeded) {
1256            synchronized (mAsecMountSet) {
1257                mAsecMountSet.add(id);
1258            }
1259        }
1260        return rc;
1261    }
1262
1263    public int finalizeSecureContainer(String id) {
1264        validatePermission(android.Manifest.permission.ASEC_CREATE);
1265        warnOnNotMounted();
1266
1267        int rc = StorageResultCode.OperationSucceeded;
1268        try {
1269            mConnector.doCommand(String.format("asec finalize %s", id));
1270            /*
1271             * Finalization does a remount, so no need
1272             * to update mAsecMountSet
1273             */
1274        } catch (NativeDaemonConnectorException e) {
1275            rc = StorageResultCode.OperationFailedInternalError;
1276        }
1277        return rc;
1278    }
1279
1280    public int destroySecureContainer(String id, boolean force) {
1281        validatePermission(android.Manifest.permission.ASEC_DESTROY);
1282        waitForReady();
1283        warnOnNotMounted();
1284
1285        int rc = StorageResultCode.OperationSucceeded;
1286        try {
1287            mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : "")));
1288        } catch (NativeDaemonConnectorException e) {
1289            int code = e.getCode();
1290            if (code == VoldResponseCode.OpFailedStorageBusy) {
1291                rc = StorageResultCode.OperationFailedStorageBusy;
1292            } else {
1293                rc = StorageResultCode.OperationFailedInternalError;
1294            }
1295        }
1296
1297        if (rc == StorageResultCode.OperationSucceeded) {
1298            synchronized (mAsecMountSet) {
1299                if (mAsecMountSet.contains(id)) {
1300                    mAsecMountSet.remove(id);
1301                }
1302            }
1303        }
1304
1305        return rc;
1306    }
1307
1308    public int mountSecureContainer(String id, String key, int ownerUid) {
1309        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1310        waitForReady();
1311        warnOnNotMounted();
1312
1313        synchronized (mAsecMountSet) {
1314            if (mAsecMountSet.contains(id)) {
1315                return StorageResultCode.OperationFailedStorageMounted;
1316            }
1317        }
1318
1319        int rc = StorageResultCode.OperationSucceeded;
1320        String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
1321        try {
1322            mConnector.doCommand(cmd);
1323        } catch (NativeDaemonConnectorException e) {
1324            int code = e.getCode();
1325            if (code != VoldResponseCode.OpFailedStorageBusy) {
1326                rc = StorageResultCode.OperationFailedInternalError;
1327            }
1328        }
1329
1330        if (rc == StorageResultCode.OperationSucceeded) {
1331            synchronized (mAsecMountSet) {
1332                mAsecMountSet.add(id);
1333            }
1334        }
1335        return rc;
1336    }
1337
1338    public int unmountSecureContainer(String id, boolean force) {
1339        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1340        waitForReady();
1341        warnOnNotMounted();
1342
1343        synchronized (mAsecMountSet) {
1344            if (!mAsecMountSet.contains(id)) {
1345                return StorageResultCode.OperationFailedStorageNotMounted;
1346            }
1347         }
1348
1349        int rc = StorageResultCode.OperationSucceeded;
1350        String cmd = String.format("asec unmount %s%s", id, (force ? " force" : ""));
1351        try {
1352            mConnector.doCommand(cmd);
1353        } catch (NativeDaemonConnectorException e) {
1354            int code = e.getCode();
1355            if (code == VoldResponseCode.OpFailedStorageBusy) {
1356                rc = StorageResultCode.OperationFailedStorageBusy;
1357            } else {
1358                rc = StorageResultCode.OperationFailedInternalError;
1359            }
1360        }
1361
1362        if (rc == StorageResultCode.OperationSucceeded) {
1363            synchronized (mAsecMountSet) {
1364                mAsecMountSet.remove(id);
1365            }
1366        }
1367        return rc;
1368    }
1369
1370    public boolean isSecureContainerMounted(String id) {
1371        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1372        waitForReady();
1373        warnOnNotMounted();
1374
1375        synchronized (mAsecMountSet) {
1376            return mAsecMountSet.contains(id);
1377        }
1378    }
1379
1380    public int renameSecureContainer(String oldId, String newId) {
1381        validatePermission(android.Manifest.permission.ASEC_RENAME);
1382        waitForReady();
1383        warnOnNotMounted();
1384
1385        synchronized (mAsecMountSet) {
1386            /*
1387             * Because a mounted container has active internal state which cannot be
1388             * changed while active, we must ensure both ids are not currently mounted.
1389             */
1390            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
1391                return StorageResultCode.OperationFailedStorageMounted;
1392            }
1393        }
1394
1395        int rc = StorageResultCode.OperationSucceeded;
1396        String cmd = String.format("asec rename %s %s", oldId, newId);
1397        try {
1398            mConnector.doCommand(cmd);
1399        } catch (NativeDaemonConnectorException e) {
1400            rc = StorageResultCode.OperationFailedInternalError;
1401        }
1402
1403        return rc;
1404    }
1405
1406    public String getSecureContainerPath(String id) {
1407        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1408        waitForReady();
1409        warnOnNotMounted();
1410
1411        try {
1412            ArrayList<String> rsp = mConnector.doCommand(String.format("asec path %s", id));
1413            String []tok = rsp.get(0).split(" ");
1414            int code = Integer.parseInt(tok[0]);
1415            if (code != VoldResponseCode.AsecPathResult) {
1416                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1417            }
1418            return tok[1];
1419        } catch (NativeDaemonConnectorException e) {
1420            int code = e.getCode();
1421            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1422                throw new IllegalArgumentException(String.format("Container '%s' not found", id));
1423            } else {
1424                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1425            }
1426        }
1427    }
1428
1429    public void finishMediaUpdate() {
1430        mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
1431    }
1432
1433    private boolean isCallerOwnerOfPackageOrSystem(String packageName) {
1434        final int callerUid = Binder.getCallingUid();
1435        return isUidOwnerOfPackageOrSystem(packageName, callerUid);
1436    }
1437
1438    private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
1439        if (callerUid == android.os.Process.SYSTEM_UID) {
1440            return true;
1441        }
1442
1443        if (packageName == null) {
1444            return false;
1445        }
1446
1447        final int packageUid = mPms.getPackageUid(packageName);
1448
1449        if (DEBUG_OBB) {
1450            Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
1451                    packageUid + ", callerUid = " + callerUid);
1452        }
1453
1454        return callerUid == packageUid;
1455    }
1456
1457    public String getMountedObbPath(String filename) {
1458        waitForReady();
1459        warnOnNotMounted();
1460
1461        try {
1462            ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename));
1463            String []tok = rsp.get(0).split(" ");
1464            int code = Integer.parseInt(tok[0]);
1465            if (code != VoldResponseCode.AsecPathResult) {
1466                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1467            }
1468            return tok[1];
1469        } catch (NativeDaemonConnectorException e) {
1470            int code = e.getCode();
1471            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1472                return null;
1473            } else {
1474                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1475            }
1476        }
1477    }
1478
1479    public boolean isObbMounted(String filename) {
1480        synchronized (mObbMounts) {
1481            return mObbPathToStateMap.containsKey(filename);
1482        }
1483    }
1484
1485    public void mountObb(String filename, String key, IObbActionListener token) {
1486        waitForReady();
1487        warnOnNotMounted();
1488
1489        final ObbState obbState;
1490
1491        synchronized (mObbMounts) {
1492            if (isObbMounted(filename)) {
1493                throw new IllegalArgumentException("OBB file is already mounted");
1494            }
1495
1496            if (mObbMounts.containsKey(token)) {
1497                throw new IllegalArgumentException("You may only have one OBB mounted at a time");
1498            }
1499
1500            final int callerUid = Binder.getCallingUid();
1501            obbState = new ObbState(filename, token, callerUid);
1502            addObbState(obbState);
1503        }
1504
1505        try {
1506            token.asBinder().linkToDeath(obbState, 0);
1507        } catch (RemoteException rex) {
1508            Slog.e(TAG, "Failed to link to listener death");
1509        }
1510
1511        MountObbAction action = new MountObbAction(obbState, key);
1512        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1513
1514        if (DEBUG_OBB)
1515            Slog.i(TAG, "Send to OBB handler: " + action.toString());
1516    }
1517
1518    public void unmountObb(String filename, boolean force, IObbActionListener token) {
1519        final ObbState obbState;
1520
1521        synchronized (mObbMounts) {
1522            if (!isObbMounted(filename)) {
1523                throw new IllegalArgumentException("OBB is not mounted");
1524            }
1525            obbState = mObbPathToStateMap.get(filename);
1526        }
1527
1528        UnmountObbAction action = new UnmountObbAction(obbState, force);
1529        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1530
1531        if (DEBUG_OBB)
1532            Slog.i(TAG, "Send to OBB handler: " + action.toString());
1533    }
1534
1535    private void addObbState(ObbState obbState) {
1536        synchronized (mObbMounts) {
1537            mObbMounts.put(obbState.token, obbState);
1538            mObbPathToStateMap.put(obbState.filename, obbState);
1539        }
1540    }
1541
1542    private void removeObbState(ObbState obbState) {
1543        synchronized (mObbMounts) {
1544            mObbMounts.remove(obbState.token);
1545            mObbPathToStateMap.remove(obbState.filename);
1546        }
1547    }
1548
1549    private class ObbActionHandler extends Handler {
1550        private boolean mBound = false;
1551        private List<ObbAction> mActions = new LinkedList<ObbAction>();
1552
1553        ObbActionHandler(Looper l) {
1554            super(l);
1555        }
1556
1557        @Override
1558        public void handleMessage(Message msg) {
1559            switch (msg.what) {
1560                case OBB_RUN_ACTION: {
1561                    ObbAction action = (ObbAction) msg.obj;
1562
1563                    if (DEBUG_OBB)
1564                        Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
1565
1566                    // If a bind was already initiated we don't really
1567                    // need to do anything. The pending install
1568                    // will be processed later on.
1569                    if (!mBound) {
1570                        // If this is the only one pending we might
1571                        // have to bind to the service again.
1572                        if (!connectToService()) {
1573                            Slog.e(TAG, "Failed to bind to media container service");
1574                            action.handleError();
1575                            return;
1576                        } else {
1577                            // Once we bind to the service, the first
1578                            // pending request will be processed.
1579                            mActions.add(action);
1580                        }
1581                    } else {
1582                        // Already bound to the service. Just make
1583                        // sure we trigger off processing the first request.
1584                        if (mActions.size() == 0) {
1585                            mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
1586                        }
1587
1588                        mActions.add(action);
1589                    }
1590                    break;
1591                }
1592                case OBB_MCS_BOUND: {
1593                    if (DEBUG_OBB)
1594                        Slog.i(TAG, "OBB_MCS_BOUND");
1595                    if (msg.obj != null) {
1596                        mContainerService = (IMediaContainerService) msg.obj;
1597                    }
1598                    if (mContainerService == null) {
1599                        // Something seriously wrong. Bail out
1600                        Slog.e(TAG, "Cannot bind to media container service");
1601                        for (ObbAction action : mActions) {
1602                            // Indicate service bind error
1603                            action.handleError();
1604                        }
1605                        mActions.clear();
1606                    } else if (mActions.size() > 0) {
1607                        ObbAction action = mActions.get(0);
1608                        if (action != null) {
1609                            action.execute(this);
1610                        }
1611                    } else {
1612                        // Should never happen ideally.
1613                        Slog.w(TAG, "Empty queue");
1614                    }
1615                    break;
1616                }
1617                case OBB_MCS_RECONNECT: {
1618                    if (DEBUG_OBB)
1619                        Slog.i(TAG, "OBB_MCS_RECONNECT");
1620                    if (mActions.size() > 0) {
1621                        if (mBound) {
1622                            disconnectService();
1623                        }
1624                        if (!connectToService()) {
1625                            Slog.e(TAG, "Failed to bind to media container service");
1626                            for (ObbAction action : mActions) {
1627                                // Indicate service bind error
1628                                action.handleError();
1629                            }
1630                            mActions.clear();
1631                        }
1632                    }
1633                    break;
1634                }
1635                case OBB_MCS_UNBIND: {
1636                    if (DEBUG_OBB)
1637                        Slog.i(TAG, "OBB_MCS_UNBIND");
1638
1639                    // Delete pending install
1640                    if (mActions.size() > 0) {
1641                        mActions.remove(0);
1642                    }
1643                    if (mActions.size() == 0) {
1644                        if (mBound) {
1645                            disconnectService();
1646                        }
1647                    } else {
1648                        // There are more pending requests in queue.
1649                        // Just post MCS_BOUND message to trigger processing
1650                        // of next pending install.
1651                        mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
1652                    }
1653                    break;
1654                }
1655                case OBB_MCS_GIVE_UP: {
1656                    if (DEBUG_OBB)
1657                        Slog.i(TAG, "OBB_MCS_GIVE_UP");
1658                    mActions.remove(0);
1659                    break;
1660                }
1661            }
1662        }
1663
1664        private boolean connectToService() {
1665            if (DEBUG_OBB)
1666                Slog.i(TAG, "Trying to bind to DefaultContainerService");
1667
1668            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
1669            if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
1670                mBound = true;
1671                return true;
1672            }
1673            return false;
1674        }
1675
1676        private void disconnectService() {
1677            mContainerService = null;
1678            mBound = false;
1679            mContext.unbindService(mDefContainerConn);
1680        }
1681    }
1682
1683    abstract class ObbAction {
1684        private static final int MAX_RETRIES = 3;
1685        private int mRetries;
1686
1687        ObbState mObbState;
1688
1689        ObbAction(ObbState obbState) {
1690            mObbState = obbState;
1691        }
1692
1693        public void execute(ObbActionHandler handler) {
1694            try {
1695                if (DEBUG_OBB)
1696                    Slog.i(TAG, "Starting to execute action: " + this.toString());
1697                mRetries++;
1698                if (mRetries > MAX_RETRIES) {
1699                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
1700                    mObbActionHandler.sendEmptyMessage(OBB_MCS_GIVE_UP);
1701                    handleError();
1702                    return;
1703                } else {
1704                    handleExecute();
1705                    if (DEBUG_OBB)
1706                        Slog.i(TAG, "Posting install MCS_UNBIND");
1707                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
1708                }
1709            } catch (RemoteException e) {
1710                if (DEBUG_OBB)
1711                    Slog.i(TAG, "Posting install MCS_RECONNECT");
1712                mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT);
1713            } catch (Exception e) {
1714                if (DEBUG_OBB)
1715                    Slog.d(TAG, "Error handling OBB action", e);
1716                handleError();
1717            }
1718        }
1719
1720        abstract void handleExecute() throws RemoteException;
1721        abstract void handleError();
1722    }
1723
1724    class MountObbAction extends ObbAction {
1725        private String mKey;
1726
1727        MountObbAction(ObbState obbState, String key) {
1728            super(obbState);
1729            mKey = key;
1730        }
1731
1732        public void handleExecute() throws RemoteException {
1733            ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
1734            if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
1735                throw new IllegalArgumentException("Caller package does not match OBB file");
1736            }
1737
1738            if (mKey == null) {
1739                mKey = "none";
1740            }
1741
1742            int rc = StorageResultCode.OperationSucceeded;
1743            String cmd = String.format("obb mount %s %s %d", mObbState.filename, mKey,
1744                    mObbState.callerUid);
1745            try {
1746                mConnector.doCommand(cmd);
1747            } catch (NativeDaemonConnectorException e) {
1748                int code = e.getCode();
1749                if (code != VoldResponseCode.OpFailedStorageBusy) {
1750                    rc = StorageResultCode.OperationFailedInternalError;
1751                }
1752            }
1753
1754            if (rc == StorageResultCode.OperationSucceeded) {
1755                try {
1756                    mObbState.token.onObbResult(mObbState.filename, "mounted");
1757                } catch (RemoteException e) {
1758                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
1759                }
1760            } else {
1761                Slog.e(TAG, "Couldn't mount OBB file");
1762
1763                // We didn't succeed, so remove this from the mount-set.
1764                removeObbState(mObbState);
1765            }
1766        }
1767
1768        public void handleError() {
1769            removeObbState(mObbState);
1770
1771            try {
1772                mObbState.token.onObbResult(mObbState.filename, "error");
1773            } catch (RemoteException e) {
1774                Slog.e(TAG, "Couldn't send back OBB mount error for " + mObbState.filename);
1775            }
1776        }
1777
1778        @Override
1779        public String toString() {
1780            StringBuilder sb = new StringBuilder();
1781            sb.append("MountObbAction{");
1782            sb.append("filename=");
1783            sb.append(mObbState.filename);
1784            sb.append(",callerUid=");
1785            sb.append(mObbState.callerUid);
1786            sb.append(",token=");
1787            sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL");
1788            sb.append('}');
1789            return sb.toString();
1790        }
1791    }
1792
1793    class UnmountObbAction extends ObbAction {
1794        private boolean mForceUnmount;
1795
1796        UnmountObbAction(ObbState obbState, boolean force) {
1797            super(obbState);
1798            mForceUnmount = force;
1799        }
1800
1801        public void handleExecute() throws RemoteException {
1802            ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
1803
1804            if (!isCallerOwnerOfPackageOrSystem(obbInfo.packageName)) {
1805                throw new IllegalArgumentException("Caller package does not match OBB file");
1806            }
1807
1808            int rc = StorageResultCode.OperationSucceeded;
1809            String cmd = String.format("obb unmount %s%s", mObbState.filename,
1810                    (mForceUnmount ? " force" : ""));
1811            try {
1812                mConnector.doCommand(cmd);
1813            } catch (NativeDaemonConnectorException e) {
1814                int code = e.getCode();
1815                if (code == VoldResponseCode.OpFailedStorageBusy) {
1816                    rc = StorageResultCode.OperationFailedStorageBusy;
1817                } else {
1818                    rc = StorageResultCode.OperationFailedInternalError;
1819                }
1820            }
1821
1822            if (rc == StorageResultCode.OperationSucceeded) {
1823                removeObbState(mObbState);
1824
1825                try {
1826                    mObbState.token.onObbResult(mObbState.filename, "unmounted");
1827                } catch (RemoteException e) {
1828                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
1829                }
1830            } else {
1831                try {
1832                    mObbState.token.onObbResult(mObbState.filename, "error");
1833                } catch (RemoteException e) {
1834                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
1835                }
1836            }
1837        }
1838
1839        public void handleError() {
1840            removeObbState(mObbState);
1841
1842            try {
1843                mObbState.token.onObbResult(mObbState.filename, "error");
1844            } catch (RemoteException e) {
1845                Slog.e(TAG, "Couldn't send back OBB unmount error for " + mObbState.filename);
1846            }
1847        }
1848
1849        @Override
1850        public String toString() {
1851            StringBuilder sb = new StringBuilder();
1852            sb.append("UnmountObbAction{");
1853            sb.append("filename=");
1854            sb.append(mObbState.filename != null ? mObbState.filename : "null");
1855            sb.append(",force=");
1856            sb.append(mForceUnmount);
1857            sb.append(",callerUid=");
1858            sb.append(mObbState.callerUid);
1859            sb.append(",token=");
1860            sb.append(mObbState.token != null ? mObbState.token.toString() : "null");
1861            sb.append('}');
1862            return sb.toString();
1863        }
1864    }
1865}
1866
1867