MountService.java revision 4fbbda4cecb078bd3867f416b02cc75f5455284f
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                && Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()))) {
1582            Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
1583        }
1584    }
1585
1586    public String[] getSecureContainerList() {
1587        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1588        waitForReady();
1589        warnOnNotMounted();
1590
1591        try {
1592            return NativeDaemonEvent.filterMessageList(
1593                    mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
1594        } catch (NativeDaemonConnectorException e) {
1595            return new String[0];
1596        }
1597    }
1598
1599    public int createSecureContainer(String id, int sizeMb, String fstype, String key,
1600            int ownerUid, boolean external) {
1601        validatePermission(android.Manifest.permission.ASEC_CREATE);
1602        waitForReady();
1603        warnOnNotMounted();
1604
1605        int rc = StorageResultCode.OperationSucceeded;
1606        try {
1607            mConnector.execute("asec", "create", id, sizeMb, fstype, key, ownerUid,
1608                    external ? "1" : "0");
1609        } catch (NativeDaemonConnectorException e) {
1610            rc = StorageResultCode.OperationFailedInternalError;
1611        }
1612
1613        if (rc == StorageResultCode.OperationSucceeded) {
1614            synchronized (mAsecMountSet) {
1615                mAsecMountSet.add(id);
1616            }
1617        }
1618        return rc;
1619    }
1620
1621    public int finalizeSecureContainer(String id) {
1622        validatePermission(android.Manifest.permission.ASEC_CREATE);
1623        warnOnNotMounted();
1624
1625        int rc = StorageResultCode.OperationSucceeded;
1626        try {
1627            mConnector.execute("asec", "finalize", id);
1628            /*
1629             * Finalization does a remount, so no need
1630             * to update mAsecMountSet
1631             */
1632        } catch (NativeDaemonConnectorException e) {
1633            rc = StorageResultCode.OperationFailedInternalError;
1634        }
1635        return rc;
1636    }
1637
1638    public int fixPermissionsSecureContainer(String id, int gid, String filename) {
1639        validatePermission(android.Manifest.permission.ASEC_CREATE);
1640        warnOnNotMounted();
1641
1642        int rc = StorageResultCode.OperationSucceeded;
1643        try {
1644            mConnector.execute("asec", "fixperms", id, gid, filename);
1645            /*
1646             * Fix permissions does a remount, so no need to update
1647             * mAsecMountSet
1648             */
1649        } catch (NativeDaemonConnectorException e) {
1650            rc = StorageResultCode.OperationFailedInternalError;
1651        }
1652        return rc;
1653    }
1654
1655    public int destroySecureContainer(String id, boolean force) {
1656        validatePermission(android.Manifest.permission.ASEC_DESTROY);
1657        waitForReady();
1658        warnOnNotMounted();
1659
1660        /*
1661         * Force a GC to make sure AssetManagers in other threads of the
1662         * system_server are cleaned up. We have to do this since AssetManager
1663         * instances are kept as a WeakReference and it's possible we have files
1664         * open on the external storage.
1665         */
1666        Runtime.getRuntime().gc();
1667
1668        int rc = StorageResultCode.OperationSucceeded;
1669        try {
1670            final Command cmd = new Command("asec", "destroy", id);
1671            if (force) {
1672                cmd.appendArg("force");
1673            }
1674            mConnector.execute(cmd);
1675        } catch (NativeDaemonConnectorException e) {
1676            int code = e.getCode();
1677            if (code == VoldResponseCode.OpFailedStorageBusy) {
1678                rc = StorageResultCode.OperationFailedStorageBusy;
1679            } else {
1680                rc = StorageResultCode.OperationFailedInternalError;
1681            }
1682        }
1683
1684        if (rc == StorageResultCode.OperationSucceeded) {
1685            synchronized (mAsecMountSet) {
1686                if (mAsecMountSet.contains(id)) {
1687                    mAsecMountSet.remove(id);
1688                }
1689            }
1690        }
1691
1692        return rc;
1693    }
1694
1695    public int mountSecureContainer(String id, String key, int ownerUid) {
1696        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1697        waitForReady();
1698        warnOnNotMounted();
1699
1700        synchronized (mAsecMountSet) {
1701            if (mAsecMountSet.contains(id)) {
1702                return StorageResultCode.OperationFailedStorageMounted;
1703            }
1704        }
1705
1706        int rc = StorageResultCode.OperationSucceeded;
1707        try {
1708            mConnector.execute("asec", "mount", id, key, ownerUid);
1709        } catch (NativeDaemonConnectorException e) {
1710            int code = e.getCode();
1711            if (code != VoldResponseCode.OpFailedStorageBusy) {
1712                rc = StorageResultCode.OperationFailedInternalError;
1713            }
1714        }
1715
1716        if (rc == StorageResultCode.OperationSucceeded) {
1717            synchronized (mAsecMountSet) {
1718                mAsecMountSet.add(id);
1719            }
1720        }
1721        return rc;
1722    }
1723
1724    public int unmountSecureContainer(String id, boolean force) {
1725        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1726        waitForReady();
1727        warnOnNotMounted();
1728
1729        synchronized (mAsecMountSet) {
1730            if (!mAsecMountSet.contains(id)) {
1731                return StorageResultCode.OperationFailedStorageNotMounted;
1732            }
1733         }
1734
1735        /*
1736         * Force a GC to make sure AssetManagers in other threads of the
1737         * system_server are cleaned up. We have to do this since AssetManager
1738         * instances are kept as a WeakReference and it's possible we have files
1739         * open on the external storage.
1740         */
1741        Runtime.getRuntime().gc();
1742
1743        int rc = StorageResultCode.OperationSucceeded;
1744        try {
1745            final Command cmd = new Command("asec", "unmount", id);
1746            if (force) {
1747                cmd.appendArg("force");
1748            }
1749            mConnector.execute(cmd);
1750        } catch (NativeDaemonConnectorException e) {
1751            int code = e.getCode();
1752            if (code == VoldResponseCode.OpFailedStorageBusy) {
1753                rc = StorageResultCode.OperationFailedStorageBusy;
1754            } else {
1755                rc = StorageResultCode.OperationFailedInternalError;
1756            }
1757        }
1758
1759        if (rc == StorageResultCode.OperationSucceeded) {
1760            synchronized (mAsecMountSet) {
1761                mAsecMountSet.remove(id);
1762            }
1763        }
1764        return rc;
1765    }
1766
1767    public boolean isSecureContainerMounted(String id) {
1768        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1769        waitForReady();
1770        warnOnNotMounted();
1771
1772        synchronized (mAsecMountSet) {
1773            return mAsecMountSet.contains(id);
1774        }
1775    }
1776
1777    public int renameSecureContainer(String oldId, String newId) {
1778        validatePermission(android.Manifest.permission.ASEC_RENAME);
1779        waitForReady();
1780        warnOnNotMounted();
1781
1782        synchronized (mAsecMountSet) {
1783            /*
1784             * Because a mounted container has active internal state which cannot be
1785             * changed while active, we must ensure both ids are not currently mounted.
1786             */
1787            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
1788                return StorageResultCode.OperationFailedStorageMounted;
1789            }
1790        }
1791
1792        int rc = StorageResultCode.OperationSucceeded;
1793        try {
1794            mConnector.execute("asec", "rename", oldId, newId);
1795        } catch (NativeDaemonConnectorException e) {
1796            rc = StorageResultCode.OperationFailedInternalError;
1797        }
1798
1799        return rc;
1800    }
1801
1802    public String getSecureContainerPath(String id) {
1803        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1804        waitForReady();
1805        warnOnNotMounted();
1806
1807        final NativeDaemonEvent event;
1808        try {
1809            event = mConnector.execute("asec", "path", id);
1810            event.checkCode(VoldResponseCode.AsecPathResult);
1811            return event.getMessage();
1812        } catch (NativeDaemonConnectorException e) {
1813            int code = e.getCode();
1814            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1815                Slog.i(TAG, String.format("Container '%s' not found", id));
1816                return null;
1817            } else {
1818                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1819            }
1820        }
1821    }
1822
1823    public String getSecureContainerFilesystemPath(String id) {
1824        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1825        waitForReady();
1826        warnOnNotMounted();
1827
1828        final NativeDaemonEvent event;
1829        try {
1830            event = mConnector.execute("asec", "fspath", id);
1831            event.checkCode(VoldResponseCode.AsecPathResult);
1832            return event.getMessage();
1833        } catch (NativeDaemonConnectorException e) {
1834            int code = e.getCode();
1835            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1836                Slog.i(TAG, String.format("Container '%s' not found", id));
1837                return null;
1838            } else {
1839                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1840            }
1841        }
1842    }
1843
1844    public void finishMediaUpdate() {
1845        mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
1846    }
1847
1848    private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
1849        if (callerUid == android.os.Process.SYSTEM_UID) {
1850            return true;
1851        }
1852
1853        if (packageName == null) {
1854            return false;
1855        }
1856
1857        final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid));
1858
1859        if (DEBUG_OBB) {
1860            Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
1861                    packageUid + ", callerUid = " + callerUid);
1862        }
1863
1864        return callerUid == packageUid;
1865    }
1866
1867    public String getMountedObbPath(String rawPath) {
1868        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1869
1870        waitForReady();
1871        warnOnNotMounted();
1872
1873        final ObbState state;
1874        synchronized (mObbPathToStateMap) {
1875            state = mObbPathToStateMap.get(rawPath);
1876        }
1877        if (state == null) {
1878            Slog.w(TAG, "Failed to find OBB mounted at " + rawPath);
1879            return null;
1880        }
1881
1882        final NativeDaemonEvent event;
1883        try {
1884            event = mConnector.execute("obb", "path", state.voldPath);
1885            event.checkCode(VoldResponseCode.AsecPathResult);
1886            return event.getMessage();
1887        } catch (NativeDaemonConnectorException e) {
1888            int code = e.getCode();
1889            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1890                return null;
1891            } else {
1892                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1893            }
1894        }
1895    }
1896
1897    @Override
1898    public boolean isObbMounted(String rawPath) {
1899        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1900        synchronized (mObbMounts) {
1901            return mObbPathToStateMap.containsKey(rawPath);
1902        }
1903    }
1904
1905    @Override
1906    public void mountObb(
1907            String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) {
1908        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1909        Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null");
1910        Preconditions.checkNotNull(token, "token cannot be null");
1911
1912        final int callingUid = Binder.getCallingUid();
1913        final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce);
1914        final ObbAction action = new MountObbAction(obbState, key, callingUid);
1915        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1916
1917        if (DEBUG_OBB)
1918            Slog.i(TAG, "Send to OBB handler: " + action.toString());
1919    }
1920
1921    @Override
1922    public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) {
1923        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1924
1925        final ObbState existingState;
1926        synchronized (mObbPathToStateMap) {
1927            existingState = mObbPathToStateMap.get(rawPath);
1928        }
1929
1930        if (existingState != null) {
1931            // TODO: separate state object from request data
1932            final int callingUid = Binder.getCallingUid();
1933            final ObbState newState = new ObbState(
1934                    rawPath, existingState.canonicalPath, callingUid, token, nonce);
1935            final ObbAction action = new UnmountObbAction(newState, force);
1936            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1937
1938            if (DEBUG_OBB)
1939                Slog.i(TAG, "Send to OBB handler: " + action.toString());
1940        } else {
1941            Slog.w(TAG, "Unknown OBB mount at " + rawPath);
1942        }
1943    }
1944
1945    @Override
1946    public int getEncryptionState() {
1947        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1948                "no permission to access the crypt keeper");
1949
1950        waitForReady();
1951
1952        final NativeDaemonEvent event;
1953        try {
1954            event = mConnector.execute("cryptfs", "cryptocomplete");
1955            return Integer.parseInt(event.getMessage());
1956        } catch (NumberFormatException e) {
1957            // Bad result - unexpected.
1958            Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
1959            return ENCRYPTION_STATE_ERROR_UNKNOWN;
1960        } catch (NativeDaemonConnectorException e) {
1961            // Something bad happened.
1962            Slog.w(TAG, "Error in communicating with cryptfs in validating");
1963            return ENCRYPTION_STATE_ERROR_UNKNOWN;
1964        }
1965    }
1966
1967    @Override
1968    public int decryptStorage(String password) {
1969        if (TextUtils.isEmpty(password)) {
1970            throw new IllegalArgumentException("password cannot be empty");
1971        }
1972
1973        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1974                "no permission to access the crypt keeper");
1975
1976        waitForReady();
1977
1978        if (DEBUG_EVENTS) {
1979            Slog.i(TAG, "decrypting storage...");
1980        }
1981
1982        final NativeDaemonEvent event;
1983        try {
1984            event = mConnector.execute("cryptfs", "checkpw", password);
1985
1986            final int code = Integer.parseInt(event.getMessage());
1987            if (code == 0) {
1988                // Decrypt was successful. Post a delayed message before restarting in order
1989                // to let the UI to clear itself
1990                mHandler.postDelayed(new Runnable() {
1991                    public void run() {
1992                        try {
1993                            mConnector.execute("cryptfs", "restart");
1994                        } catch (NativeDaemonConnectorException e) {
1995                            Slog.e(TAG, "problem executing in background", e);
1996                        }
1997                    }
1998                }, 1000); // 1 second
1999            }
2000
2001            return code;
2002        } catch (NativeDaemonConnectorException e) {
2003            // Decryption failed
2004            return e.getCode();
2005        }
2006    }
2007
2008    public int encryptStorage(String password) {
2009        if (TextUtils.isEmpty(password)) {
2010            throw new IllegalArgumentException("password cannot be empty");
2011        }
2012
2013        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
2014            "no permission to access the crypt keeper");
2015
2016        waitForReady();
2017
2018        if (DEBUG_EVENTS) {
2019            Slog.i(TAG, "encrypting storage...");
2020        }
2021
2022        try {
2023            mConnector.execute("cryptfs", "enablecrypto", "inplace", password);
2024        } catch (NativeDaemonConnectorException e) {
2025            // Encryption failed
2026            return e.getCode();
2027        }
2028
2029        return 0;
2030    }
2031
2032    public int changeEncryptionPassword(String password) {
2033        if (TextUtils.isEmpty(password)) {
2034            throw new IllegalArgumentException("password cannot be empty");
2035        }
2036
2037        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
2038            "no permission to access the crypt keeper");
2039
2040        waitForReady();
2041
2042        if (DEBUG_EVENTS) {
2043            Slog.i(TAG, "changing encryption password...");
2044        }
2045
2046        final NativeDaemonEvent event;
2047        try {
2048            event = mConnector.execute("cryptfs", "changepw", password);
2049            return Integer.parseInt(event.getMessage());
2050        } catch (NativeDaemonConnectorException e) {
2051            // Encryption failed
2052            return e.getCode();
2053        }
2054    }
2055
2056    /**
2057     * Validate a user-supplied password string with cryptfs
2058     */
2059    @Override
2060    public int verifyEncryptionPassword(String password) throws RemoteException {
2061        // Only the system process is permitted to validate passwords
2062        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
2063            throw new SecurityException("no permission to access the crypt keeper");
2064        }
2065
2066        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
2067            "no permission to access the crypt keeper");
2068
2069        if (TextUtils.isEmpty(password)) {
2070            throw new IllegalArgumentException("password cannot be empty");
2071        }
2072
2073        waitForReady();
2074
2075        if (DEBUG_EVENTS) {
2076            Slog.i(TAG, "validating encryption password...");
2077        }
2078
2079        final NativeDaemonEvent event;
2080        try {
2081            event = mConnector.execute("cryptfs", "verifypw", password);
2082            Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
2083            return Integer.parseInt(event.getMessage());
2084        } catch (NativeDaemonConnectorException e) {
2085            // Encryption failed
2086            return e.getCode();
2087        }
2088    }
2089
2090    @Override
2091    public StorageVolume[] getVolumeList() {
2092        final int callingUserId = UserHandle.getCallingUserId();
2093        final boolean accessAll = (mContext.checkPermission(
2094                android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
2095                Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED);
2096
2097        synchronized (mVolumesLock) {
2098            final ArrayList<StorageVolume> filtered = Lists.newArrayList();
2099            for (StorageVolume volume : mVolumes) {
2100                final UserHandle owner = volume.getOwner();
2101                final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId;
2102                if (accessAll || ownerMatch) {
2103                    filtered.add(volume);
2104                }
2105            }
2106            return filtered.toArray(new StorageVolume[filtered.size()]);
2107        }
2108    }
2109
2110    private void addObbStateLocked(ObbState obbState) throws RemoteException {
2111        final IBinder binder = obbState.getBinder();
2112        List<ObbState> obbStates = mObbMounts.get(binder);
2113
2114        if (obbStates == null) {
2115            obbStates = new ArrayList<ObbState>();
2116            mObbMounts.put(binder, obbStates);
2117        } else {
2118            for (final ObbState o : obbStates) {
2119                if (o.rawPath.equals(obbState.rawPath)) {
2120                    throw new IllegalStateException("Attempt to add ObbState twice. "
2121                            + "This indicates an error in the MountService logic.");
2122                }
2123            }
2124        }
2125
2126        obbStates.add(obbState);
2127        try {
2128            obbState.link();
2129        } catch (RemoteException e) {
2130            /*
2131             * The binder died before we could link it, so clean up our state
2132             * and return failure.
2133             */
2134            obbStates.remove(obbState);
2135            if (obbStates.isEmpty()) {
2136                mObbMounts.remove(binder);
2137            }
2138
2139            // Rethrow the error so mountObb can get it
2140            throw e;
2141        }
2142
2143        mObbPathToStateMap.put(obbState.rawPath, obbState);
2144    }
2145
2146    private void removeObbStateLocked(ObbState obbState) {
2147        final IBinder binder = obbState.getBinder();
2148        final List<ObbState> obbStates = mObbMounts.get(binder);
2149        if (obbStates != null) {
2150            if (obbStates.remove(obbState)) {
2151                obbState.unlink();
2152            }
2153            if (obbStates.isEmpty()) {
2154                mObbMounts.remove(binder);
2155            }
2156        }
2157
2158        mObbPathToStateMap.remove(obbState.rawPath);
2159    }
2160
2161    private class ObbActionHandler extends Handler {
2162        private boolean mBound = false;
2163        private final List<ObbAction> mActions = new LinkedList<ObbAction>();
2164
2165        ObbActionHandler(Looper l) {
2166            super(l);
2167        }
2168
2169        @Override
2170        public void handleMessage(Message msg) {
2171            switch (msg.what) {
2172                case OBB_RUN_ACTION: {
2173                    final ObbAction action = (ObbAction) msg.obj;
2174
2175                    if (DEBUG_OBB)
2176                        Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
2177
2178                    // If a bind was already initiated we don't really
2179                    // need to do anything. The pending install
2180                    // will be processed later on.
2181                    if (!mBound) {
2182                        // If this is the only one pending we might
2183                        // have to bind to the service again.
2184                        if (!connectToService()) {
2185                            Slog.e(TAG, "Failed to bind to media container service");
2186                            action.handleError();
2187                            return;
2188                        }
2189                    }
2190
2191                    mActions.add(action);
2192                    break;
2193                }
2194                case OBB_MCS_BOUND: {
2195                    if (DEBUG_OBB)
2196                        Slog.i(TAG, "OBB_MCS_BOUND");
2197                    if (msg.obj != null) {
2198                        mContainerService = (IMediaContainerService) msg.obj;
2199                    }
2200                    if (mContainerService == null) {
2201                        // Something seriously wrong. Bail out
2202                        Slog.e(TAG, "Cannot bind to media container service");
2203                        for (ObbAction action : mActions) {
2204                            // Indicate service bind error
2205                            action.handleError();
2206                        }
2207                        mActions.clear();
2208                    } else if (mActions.size() > 0) {
2209                        final ObbAction action = mActions.get(0);
2210                        if (action != null) {
2211                            action.execute(this);
2212                        }
2213                    } else {
2214                        // Should never happen ideally.
2215                        Slog.w(TAG, "Empty queue");
2216                    }
2217                    break;
2218                }
2219                case OBB_MCS_RECONNECT: {
2220                    if (DEBUG_OBB)
2221                        Slog.i(TAG, "OBB_MCS_RECONNECT");
2222                    if (mActions.size() > 0) {
2223                        if (mBound) {
2224                            disconnectService();
2225                        }
2226                        if (!connectToService()) {
2227                            Slog.e(TAG, "Failed to bind to media container service");
2228                            for (ObbAction action : mActions) {
2229                                // Indicate service bind error
2230                                action.handleError();
2231                            }
2232                            mActions.clear();
2233                        }
2234                    }
2235                    break;
2236                }
2237                case OBB_MCS_UNBIND: {
2238                    if (DEBUG_OBB)
2239                        Slog.i(TAG, "OBB_MCS_UNBIND");
2240
2241                    // Delete pending install
2242                    if (mActions.size() > 0) {
2243                        mActions.remove(0);
2244                    }
2245                    if (mActions.size() == 0) {
2246                        if (mBound) {
2247                            disconnectService();
2248                        }
2249                    } else {
2250                        // There are more pending requests in queue.
2251                        // Just post MCS_BOUND message to trigger processing
2252                        // of next pending install.
2253                        mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
2254                    }
2255                    break;
2256                }
2257                case OBB_FLUSH_MOUNT_STATE: {
2258                    final String path = (String) msg.obj;
2259
2260                    if (DEBUG_OBB)
2261                        Slog.i(TAG, "Flushing all OBB state for path " + path);
2262
2263                    synchronized (mObbMounts) {
2264                        final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
2265
2266                        final Iterator<ObbState> i = mObbPathToStateMap.values().iterator();
2267                        while (i.hasNext()) {
2268                            final ObbState state = i.next();
2269
2270                            /*
2271                             * If this entry's source file is in the volume path
2272                             * that got unmounted, remove it because it's no
2273                             * longer valid.
2274                             */
2275                            if (state.canonicalPath.startsWith(path)) {
2276                                obbStatesToRemove.add(state);
2277                            }
2278                        }
2279
2280                        for (final ObbState obbState : obbStatesToRemove) {
2281                            if (DEBUG_OBB)
2282                                Slog.i(TAG, "Removing state for " + obbState.rawPath);
2283
2284                            removeObbStateLocked(obbState);
2285
2286                            try {
2287                                obbState.token.onObbResult(obbState.rawPath, obbState.nonce,
2288                                        OnObbStateChangeListener.UNMOUNTED);
2289                            } catch (RemoteException e) {
2290                                Slog.i(TAG, "Couldn't send unmount notification for  OBB: "
2291                                        + obbState.rawPath);
2292                            }
2293                        }
2294                    }
2295                    break;
2296                }
2297            }
2298        }
2299
2300        private boolean connectToService() {
2301            if (DEBUG_OBB)
2302                Slog.i(TAG, "Trying to bind to DefaultContainerService");
2303
2304            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
2305            if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
2306                mBound = true;
2307                return true;
2308            }
2309            return false;
2310        }
2311
2312        private void disconnectService() {
2313            mContainerService = null;
2314            mBound = false;
2315            mContext.unbindService(mDefContainerConn);
2316        }
2317    }
2318
2319    abstract class ObbAction {
2320        private static final int MAX_RETRIES = 3;
2321        private int mRetries;
2322
2323        ObbState mObbState;
2324
2325        ObbAction(ObbState obbState) {
2326            mObbState = obbState;
2327        }
2328
2329        public void execute(ObbActionHandler handler) {
2330            try {
2331                if (DEBUG_OBB)
2332                    Slog.i(TAG, "Starting to execute action: " + toString());
2333                mRetries++;
2334                if (mRetries > MAX_RETRIES) {
2335                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
2336                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2337                    handleError();
2338                    return;
2339                } else {
2340                    handleExecute();
2341                    if (DEBUG_OBB)
2342                        Slog.i(TAG, "Posting install MCS_UNBIND");
2343                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2344                }
2345            } catch (RemoteException e) {
2346                if (DEBUG_OBB)
2347                    Slog.i(TAG, "Posting install MCS_RECONNECT");
2348                mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT);
2349            } catch (Exception e) {
2350                if (DEBUG_OBB)
2351                    Slog.d(TAG, "Error handling OBB action", e);
2352                handleError();
2353                mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2354            }
2355        }
2356
2357        abstract void handleExecute() throws RemoteException, IOException;
2358        abstract void handleError();
2359
2360        protected ObbInfo getObbInfo() throws IOException {
2361            ObbInfo obbInfo;
2362            try {
2363                obbInfo = mContainerService.getObbInfo(mObbState.ownerPath);
2364            } catch (RemoteException e) {
2365                Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
2366                        + mObbState.ownerPath);
2367                obbInfo = null;
2368            }
2369            if (obbInfo == null) {
2370                throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath);
2371            }
2372            return obbInfo;
2373        }
2374
2375        protected void sendNewStatusOrIgnore(int status) {
2376            if (mObbState == null || mObbState.token == null) {
2377                return;
2378            }
2379
2380            try {
2381                mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status);
2382            } catch (RemoteException e) {
2383                Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
2384            }
2385        }
2386    }
2387
2388    class MountObbAction extends ObbAction {
2389        private final String mKey;
2390        private final int mCallingUid;
2391
2392        MountObbAction(ObbState obbState, String key, int callingUid) {
2393            super(obbState);
2394            mKey = key;
2395            mCallingUid = callingUid;
2396        }
2397
2398        @Override
2399        public void handleExecute() throws IOException, RemoteException {
2400            waitForReady();
2401            warnOnNotMounted();
2402
2403            final ObbInfo obbInfo = getObbInfo();
2404
2405            if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) {
2406                Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename
2407                        + " which is owned by " + obbInfo.packageName);
2408                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
2409                return;
2410            }
2411
2412            final boolean isMounted;
2413            synchronized (mObbMounts) {
2414                isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath);
2415            }
2416            if (isMounted) {
2417                Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename);
2418                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED);
2419                return;
2420            }
2421
2422            final String hashedKey;
2423            if (mKey == null) {
2424                hashedKey = "none";
2425            } else {
2426                try {
2427                    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
2428
2429                    KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt,
2430                            PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE);
2431                    SecretKey key = factory.generateSecret(ks);
2432                    BigInteger bi = new BigInteger(key.getEncoded());
2433                    hashedKey = bi.toString(16);
2434                } catch (NoSuchAlgorithmException e) {
2435                    Slog.e(TAG, "Could not load PBKDF2 algorithm", e);
2436                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2437                    return;
2438                } catch (InvalidKeySpecException e) {
2439                    Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e);
2440                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2441                    return;
2442                }
2443            }
2444
2445            int rc = StorageResultCode.OperationSucceeded;
2446            try {
2447                mConnector.execute(
2448                        "obb", "mount", mObbState.voldPath, hashedKey, mObbState.ownerGid);
2449            } catch (NativeDaemonConnectorException e) {
2450                int code = e.getCode();
2451                if (code != VoldResponseCode.OpFailedStorageBusy) {
2452                    rc = StorageResultCode.OperationFailedInternalError;
2453                }
2454            }
2455
2456            if (rc == StorageResultCode.OperationSucceeded) {
2457                if (DEBUG_OBB)
2458                    Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath);
2459
2460                synchronized (mObbMounts) {
2461                    addObbStateLocked(mObbState);
2462                }
2463
2464                sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED);
2465            } else {
2466                Slog.e(TAG, "Couldn't mount OBB file: " + rc);
2467
2468                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT);
2469            }
2470        }
2471
2472        @Override
2473        public void handleError() {
2474            sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2475        }
2476
2477        @Override
2478        public String toString() {
2479            StringBuilder sb = new StringBuilder();
2480            sb.append("MountObbAction{");
2481            sb.append(mObbState);
2482            sb.append('}');
2483            return sb.toString();
2484        }
2485    }
2486
2487    class UnmountObbAction extends ObbAction {
2488        private final boolean mForceUnmount;
2489
2490        UnmountObbAction(ObbState obbState, boolean force) {
2491            super(obbState);
2492            mForceUnmount = force;
2493        }
2494
2495        @Override
2496        public void handleExecute() throws IOException {
2497            waitForReady();
2498            warnOnNotMounted();
2499
2500            final ObbInfo obbInfo = getObbInfo();
2501
2502            final ObbState existingState;
2503            synchronized (mObbMounts) {
2504                existingState = mObbPathToStateMap.get(mObbState.rawPath);
2505            }
2506
2507            if (existingState == null) {
2508                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED);
2509                return;
2510            }
2511
2512            if (existingState.ownerGid != mObbState.ownerGid) {
2513                Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath
2514                        + " (owned by GID " + existingState.ownerGid + ")");
2515                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
2516                return;
2517            }
2518
2519            int rc = StorageResultCode.OperationSucceeded;
2520            try {
2521                final Command cmd = new Command("obb", "unmount", mObbState.voldPath);
2522                if (mForceUnmount) {
2523                    cmd.appendArg("force");
2524                }
2525                mConnector.execute(cmd);
2526            } catch (NativeDaemonConnectorException e) {
2527                int code = e.getCode();
2528                if (code == VoldResponseCode.OpFailedStorageBusy) {
2529                    rc = StorageResultCode.OperationFailedStorageBusy;
2530                } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
2531                    // If it's not mounted then we've already won.
2532                    rc = StorageResultCode.OperationSucceeded;
2533                } else {
2534                    rc = StorageResultCode.OperationFailedInternalError;
2535                }
2536            }
2537
2538            if (rc == StorageResultCode.OperationSucceeded) {
2539                synchronized (mObbMounts) {
2540                    removeObbStateLocked(existingState);
2541                }
2542
2543                sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED);
2544            } else {
2545                Slog.w(TAG, "Could not unmount OBB: " + existingState);
2546                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT);
2547            }
2548        }
2549
2550        @Override
2551        public void handleError() {
2552            sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2553        }
2554
2555        @Override
2556        public String toString() {
2557            StringBuilder sb = new StringBuilder();
2558            sb.append("UnmountObbAction{");
2559            sb.append(mObbState);
2560            sb.append(",force=");
2561            sb.append(mForceUnmount);
2562            sb.append('}');
2563            return sb.toString();
2564        }
2565    }
2566
2567    // @VisibleForTesting
2568    public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) {
2569        // TODO: allow caller to provide Environment for full testing
2570
2571        // Only adjust paths when storage is emulated
2572        if (!Environment.isExternalStorageEmulated()) {
2573            return canonicalPath;
2574        }
2575
2576        String path = canonicalPath.toString();
2577
2578        // First trim off any external storage prefix
2579        final UserEnvironment userEnv = new UserEnvironment(userId);
2580
2581        // /storage/emulated/0
2582        final String externalPath = userEnv.getExternalStorageDirectory().toString();
2583        // /storage/emulated_legacy
2584        final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory()
2585                .toString();
2586
2587        if (path.startsWith(externalPath)) {
2588            path = path.substring(externalPath.length() + 1);
2589        } else if (path.startsWith(legacyExternalPath)) {
2590            path = path.substring(legacyExternalPath.length() + 1);
2591        } else {
2592            return canonicalPath;
2593        }
2594
2595        // Handle special OBB paths on emulated storage
2596        final String obbPath = "Android/obb";
2597        if (path.startsWith(obbPath)) {
2598            path = path.substring(obbPath.length() + 1);
2599
2600            if (forVold) {
2601                return new File(Environment.getEmulatedStorageObbSource(), path).toString();
2602            } else {
2603                final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
2604                return new File(ownerEnv.getExternalStorageObbDirectory(), path).toString();
2605            }
2606        }
2607
2608        // Handle normal external storage paths
2609        if (forVold) {
2610            return new File(Environment.getEmulatedStorageSource(userId), path).toString();
2611        } else {
2612            return new File(userEnv.getExternalStorageDirectory(), path).toString();
2613        }
2614    }
2615
2616    @Override
2617    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2618        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
2619            pw.println("Permission Denial: can't dump ActivityManager from from pid="
2620                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
2621                    + " without permission " + android.Manifest.permission.DUMP);
2622            return;
2623        }
2624
2625        synchronized (mObbMounts) {
2626            pw.println("  mObbMounts:");
2627
2628            final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator();
2629            while (binders.hasNext()) {
2630                Entry<IBinder, List<ObbState>> e = binders.next();
2631                pw.print("    Key="); pw.println(e.getKey().toString());
2632                final List<ObbState> obbStates = e.getValue();
2633                for (final ObbState obbState : obbStates) {
2634                    pw.print("      "); pw.println(obbState.toString());
2635                }
2636            }
2637
2638            pw.println("");
2639            pw.println("  mObbPathToStateMap:");
2640            final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
2641            while (maps.hasNext()) {
2642                final Entry<String, ObbState> e = maps.next();
2643                pw.print("    "); pw.print(e.getKey());
2644                pw.print(" -> "); pw.println(e.getValue().toString());
2645            }
2646        }
2647
2648        pw.println("");
2649
2650        synchronized (mVolumesLock) {
2651            pw.println("  mVolumes:");
2652
2653            final int N = mVolumes.size();
2654            for (int i = 0; i < N; i++) {
2655                final StorageVolume v = mVolumes.get(i);
2656                pw.print("    ");
2657                pw.println(v.toString());
2658            }
2659        }
2660
2661        pw.println();
2662        pw.println("  mConnection:");
2663        mConnector.dump(fd, pw, args);
2664    }
2665
2666    /** {@inheritDoc} */
2667    public void monitor() {
2668        if (mConnector != null) {
2669            mConnector.monitor();
2670        }
2671    }
2672}
2673