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