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