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