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