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