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