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