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