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