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