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