MountService.java revision 7e92ef3a1146102806fa0543ef12e09231c55639
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 com.android.internal.util.XmlUtils.readIntAttribute;
20import static com.android.internal.util.XmlUtils.readStringAttribute;
21import static com.android.internal.util.XmlUtils.writeIntAttribute;
22import static com.android.internal.util.XmlUtils.writeStringAttribute;
23import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
24import static org.xmlpull.v1.XmlPullParser.START_TAG;
25
26import android.Manifest;
27import android.app.ActivityManagerNative;
28import android.app.AppOpsManager;
29import android.content.ComponentName;
30import android.content.Context;
31import android.content.Intent;
32import android.content.ServiceConnection;
33import android.content.res.Configuration;
34import android.content.res.ObbInfo;
35import android.mtp.MtpStorage;
36import android.net.Uri;
37import android.os.Binder;
38import android.os.Environment;
39import android.os.Environment.UserEnvironment;
40import android.os.FileUtils;
41import android.os.Handler;
42import android.os.HandlerThread;
43import android.os.IBinder;
44import android.os.Looper;
45import android.os.Message;
46import android.os.RemoteCallbackList;
47import android.os.RemoteException;
48import android.os.ServiceManager;
49import android.os.SystemClock;
50import android.os.SystemProperties;
51import android.os.UserHandle;
52import android.os.UserManager;
53import android.os.storage.DiskInfo;
54import android.os.storage.IMountService;
55import android.os.storage.IMountServiceListener;
56import android.os.storage.IMountShutdownObserver;
57import android.os.storage.IObbActionListener;
58import android.os.storage.OnObbStateChangeListener;
59import android.os.storage.StorageManager;
60import android.os.storage.StorageResultCode;
61import android.os.storage.StorageVolume;
62import android.os.storage.VolumeInfo;
63import android.text.TextUtils;
64import android.text.format.DateUtils;
65import android.util.ArrayMap;
66import android.util.AtomicFile;
67import android.util.DebugUtils;
68import android.util.Log;
69import android.util.Slog;
70import android.util.Xml;
71
72import libcore.io.IoUtils;
73import libcore.util.EmptyArray;
74import libcore.util.HexEncoding;
75
76import com.android.internal.annotations.GuardedBy;
77import com.android.internal.annotations.VisibleForTesting;
78import com.android.internal.app.IMediaContainerService;
79import com.android.internal.os.SomeArgs;
80import com.android.internal.util.ArrayUtils;
81import com.android.internal.util.FastXmlSerializer;
82import com.android.internal.util.IndentingPrintWriter;
83import com.android.internal.util.Preconditions;
84import com.android.server.NativeDaemonConnector.Command;
85import com.android.server.NativeDaemonConnector.SensitiveArg;
86import com.android.server.pm.PackageManagerService;
87
88import org.xmlpull.v1.XmlPullParser;
89import org.xmlpull.v1.XmlPullParserException;
90import org.xmlpull.v1.XmlSerializer;
91
92import java.io.File;
93import java.io.FileDescriptor;
94import java.io.FileInputStream;
95import java.io.FileNotFoundException;
96import java.io.FileOutputStream;
97import java.io.IOException;
98import java.io.PrintWriter;
99import java.math.BigInteger;
100import java.nio.charset.StandardCharsets;
101import java.security.NoSuchAlgorithmException;
102import java.security.spec.InvalidKeySpecException;
103import java.security.spec.KeySpec;
104import java.text.SimpleDateFormat;
105import java.util.ArrayList;
106import java.util.Arrays;
107import java.util.Date;
108import java.util.HashMap;
109import java.util.HashSet;
110import java.util.Iterator;
111import java.util.LinkedList;
112import java.util.List;
113import java.util.Locale;
114import java.util.Map;
115import java.util.Map.Entry;
116import java.util.Objects;
117import java.util.concurrent.CountDownLatch;
118import java.util.concurrent.TimeUnit;
119
120import javax.crypto.SecretKey;
121import javax.crypto.SecretKeyFactory;
122import javax.crypto.spec.PBEKeySpec;
123
124/**
125 * Service responsible for various storage media. Connects to {@code vold} to
126 * watch for and manage dynamically added storage, such as SD cards and USB mass
127 * storage. Also decides how storage should be presented to users on the device.
128 */
129class MountService extends IMountService.Stub
130        implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
131
132    // TODO: finish enforcing UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA
133
134    // Static direct instance pointer for the tightly-coupled idle service to use
135    static MountService sSelf = null;
136
137    public static class Lifecycle extends SystemService {
138        private MountService mMountService;
139
140        public Lifecycle(Context context) {
141            super(context);
142        }
143
144        @Override
145        public void onStart() {
146            mMountService = new MountService(getContext());
147            publishBinderService("mount", mMountService);
148        }
149
150        @Override
151        public void onBootPhase(int phase) {
152            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
153                mMountService.systemReady();
154            }
155        }
156
157        @Override
158        public void onStartUser(int userHandle) {
159            mMountService.onStartUser(userHandle);
160        }
161
162        @Override
163        public void onCleanupUser(int userHandle) {
164            mMountService.onCleanupUser(userHandle);
165        }
166    }
167
168    private static final boolean LOCAL_LOGD = false;
169    private static final boolean DEBUG_EVENTS = false;
170    private static final boolean DEBUG_OBB = false;
171
172    // Disable this since it messes up long-running cryptfs operations.
173    private static final boolean WATCHDOG_ENABLE = false;
174
175    private static final String TAG = "MountService";
176
177    private static final String VOLD_TAG = "VoldConnector";
178
179    /** Maximum number of ASEC containers allowed to be mounted. */
180    private static final int MAX_CONTAINERS = 250;
181
182    /*
183     * Internal vold response code constants
184     */
185    class VoldResponseCode {
186        /*
187         * 100 series - Requestion action was initiated; expect another reply
188         *              before proceeding with a new command.
189         */
190        public static final int VolumeListResult               = 110;
191        public static final int AsecListResult                 = 111;
192        public static final int StorageUsersListResult         = 112;
193        public static final int CryptfsGetfieldResult          = 113;
194
195        /*
196         * 200 series - Requestion action has been successfully completed.
197         */
198        public static final int ShareStatusResult              = 210;
199        public static final int AsecPathResult                 = 211;
200        public static final int ShareEnabledResult             = 212;
201
202        /*
203         * 400 series - Command was accepted, but the requested action
204         *              did not take place.
205         */
206        public static final int OpFailedNoMedia                = 401;
207        public static final int OpFailedMediaBlank             = 402;
208        public static final int OpFailedMediaCorrupt           = 403;
209        public static final int OpFailedVolNotMounted          = 404;
210        public static final int OpFailedStorageBusy            = 405;
211        public static final int OpFailedStorageNotFound        = 406;
212
213        /*
214         * 600 series - Unsolicited broadcasts.
215         */
216        public static final int DISK_CREATED = 640;
217        public static final int DISK_SIZE_CHANGED = 641;
218        public static final int DISK_LABEL_CHANGED = 642;
219        public static final int DISK_UNSUPPORTED = 643;
220        public static final int DISK_DESTROYED = 649;
221
222        public static final int VOLUME_CREATED = 650;
223        public static final int VOLUME_STATE_CHANGED = 651;
224        public static final int VOLUME_FS_TYPE_CHANGED = 652;
225        public static final int VOLUME_FS_UUID_CHANGED = 653;
226        public static final int VOLUME_FS_LABEL_CHANGED = 654;
227        public static final int VOLUME_PATH_CHANGED = 655;
228        public static final int VOLUME_DESTROYED = 659;
229
230        /*
231         * 700 series - fstrim
232         */
233        public static final int FstrimCompleted                = 700;
234    }
235
236    private static final String TAG_VOLUMES = "volumes";
237    private static final String TAG_VOLUME = "volume";
238    private static final String ATTR_TYPE = "type";
239    private static final String ATTR_FS_UUID = "fsUuid";
240    private static final String ATTR_NICKNAME = "nickname";
241    private static final String ATTR_USER_FLAGS = "userFlags";
242
243    private final AtomicFile mMetadataFile;
244
245    private static class VolumeMetadata {
246        public final int type;
247        public final String fsUuid;
248        public String nickname;
249        public int userFlags;
250
251        public VolumeMetadata(int type, String fsUuid) {
252            this.type = type;
253            this.fsUuid = Preconditions.checkNotNull(fsUuid);
254        }
255
256        public static VolumeMetadata read(XmlPullParser in) throws IOException {
257            final int type = readIntAttribute(in, ATTR_TYPE);
258            final String fsUuid = readStringAttribute(in, ATTR_FS_UUID);
259            final VolumeMetadata meta = new VolumeMetadata(type, fsUuid);
260            meta.nickname = readStringAttribute(in, ATTR_NICKNAME);
261            meta.userFlags = readIntAttribute(in, ATTR_USER_FLAGS);
262            return meta;
263        }
264
265        public static void write(XmlSerializer out, VolumeMetadata meta) throws IOException {
266            out.startTag(null, TAG_VOLUME);
267            writeIntAttribute(out, ATTR_TYPE, meta.type);
268            writeStringAttribute(out, ATTR_FS_UUID, meta.fsUuid);
269            writeStringAttribute(out, ATTR_NICKNAME, meta.nickname);
270            writeIntAttribute(out, ATTR_USER_FLAGS, meta.userFlags);
271            out.endTag(null, TAG_VOLUME);
272        }
273
274        public void dump(IndentingPrintWriter pw) {
275            pw.println("VolumeMetadata:");
276            pw.increaseIndent();
277            pw.printPair("type", DebugUtils.valueToString(VolumeInfo.class, "TYPE_", type));
278            pw.printPair("fsUuid", fsUuid);
279            pw.printPair("nickname", nickname);
280            pw.printPair("userFlags",
281                    DebugUtils.flagsToString(VolumeInfo.class, "USER_FLAG_", userFlags));
282            pw.decreaseIndent();
283            pw.println();
284        }
285    }
286
287    /**
288     * <em>Never</em> hold the lock while performing downcalls into vold, since
289     * unsolicited events can suddenly appear to update data structures.
290     */
291    private final Object mLock = new Object();
292
293    @GuardedBy("mLock")
294    private int[] mStartedUsers = EmptyArray.INT;
295
296    /** Map from disk ID to disk */
297    @GuardedBy("mLock")
298    private ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>();
299    /** Map from volume ID to disk */
300    @GuardedBy("mLock")
301    private ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>();
302
303    /** Map from UUID to metadata */
304    @GuardedBy("mLock")
305    private ArrayMap<String, VolumeMetadata> mMetadata = new ArrayMap<>();
306
307    private DiskInfo findDiskById(String id) {
308        synchronized (mLock) {
309            final DiskInfo disk = mDisks.get(id);
310            if (disk != null) {
311                return disk;
312            }
313        }
314        throw new IllegalArgumentException("No disk found for ID " + id);
315    }
316
317    private VolumeInfo findVolumeById(String id) {
318        synchronized (mLock) {
319            final VolumeInfo vol = mVolumes.get(id);
320            if (vol != null) {
321                return vol;
322            }
323        }
324        throw new IllegalArgumentException("No volume found for ID " + id);
325    }
326
327    @Deprecated
328    private String findVolumeIdForPath(String path) {
329        synchronized (mLock) {
330            for (int i = 0; i < mVolumes.size(); i++) {
331                final VolumeInfo vol = mVolumes.valueAt(i);
332                if (vol.path != null && path.startsWith(vol.path)) {
333                    return vol.id;
334                }
335            }
336        }
337        throw new IllegalArgumentException("No volume found for path " + path);
338    }
339
340    private VolumeMetadata findOrCreateMetadataLocked(VolumeInfo vol) {
341        VolumeMetadata meta = mMetadata.get(vol.fsUuid);
342        if (meta == null) {
343            meta = new VolumeMetadata(vol.type, vol.fsUuid);
344            mMetadata.put(meta.fsUuid, meta);
345        }
346        return meta;
347    }
348
349    private static int sNextMtpIndex = 1;
350
351    private static int allocateMtpIndex(String volId) {
352        if (VolumeInfo.ID_EMULATED_INTERNAL.equals(volId)) {
353            return 0;
354        } else {
355            return sNextMtpIndex++;
356        }
357    }
358
359    /** List of crypto types.
360      * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
361      * corresponding commands in CommandListener.cpp */
362    public static final String[] CRYPTO_TYPES
363        = { "password", "default", "pattern", "pin" };
364
365    private final Context mContext;
366    private final NativeDaemonConnector mConnector;
367
368    private volatile boolean mSystemReady = false;
369    private volatile boolean mDaemonConnected = false;
370
371    private PackageManagerService mPms;
372
373    private final Callbacks mCallbacks;
374
375    private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
376    private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
377
378    private final Object mUnmountLock = new Object();
379    @GuardedBy("mUnmountLock")
380    private CountDownLatch mUnmountSignal;
381
382    /**
383     * Private hash of currently mounted secure containers.
384     * Used as a lock in methods to manipulate secure containers.
385     */
386    final private HashSet<String> mAsecMountSet = new HashSet<String>();
387
388    /**
389     * The size of the crypto algorithm key in bits for OBB files. Currently
390     * Twofish is used which takes 128-bit keys.
391     */
392    private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128;
393
394    /**
395     * The number of times to run SHA1 in the PBKDF2 function for OBB files.
396     * 1024 is reasonably secure and not too slow.
397     */
398    private static final int PBKDF2_HASH_ROUNDS = 1024;
399
400    /**
401     * Mounted OBB tracking information. Used to track the current state of all
402     * OBBs.
403     */
404    final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
405
406    /** Map from raw paths to {@link ObbState}. */
407    final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
408
409    class ObbState implements IBinder.DeathRecipient {
410        public ObbState(String rawPath, String canonicalPath, int callingUid,
411                IObbActionListener token, int nonce) {
412            this.rawPath = rawPath;
413            this.canonicalPath = canonicalPath.toString();
414
415            final int userId = UserHandle.getUserId(callingUid);
416            this.ownerPath = buildObbPath(canonicalPath, userId, false);
417            this.voldPath = buildObbPath(canonicalPath, userId, true);
418
419            this.ownerGid = UserHandle.getSharedAppGid(callingUid);
420            this.token = token;
421            this.nonce = nonce;
422        }
423
424        final String rawPath;
425        final String canonicalPath;
426        final String ownerPath;
427        final String voldPath;
428
429        final int ownerGid;
430
431        // Token of remote Binder caller
432        final IObbActionListener token;
433
434        // Identifier to pass back to the token
435        final int nonce;
436
437        public IBinder getBinder() {
438            return token.asBinder();
439        }
440
441        @Override
442        public void binderDied() {
443            ObbAction action = new UnmountObbAction(this, true);
444            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
445        }
446
447        public void link() throws RemoteException {
448            getBinder().linkToDeath(this, 0);
449        }
450
451        public void unlink() {
452            getBinder().unlinkToDeath(this, 0);
453        }
454
455        @Override
456        public String toString() {
457            StringBuilder sb = new StringBuilder("ObbState{");
458            sb.append("rawPath=").append(rawPath);
459            sb.append(",canonicalPath=").append(canonicalPath);
460            sb.append(",ownerPath=").append(ownerPath);
461            sb.append(",voldPath=").append(voldPath);
462            sb.append(",ownerGid=").append(ownerGid);
463            sb.append(",token=").append(token);
464            sb.append(",binder=").append(getBinder());
465            sb.append('}');
466            return sb.toString();
467        }
468    }
469
470    // OBB Action Handler
471    final private ObbActionHandler mObbActionHandler;
472
473    // OBB action handler messages
474    private static final int OBB_RUN_ACTION = 1;
475    private static final int OBB_MCS_BOUND = 2;
476    private static final int OBB_MCS_UNBIND = 3;
477    private static final int OBB_MCS_RECONNECT = 4;
478    private static final int OBB_FLUSH_MOUNT_STATE = 5;
479
480    /*
481     * Default Container Service information
482     */
483    static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
484            "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService");
485
486    final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
487
488    class DefaultContainerConnection implements ServiceConnection {
489        @Override
490        public void onServiceConnected(ComponentName name, IBinder service) {
491            if (DEBUG_OBB)
492                Slog.i(TAG, "onServiceConnected");
493            IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
494            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
495        }
496
497        @Override
498        public void onServiceDisconnected(ComponentName name) {
499            if (DEBUG_OBB)
500                Slog.i(TAG, "onServiceDisconnected");
501        }
502    };
503
504    // Used in the ObbActionHandler
505    private IMediaContainerService mContainerService = null;
506
507    // Last fstrim operation tracking
508    private static final String LAST_FSTRIM_FILE = "last-fstrim";
509    private final File mLastMaintenanceFile;
510    private long mLastMaintenance;
511
512    // Handler messages
513    private static final int H_SYSTEM_READY = 1;
514    private static final int H_DAEMON_CONNECTED = 2;
515    private static final int H_SHUTDOWN = 3;
516    private static final int H_FSTRIM = 4;
517    private static final int H_VOLUME_MOUNT = 5;
518    private static final int H_VOLUME_BROADCAST = 6;
519
520    class MountServiceHandler extends Handler {
521        public MountServiceHandler(Looper looper) {
522            super(looper);
523        }
524
525        @Override
526        public void handleMessage(Message msg) {
527            switch (msg.what) {
528                case H_SYSTEM_READY: {
529                    handleSystemReady();
530                    break;
531                }
532                case H_DAEMON_CONNECTED: {
533                    handleDaemonConnected();
534                    break;
535                }
536                case H_FSTRIM: {
537                    if (!isReady()) {
538                        Slog.i(TAG, "fstrim requested, but no daemon connection yet; trying again");
539                        sendMessageDelayed(obtainMessage(H_FSTRIM), DateUtils.SECOND_IN_MILLIS);
540                    }
541
542                    Slog.i(TAG, "Running fstrim idle maintenance");
543
544                    // Remember when we kicked it off
545                    try {
546                        mLastMaintenance = System.currentTimeMillis();
547                        mLastMaintenanceFile.setLastModified(mLastMaintenance);
548                    } catch (Exception e) {
549                        Slog.e(TAG, "Unable to record last fstrim!");
550                    }
551
552                    try {
553                        // This method must be run on the main (handler) thread,
554                        // so it is safe to directly call into vold.
555                        mConnector.execute("fstrim", "dotrim");
556                        EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
557                    } catch (NativeDaemonConnectorException ndce) {
558                        Slog.e(TAG, "Failed to run fstrim!");
559                    }
560
561                    // invoke the completion callback, if any
562                    Runnable callback = (Runnable) msg.obj;
563                    if (callback != null) {
564                        callback.run();
565                    }
566                    break;
567                }
568                case H_SHUTDOWN: {
569                    final IMountShutdownObserver obs = (IMountShutdownObserver) msg.obj;
570                    boolean success = false;
571                    try {
572                        success = mConnector.execute("volume", "shutdown").isClassOk();
573                    } catch (NativeDaemonConnectorException ignored) {
574                    }
575                    if (obs != null) {
576                        try {
577                            obs.onShutDownComplete(success ? 0 : -1);
578                        } catch (RemoteException ignored) {
579                        }
580                    }
581                    break;
582                }
583                case H_VOLUME_MOUNT: {
584                    final VolumeInfo vol = (VolumeInfo) msg.obj;
585                    try {
586                        mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
587                                vol.mountUserId);
588                    } catch (NativeDaemonConnectorException ignored) {
589                    }
590                    break;
591                }
592                case H_VOLUME_BROADCAST: {
593                    final StorageVolume userVol = (StorageVolume) msg.obj;
594                    final String envState = userVol.getState();
595                    Slog.d(TAG, "Volume " + userVol.getId() + " broadcasting " + envState + " to "
596                            + userVol.getOwner());
597
598                    final String action = VolumeInfo.getBroadcastForEnvironment(envState);
599                    if (action != null) {
600                        final Intent intent = new Intent(action,
601                                Uri.fromFile(userVol.getPathFile()));
602                        intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol);
603                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
604                        mContext.sendBroadcastAsUser(intent, userVol.getOwner());
605                    }
606                    break;
607                }
608            }
609        }
610    }
611
612    private final Handler mHandler;
613
614    @Override
615    public void waitForAsecScan() {
616        waitForLatch(mAsecsScanned, "mAsecsScanned");
617    }
618
619    private void waitForReady() {
620        waitForLatch(mConnectedSignal, "mConnectedSignal");
621    }
622
623    private void waitForLatch(CountDownLatch latch, String condition) {
624        for (;;) {
625            try {
626                if (latch.await(5000, TimeUnit.MILLISECONDS)) {
627                    return;
628                } else {
629                    Slog.w(TAG, "Thread " + Thread.currentThread().getName()
630                            + " still waiting for " + condition + "...");
631                }
632            } catch (InterruptedException e) {
633                Slog.w(TAG, "Interrupt while waiting for MountService to be ready.");
634            }
635        }
636    }
637
638    private boolean isReady() {
639        try {
640            return mConnectedSignal.await(0, TimeUnit.MILLISECONDS);
641        } catch (InterruptedException e) {
642            return false;
643        }
644    }
645
646    private void handleSystemReady() {
647        resetIfReadyAndConnected();
648
649        // Start scheduling nominally-daily fstrim operations
650        MountServiceIdler.scheduleIdlePass(mContext);
651    }
652
653    private void resetIfReadyAndConnected() {
654        Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
655                + ", mDaemonConnected=" + mDaemonConnected);
656        if (mSystemReady && mDaemonConnected) {
657            mDisks.clear();
658            mVolumes.clear();
659
660            // Create a stub volume that represents internal storage
661            final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
662                    VolumeInfo.TYPE_PRIVATE, null, 0);
663            internal.state = VolumeInfo.STATE_MOUNTED;
664            internal.path = Environment.getDataDirectory().getAbsolutePath();
665            mVolumes.put(internal.id, internal);
666
667            try {
668                mConnector.execute("volume", "reset");
669            } catch (NativeDaemonConnectorException e) {
670                Slog.w(TAG, "Failed to reset vold", e);
671            }
672        }
673    }
674
675    private void onStartUser(int userId) {
676        Slog.d(TAG, "onStartUser " + userId);
677
678        // We purposefully block here to make sure that user-specific
679        // staging area is ready so it's ready for zygote-forked apps to
680        // bind mount against.
681        try {
682            mConnector.execute("volume", "start_user", userId);
683        } catch (NativeDaemonConnectorException ignored) {
684        }
685
686        // Record user as started so newly mounted volumes kick off events
687        // correctly, then synthesize events for any already-mounted volumes.
688        synchronized (mVolumes) {
689            for (int i = 0; i < mVolumes.size(); i++) {
690                final VolumeInfo vol = mVolumes.valueAt(i);
691                if (vol.isVisibleToUser(userId) && vol.state == VolumeInfo.STATE_MOUNTED) {
692                    final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
693                    mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
694
695                    mCallbacks.notifyStorageStateChanged(userVol.getPath(),
696                            Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED);
697                }
698            }
699            mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userId);
700        }
701    }
702
703    private void onCleanupUser(int userId) {
704        Slog.d(TAG, "onCleanupUser " + userId);
705
706        try {
707            mConnector.execute("volume", "cleanup_user", userId);
708        } catch (NativeDaemonConnectorException ignored) {
709        }
710
711        synchronized (mVolumes) {
712            mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userId);
713        }
714    }
715
716    void runIdleMaintenance(Runnable callback) {
717        mHandler.sendMessage(mHandler.obtainMessage(H_FSTRIM, callback));
718    }
719
720    // Binder entry point for kicking off an immediate fstrim
721    @Override
722    public void runMaintenance() {
723        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
724        runIdleMaintenance(null);
725    }
726
727    @Override
728    public long lastMaintenance() {
729        return mLastMaintenance;
730    }
731
732    /**
733     * Callback from NativeDaemonConnector
734     */
735    @Override
736    public void onDaemonConnected() {
737        mDaemonConnected = true;
738        mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget();
739    }
740
741    private void handleDaemonConnected() {
742        resetIfReadyAndConnected();
743
744        /*
745         * Now that we've done our initialization, release
746         * the hounds!
747         */
748        mConnectedSignal.countDown();
749
750        // On an encrypted device we can't see system properties yet, so pull
751        // the system locale out of the mount service.
752        if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
753            copyLocaleFromMountService();
754        }
755
756        // Let package manager load internal ASECs.
757        mPms.scanAvailableAsecs();
758
759        // Notify people waiting for ASECs to be scanned that it's done.
760        mAsecsScanned.countDown();
761    }
762
763    private void copyLocaleFromMountService() {
764        String systemLocale;
765        try {
766            systemLocale = getField(StorageManager.SYSTEM_LOCALE_KEY);
767        } catch (RemoteException e) {
768            return;
769        }
770        if (TextUtils.isEmpty(systemLocale)) {
771            return;
772        }
773
774        Slog.d(TAG, "Got locale " + systemLocale + " from mount service");
775        Locale locale = Locale.forLanguageTag(systemLocale);
776        Configuration config = new Configuration();
777        config.setLocale(locale);
778        try {
779            ActivityManagerNative.getDefault().updateConfiguration(config);
780        } catch (RemoteException e) {
781            Slog.e(TAG, "Error setting system locale from mount service", e);
782        }
783
784        // Temporary workaround for http://b/17945169.
785        Slog.d(TAG, "Setting system properties to " + systemLocale + " from mount service");
786        SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
787    }
788
789    /**
790     * Callback from NativeDaemonConnector
791     */
792    @Override
793    public boolean onCheckHoldWakeLock(int code) {
794        return false;
795    }
796
797    /**
798     * Callback from NativeDaemonConnector
799     */
800    @Override
801    public boolean onEvent(int code, String raw, String[] cooked) {
802        synchronized (mLock) {
803            return onEventLocked(code, raw, cooked);
804        }
805    }
806
807    private boolean onEventLocked(int code, String raw, String[] cooked) {
808        switch (code) {
809            case VoldResponseCode.DISK_CREATED: {
810                if (cooked.length != 3) break;
811                final String id = cooked[1];
812                final int flags = Integer.parseInt(cooked[2]);
813                mDisks.put(id, new DiskInfo(id, flags));
814                break;
815            }
816            case VoldResponseCode.DISK_SIZE_CHANGED: {
817                if (cooked.length != 3) break;
818                final DiskInfo disk = mDisks.get(cooked[1]);
819                if (disk != null) {
820                    disk.size = Long.parseLong(cooked[2]);
821                }
822                break;
823            }
824            case VoldResponseCode.DISK_LABEL_CHANGED: {
825                if (cooked.length != 3) break;
826                final DiskInfo disk = mDisks.get(cooked[1]);
827                if (disk != null) {
828                    disk.label = cooked[2];
829                }
830                if (disk.label != null) {
831                    disk.label = disk.label.trim();
832                }
833                break;
834            }
835            case VoldResponseCode.DISK_UNSUPPORTED: {
836                if (cooked.length != 2) break;
837                final DiskInfo disk = mDisks.get(cooked[1]);
838                mCallbacks.notifyDiskUnsupported(disk);
839                break;
840            }
841            case VoldResponseCode.DISK_DESTROYED: {
842                if (cooked.length != 2) break;
843                mDisks.remove(cooked[1]);
844                break;
845            }
846
847            case VoldResponseCode.VOLUME_CREATED: {
848                final String id = cooked[1];
849                final int type = Integer.parseInt(cooked[2]);
850                final String diskId = (cooked.length == 4) ? cooked[3] : null;
851                final int mtpIndex = allocateMtpIndex(id);
852                final VolumeInfo vol = new VolumeInfo(id, type, diskId, mtpIndex);
853                mVolumes.put(id, vol);
854                onVolumeCreatedLocked(vol);
855                break;
856            }
857            case VoldResponseCode.VOLUME_STATE_CHANGED: {
858                if (cooked.length != 3) break;
859                final VolumeInfo vol = mVolumes.get(cooked[1]);
860                if (vol != null) {
861                    final int oldState = vol.state;
862                    final int newState = Integer.parseInt(cooked[2]);
863                    vol.state = newState;
864                    onVolumeStateChangedLocked(vol, oldState, newState);
865                }
866                break;
867            }
868            case VoldResponseCode.VOLUME_FS_TYPE_CHANGED: {
869                if (cooked.length != 3) break;
870                final VolumeInfo vol = mVolumes.get(cooked[1]);
871                if (vol != null) {
872                    vol.fsType = cooked[2];
873                }
874                mCallbacks.notifyVolumeMetadataChanged(vol.clone());
875                break;
876            }
877            case VoldResponseCode.VOLUME_FS_UUID_CHANGED: {
878                if (cooked.length != 3) break;
879                final VolumeInfo vol = mVolumes.get(cooked[1]);
880                if (vol != null) {
881                    vol.fsUuid = cooked[2];
882                }
883                refreshMetadataLocked();
884                mCallbacks.notifyVolumeMetadataChanged(vol.clone());
885                break;
886            }
887            case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
888                if (cooked.length != 3) break;
889                final VolumeInfo vol = mVolumes.get(cooked[1]);
890                if (vol != null) {
891                    vol.fsLabel = cooked[2];
892                }
893                mCallbacks.notifyVolumeMetadataChanged(vol.clone());
894                break;
895            }
896            case VoldResponseCode.VOLUME_PATH_CHANGED: {
897                if (cooked.length != 3) break;
898                final VolumeInfo vol = mVolumes.get(cooked[1]);
899                if (vol != null) {
900                    vol.path = cooked[2];
901                }
902                break;
903            }
904            case VoldResponseCode.VOLUME_DESTROYED: {
905                if (cooked.length != 2) break;
906                mVolumes.remove(cooked[1]);
907                break;
908            }
909
910            case VoldResponseCode.FstrimCompleted: {
911                EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
912                break;
913            }
914            default: {
915                Slog.d(TAG, "Unhandled vold event " + code);
916            }
917        }
918
919        return true;
920    }
921
922    private void onVolumeCreatedLocked(VolumeInfo vol) {
923        final boolean primaryPhysical = SystemProperties.getBoolean(
924                StorageManager.PROP_PRIMARY_PHYSICAL, false);
925        // TODO: enable switching to another emulated primary
926        if (VolumeInfo.ID_EMULATED_INTERNAL.equals(vol.id) && !primaryPhysical) {
927            vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
928            vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
929            mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
930
931        } else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
932            if (primaryPhysical) {
933                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
934                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
935            }
936
937            // Adoptable public disks are visible to apps, since they meet
938            // public API requirement of being in a stable location.
939            final DiskInfo disk = mDisks.get(vol.getDiskId());
940            if (disk != null && disk.isAdoptable()) {
941                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
942            }
943
944            vol.mountUserId = UserHandle.USER_OWNER;
945            mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
946
947        } else if (vol.type == VolumeInfo.TYPE_PRIVATE) {
948            mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
949
950        } else {
951            Slog.d(TAG, "Skipping automatic mounting of " + vol);
952        }
953    }
954
955    private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
956        mCallbacks.notifyVolumeStateChanged(vol.clone(), oldState, newState);
957
958        final String oldStateEnv = VolumeInfo.getEnvironmentForState(oldState);
959        final String newStateEnv = VolumeInfo.getEnvironmentForState(newState);
960
961        if (!Objects.equals(oldStateEnv, newStateEnv)) {
962            // Kick state changed event towards all started users. Any users
963            // started after this point will trigger additional
964            // user-specific broadcasts.
965            for (int userId : mStartedUsers) {
966                if (vol.isVisibleToUser(userId)) {
967                    final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
968                    mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
969
970                    mCallbacks.notifyStorageStateChanged(userVol.getPath(), oldStateEnv,
971                            newStateEnv);
972                }
973            }
974        }
975
976        if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.state == VolumeInfo.STATE_EJECTING) {
977            // TODO: this should eventually be handled by new ObbVolume state changes
978            /*
979             * Some OBBs might have been unmounted when this volume was
980             * unmounted, so send a message to the handler to let it know to
981             * remove those from the list of mounted OBBS.
982             */
983            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
984                    OBB_FLUSH_MOUNT_STATE, vol.path));
985        }
986    }
987
988    /**
989     * Refresh latest metadata into any currently active {@link VolumeInfo}.
990     */
991    private void refreshMetadataLocked() {
992        final int size = mVolumes.size();
993        for (int i = 0; i < size; i++) {
994            final VolumeInfo vol = mVolumes.valueAt(i);
995            final VolumeMetadata meta = mMetadata.get(vol.fsUuid);
996
997            if (meta != null) {
998                vol.nickname = meta.nickname;
999                vol.userFlags = meta.userFlags;
1000            } else {
1001                vol.nickname = null;
1002                vol.userFlags = 0;
1003            }
1004        }
1005    }
1006
1007    private void enforcePermission(String perm) {
1008        mContext.enforceCallingOrSelfPermission(perm, perm);
1009    }
1010
1011    private void enforceUserRestriction(String restriction) {
1012        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
1013        if (um.hasUserRestriction(restriction, Binder.getCallingUserHandle())) {
1014            throw new SecurityException("User has restriction " + restriction);
1015        }
1016    }
1017
1018    /**
1019     * Constructs a new MountService instance
1020     *
1021     * @param context  Binder context for this service
1022     */
1023    public MountService(Context context) {
1024        sSelf = this;
1025
1026        mContext = context;
1027        mCallbacks = new Callbacks(FgThread.get().getLooper());
1028
1029        // XXX: This will go away soon in favor of IMountServiceObserver
1030        mPms = (PackageManagerService) ServiceManager.getService("package");
1031
1032        HandlerThread hthread = new HandlerThread(TAG);
1033        hthread.start();
1034        mHandler = new MountServiceHandler(hthread.getLooper());
1035
1036        // Add OBB Action Handler to MountService thread.
1037        mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
1038
1039        // Initialize the last-fstrim tracking if necessary
1040        File dataDir = Environment.getDataDirectory();
1041        File systemDir = new File(dataDir, "system");
1042        mLastMaintenanceFile = new File(systemDir, LAST_FSTRIM_FILE);
1043        if (!mLastMaintenanceFile.exists()) {
1044            // Not setting mLastMaintenance here means that we will force an
1045            // fstrim during reboot following the OTA that installs this code.
1046            try {
1047                (new FileOutputStream(mLastMaintenanceFile)).close();
1048            } catch (IOException e) {
1049                Slog.e(TAG, "Unable to create fstrim record " + mLastMaintenanceFile.getPath());
1050            }
1051        } else {
1052            mLastMaintenance = mLastMaintenanceFile.lastModified();
1053        }
1054
1055        mMetadataFile = new AtomicFile(
1056                new File(Environment.getSystemSecureDirectory(), "storage.xml"));
1057
1058        synchronized (mLock) {
1059            readMetadataLocked();
1060        }
1061
1062        /*
1063         * Create the connection to vold with a maximum queue of twice the
1064         * amount of containers we'd ever expect to have. This keeps an
1065         * "asec list" from blocking a thread repeatedly.
1066         */
1067        mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
1068                null);
1069        mConnector.setDebug(true);
1070
1071        Thread thread = new Thread(mConnector, VOLD_TAG);
1072        thread.start();
1073
1074        // Add ourself to the Watchdog monitors if enabled.
1075        if (WATCHDOG_ENABLE) {
1076            Watchdog.getInstance().addMonitor(this);
1077        }
1078    }
1079
1080    private void systemReady() {
1081        mSystemReady = true;
1082        mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
1083    }
1084
1085    private void readMetadataLocked() {
1086        mMetadata.clear();
1087
1088        FileInputStream fis = null;
1089        try {
1090            fis = mMetadataFile.openRead();
1091            final XmlPullParser in = Xml.newPullParser();
1092            in.setInput(fis, null);
1093
1094            int type;
1095            while ((type = in.next()) != END_DOCUMENT) {
1096                if (type == START_TAG) {
1097                    final String tag = in.getName();
1098                    if (TAG_VOLUME.equals(tag)) {
1099                        final VolumeMetadata meta = VolumeMetadata.read(in);
1100                        mMetadata.put(meta.fsUuid, meta);
1101                    }
1102                }
1103            }
1104        } catch (FileNotFoundException e) {
1105            // Missing metadata is okay, probably first boot
1106        } catch (IOException e) {
1107            Slog.wtf(TAG, "Failed reading metadata", e);
1108        } catch (XmlPullParserException e) {
1109            Slog.wtf(TAG, "Failed reading metadata", e);
1110        } finally {
1111            IoUtils.closeQuietly(fis);
1112        }
1113    }
1114
1115    private void writeMetadataLocked() {
1116        FileOutputStream fos = null;
1117        try {
1118            fos = mMetadataFile.startWrite();
1119
1120            XmlSerializer out = new FastXmlSerializer();
1121            out.setOutput(fos, "utf-8");
1122            out.startDocument(null, true);
1123            out.startTag(null, TAG_VOLUMES);
1124            final int size = mMetadata.size();
1125            for (int i = 0; i < size; i++) {
1126                final VolumeMetadata meta = mMetadata.valueAt(i);
1127                VolumeMetadata.write(out, meta);
1128            }
1129            out.endTag(null, TAG_VOLUMES);
1130            out.endDocument();
1131
1132            mMetadataFile.finishWrite(fos);
1133        } catch (IOException e) {
1134            if (fos != null) {
1135                mMetadataFile.failWrite(fos);
1136            }
1137        }
1138    }
1139
1140    /**
1141     * Exposed API calls below here
1142     */
1143
1144    @Override
1145    public void registerListener(IMountServiceListener listener) {
1146        mCallbacks.register(listener);
1147    }
1148
1149    @Override
1150    public void unregisterListener(IMountServiceListener listener) {
1151        mCallbacks.unregister(listener);
1152    }
1153
1154    @Override
1155    public void shutdown(final IMountShutdownObserver observer) {
1156        enforcePermission(android.Manifest.permission.SHUTDOWN);
1157
1158        Slog.i(TAG, "Shutting down");
1159        mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget();
1160    }
1161
1162    @Override
1163    public boolean isUsbMassStorageConnected() {
1164        throw new UnsupportedOperationException();
1165    }
1166
1167    @Override
1168    public void setUsbMassStorageEnabled(boolean enable) {
1169        throw new UnsupportedOperationException();
1170    }
1171
1172    @Override
1173    public boolean isUsbMassStorageEnabled() {
1174        throw new UnsupportedOperationException();
1175    }
1176
1177    @Override
1178    public String getVolumeState(String mountPoint) {
1179        throw new UnsupportedOperationException();
1180    }
1181
1182    @Override
1183    public boolean isExternalStorageEmulated() {
1184        throw new UnsupportedOperationException();
1185    }
1186
1187    @Override
1188    public int mountVolume(String path) {
1189        mount(findVolumeIdForPath(path));
1190        return 0;
1191    }
1192
1193    @Override
1194    public void unmountVolume(String path, boolean force, boolean removeEncryption) {
1195        unmount(findVolumeIdForPath(path));
1196    }
1197
1198    @Override
1199    public int formatVolume(String path) {
1200        format(findVolumeIdForPath(path));
1201        return 0;
1202    }
1203
1204    @Override
1205    public void mount(String volId) {
1206        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1207        waitForReady();
1208
1209        final VolumeInfo vol = findVolumeById(volId);
1210        if (vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_PRIVATE) {
1211            enforceUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
1212        }
1213        try {
1214            mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
1215        } catch (NativeDaemonConnectorException e) {
1216            throw e.rethrowAsParcelableException();
1217        }
1218    }
1219
1220    @Override
1221    public void unmount(String volId) {
1222        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1223        waitForReady();
1224
1225        final VolumeInfo vol = findVolumeById(volId);
1226
1227        // TODO: expand PMS to know about multiple volumes
1228        if (vol.isPrimary()) {
1229            synchronized (mUnmountLock) {
1230                mUnmountSignal = new CountDownLatch(1);
1231                mPms.updateExternalMediaStatus(false, true);
1232                waitForLatch(mUnmountSignal, "mUnmountSignal");
1233                mUnmountSignal = null;
1234            }
1235        }
1236
1237        try {
1238            mConnector.execute("volume", "unmount", vol.id);
1239        } catch (NativeDaemonConnectorException e) {
1240            throw e.rethrowAsParcelableException();
1241        }
1242    }
1243
1244    @Override
1245    public void format(String volId) {
1246        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
1247        waitForReady();
1248
1249        final VolumeInfo vol = findVolumeById(volId);
1250        try {
1251            mConnector.execute("volume", "format", vol.id);
1252        } catch (NativeDaemonConnectorException e) {
1253            throw e.rethrowAsParcelableException();
1254        }
1255    }
1256
1257    @Override
1258    public void partitionPublic(String diskId) {
1259        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
1260        waitForReady();
1261
1262        try {
1263            mConnector.execute("volume", "partition", diskId, "public");
1264        } catch (NativeDaemonConnectorException e) {
1265            throw e.rethrowAsParcelableException();
1266        }
1267    }
1268
1269    @Override
1270    public void partitionPrivate(String diskId) {
1271        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
1272        waitForReady();
1273
1274        try {
1275            mConnector.execute("volume", "partition", diskId, "private");
1276        } catch (NativeDaemonConnectorException e) {
1277            throw e.rethrowAsParcelableException();
1278        }
1279    }
1280
1281    @Override
1282    public void partitionMixed(String diskId, int ratio) {
1283        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
1284        waitForReady();
1285
1286        try {
1287            mConnector.execute("volume", "partition", diskId, "mixed", ratio);
1288        } catch (NativeDaemonConnectorException e) {
1289            throw e.rethrowAsParcelableException();
1290        }
1291    }
1292
1293    @Override
1294    public void setVolumeNickname(String volId, String nickname) {
1295        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1296        waitForReady();
1297
1298        synchronized (mLock) {
1299            final VolumeInfo vol = findVolumeById(volId);
1300            final VolumeMetadata meta = findOrCreateMetadataLocked(vol);
1301            meta.nickname = nickname;
1302            refreshMetadataLocked();
1303            writeMetadataLocked();
1304            mCallbacks.notifyVolumeMetadataChanged(vol.clone());
1305        }
1306    }
1307
1308    @Override
1309    public void setVolumeUserFlags(String volId, int flags, int mask) {
1310        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1311        waitForReady();
1312
1313        synchronized (mLock) {
1314            final VolumeInfo vol = findVolumeById(volId);
1315            final VolumeMetadata meta = findOrCreateMetadataLocked(vol);
1316            meta.userFlags = (meta.userFlags & ~mask) | (flags & mask);
1317            refreshMetadataLocked();
1318            writeMetadataLocked();
1319            mCallbacks.notifyVolumeMetadataChanged(vol.clone());
1320        }
1321    }
1322
1323    @Override
1324    public int[] getStorageUsers(String path) {
1325        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1326        waitForReady();
1327        try {
1328            final String[] r = NativeDaemonEvent.filterMessageList(
1329                    mConnector.executeForList("storage", "users", path),
1330                    VoldResponseCode.StorageUsersListResult);
1331
1332            // FMT: <pid> <process name>
1333            int[] data = new int[r.length];
1334            for (int i = 0; i < r.length; i++) {
1335                String[] tok = r[i].split(" ");
1336                try {
1337                    data[i] = Integer.parseInt(tok[0]);
1338                } catch (NumberFormatException nfe) {
1339                    Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
1340                    return new int[0];
1341                }
1342            }
1343            return data;
1344        } catch (NativeDaemonConnectorException e) {
1345            Slog.e(TAG, "Failed to retrieve storage users list", e);
1346            return new int[0];
1347        }
1348    }
1349
1350    private void warnOnNotMounted() {
1351        synchronized (mLock) {
1352            for (int i = 0; i < mVolumes.size(); i++) {
1353                final VolumeInfo vol = mVolumes.valueAt(i);
1354                if (vol.isPrimary() && vol.state == VolumeInfo.STATE_MOUNTED) {
1355                    // Cool beans, we have a mounted primary volume
1356                    return;
1357                }
1358            }
1359        }
1360
1361        Slog.w(TAG, "No primary storage mounted!");
1362    }
1363
1364    public String[] getSecureContainerList() {
1365        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
1366        waitForReady();
1367        warnOnNotMounted();
1368
1369        try {
1370            return NativeDaemonEvent.filterMessageList(
1371                    mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
1372        } catch (NativeDaemonConnectorException e) {
1373            return new String[0];
1374        }
1375    }
1376
1377    public int createSecureContainer(String id, int sizeMb, String fstype, String key,
1378            int ownerUid, boolean external) {
1379        enforcePermission(android.Manifest.permission.ASEC_CREATE);
1380        waitForReady();
1381        warnOnNotMounted();
1382
1383        int rc = StorageResultCode.OperationSucceeded;
1384        try {
1385            mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key),
1386                    ownerUid, external ? "1" : "0");
1387        } catch (NativeDaemonConnectorException e) {
1388            rc = StorageResultCode.OperationFailedInternalError;
1389        }
1390
1391        if (rc == StorageResultCode.OperationSucceeded) {
1392            synchronized (mAsecMountSet) {
1393                mAsecMountSet.add(id);
1394            }
1395        }
1396        return rc;
1397    }
1398
1399    @Override
1400    public int resizeSecureContainer(String id, int sizeMb, String key) {
1401        enforcePermission(android.Manifest.permission.ASEC_CREATE);
1402        waitForReady();
1403        warnOnNotMounted();
1404
1405        int rc = StorageResultCode.OperationSucceeded;
1406        try {
1407            mConnector.execute("asec", "resize", id, sizeMb, new SensitiveArg(key));
1408        } catch (NativeDaemonConnectorException e) {
1409            rc = StorageResultCode.OperationFailedInternalError;
1410        }
1411        return rc;
1412    }
1413
1414    public int finalizeSecureContainer(String id) {
1415        enforcePermission(android.Manifest.permission.ASEC_CREATE);
1416        warnOnNotMounted();
1417
1418        int rc = StorageResultCode.OperationSucceeded;
1419        try {
1420            mConnector.execute("asec", "finalize", id);
1421            /*
1422             * Finalization does a remount, so no need
1423             * to update mAsecMountSet
1424             */
1425        } catch (NativeDaemonConnectorException e) {
1426            rc = StorageResultCode.OperationFailedInternalError;
1427        }
1428        return rc;
1429    }
1430
1431    public int fixPermissionsSecureContainer(String id, int gid, String filename) {
1432        enforcePermission(android.Manifest.permission.ASEC_CREATE);
1433        warnOnNotMounted();
1434
1435        int rc = StorageResultCode.OperationSucceeded;
1436        try {
1437            mConnector.execute("asec", "fixperms", id, gid, filename);
1438            /*
1439             * Fix permissions does a remount, so no need to update
1440             * mAsecMountSet
1441             */
1442        } catch (NativeDaemonConnectorException e) {
1443            rc = StorageResultCode.OperationFailedInternalError;
1444        }
1445        return rc;
1446    }
1447
1448    public int destroySecureContainer(String id, boolean force) {
1449        enforcePermission(android.Manifest.permission.ASEC_DESTROY);
1450        waitForReady();
1451        warnOnNotMounted();
1452
1453        /*
1454         * Force a GC to make sure AssetManagers in other threads of the
1455         * system_server are cleaned up. We have to do this since AssetManager
1456         * instances are kept as a WeakReference and it's possible we have files
1457         * open on the external storage.
1458         */
1459        Runtime.getRuntime().gc();
1460
1461        int rc = StorageResultCode.OperationSucceeded;
1462        try {
1463            final Command cmd = new Command("asec", "destroy", id);
1464            if (force) {
1465                cmd.appendArg("force");
1466            }
1467            mConnector.execute(cmd);
1468        } catch (NativeDaemonConnectorException e) {
1469            int code = e.getCode();
1470            if (code == VoldResponseCode.OpFailedStorageBusy) {
1471                rc = StorageResultCode.OperationFailedStorageBusy;
1472            } else {
1473                rc = StorageResultCode.OperationFailedInternalError;
1474            }
1475        }
1476
1477        if (rc == StorageResultCode.OperationSucceeded) {
1478            synchronized (mAsecMountSet) {
1479                if (mAsecMountSet.contains(id)) {
1480                    mAsecMountSet.remove(id);
1481                }
1482            }
1483        }
1484
1485        return rc;
1486    }
1487
1488    public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) {
1489        enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1490        waitForReady();
1491        warnOnNotMounted();
1492
1493        synchronized (mAsecMountSet) {
1494            if (mAsecMountSet.contains(id)) {
1495                return StorageResultCode.OperationFailedStorageMounted;
1496            }
1497        }
1498
1499        int rc = StorageResultCode.OperationSucceeded;
1500        try {
1501            mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid,
1502                    readOnly ? "ro" : "rw");
1503        } catch (NativeDaemonConnectorException e) {
1504            int code = e.getCode();
1505            if (code != VoldResponseCode.OpFailedStorageBusy) {
1506                rc = StorageResultCode.OperationFailedInternalError;
1507            }
1508        }
1509
1510        if (rc == StorageResultCode.OperationSucceeded) {
1511            synchronized (mAsecMountSet) {
1512                mAsecMountSet.add(id);
1513            }
1514        }
1515        return rc;
1516    }
1517
1518    public int unmountSecureContainer(String id, boolean force) {
1519        enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1520        waitForReady();
1521        warnOnNotMounted();
1522
1523        synchronized (mAsecMountSet) {
1524            if (!mAsecMountSet.contains(id)) {
1525                return StorageResultCode.OperationFailedStorageNotMounted;
1526            }
1527         }
1528
1529        /*
1530         * Force a GC to make sure AssetManagers in other threads of the
1531         * system_server are cleaned up. We have to do this since AssetManager
1532         * instances are kept as a WeakReference and it's possible we have files
1533         * open on the external storage.
1534         */
1535        Runtime.getRuntime().gc();
1536
1537        int rc = StorageResultCode.OperationSucceeded;
1538        try {
1539            final Command cmd = new Command("asec", "unmount", id);
1540            if (force) {
1541                cmd.appendArg("force");
1542            }
1543            mConnector.execute(cmd);
1544        } catch (NativeDaemonConnectorException e) {
1545            int code = e.getCode();
1546            if (code == VoldResponseCode.OpFailedStorageBusy) {
1547                rc = StorageResultCode.OperationFailedStorageBusy;
1548            } else {
1549                rc = StorageResultCode.OperationFailedInternalError;
1550            }
1551        }
1552
1553        if (rc == StorageResultCode.OperationSucceeded) {
1554            synchronized (mAsecMountSet) {
1555                mAsecMountSet.remove(id);
1556            }
1557        }
1558        return rc;
1559    }
1560
1561    public boolean isSecureContainerMounted(String id) {
1562        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
1563        waitForReady();
1564        warnOnNotMounted();
1565
1566        synchronized (mAsecMountSet) {
1567            return mAsecMountSet.contains(id);
1568        }
1569    }
1570
1571    public int renameSecureContainer(String oldId, String newId) {
1572        enforcePermission(android.Manifest.permission.ASEC_RENAME);
1573        waitForReady();
1574        warnOnNotMounted();
1575
1576        synchronized (mAsecMountSet) {
1577            /*
1578             * Because a mounted container has active internal state which cannot be
1579             * changed while active, we must ensure both ids are not currently mounted.
1580             */
1581            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
1582                return StorageResultCode.OperationFailedStorageMounted;
1583            }
1584        }
1585
1586        int rc = StorageResultCode.OperationSucceeded;
1587        try {
1588            mConnector.execute("asec", "rename", oldId, newId);
1589        } catch (NativeDaemonConnectorException e) {
1590            rc = StorageResultCode.OperationFailedInternalError;
1591        }
1592
1593        return rc;
1594    }
1595
1596    public String getSecureContainerPath(String id) {
1597        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
1598        waitForReady();
1599        warnOnNotMounted();
1600
1601        final NativeDaemonEvent event;
1602        try {
1603            event = mConnector.execute("asec", "path", id);
1604            event.checkCode(VoldResponseCode.AsecPathResult);
1605            return event.getMessage();
1606        } catch (NativeDaemonConnectorException e) {
1607            int code = e.getCode();
1608            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1609                Slog.i(TAG, String.format("Container '%s' not found", id));
1610                return null;
1611            } else {
1612                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1613            }
1614        }
1615    }
1616
1617    public String getSecureContainerFilesystemPath(String id) {
1618        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
1619        waitForReady();
1620        warnOnNotMounted();
1621
1622        final NativeDaemonEvent event;
1623        try {
1624            event = mConnector.execute("asec", "fspath", id);
1625            event.checkCode(VoldResponseCode.AsecPathResult);
1626            return event.getMessage();
1627        } catch (NativeDaemonConnectorException e) {
1628            int code = e.getCode();
1629            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1630                Slog.i(TAG, String.format("Container '%s' not found", id));
1631                return null;
1632            } else {
1633                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1634            }
1635        }
1636    }
1637
1638    @Override
1639    public void finishMediaUpdate() {
1640        if (mUnmountSignal != null) {
1641            mUnmountSignal.countDown();
1642        } else {
1643            Slog.w(TAG, "Odd, nobody asked to unmount?");
1644        }
1645    }
1646
1647    private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
1648        if (callerUid == android.os.Process.SYSTEM_UID) {
1649            return true;
1650        }
1651
1652        if (packageName == null) {
1653            return false;
1654        }
1655
1656        final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid));
1657
1658        if (DEBUG_OBB) {
1659            Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
1660                    packageUid + ", callerUid = " + callerUid);
1661        }
1662
1663        return callerUid == packageUid;
1664    }
1665
1666    public String getMountedObbPath(String rawPath) {
1667        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1668
1669        waitForReady();
1670        warnOnNotMounted();
1671
1672        final ObbState state;
1673        synchronized (mObbPathToStateMap) {
1674            state = mObbPathToStateMap.get(rawPath);
1675        }
1676        if (state == null) {
1677            Slog.w(TAG, "Failed to find OBB mounted at " + rawPath);
1678            return null;
1679        }
1680
1681        final NativeDaemonEvent event;
1682        try {
1683            event = mConnector.execute("obb", "path", state.voldPath);
1684            event.checkCode(VoldResponseCode.AsecPathResult);
1685            return event.getMessage();
1686        } catch (NativeDaemonConnectorException e) {
1687            int code = e.getCode();
1688            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1689                return null;
1690            } else {
1691                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1692            }
1693        }
1694    }
1695
1696    @Override
1697    public boolean isObbMounted(String rawPath) {
1698        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1699        synchronized (mObbMounts) {
1700            return mObbPathToStateMap.containsKey(rawPath);
1701        }
1702    }
1703
1704    @Override
1705    public void mountObb(
1706            String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) {
1707        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1708        Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null");
1709        Preconditions.checkNotNull(token, "token cannot be null");
1710
1711        final int callingUid = Binder.getCallingUid();
1712        final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce);
1713        final ObbAction action = new MountObbAction(obbState, key, callingUid);
1714        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1715
1716        if (DEBUG_OBB)
1717            Slog.i(TAG, "Send to OBB handler: " + action.toString());
1718    }
1719
1720    @Override
1721    public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) {
1722        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
1723
1724        final ObbState existingState;
1725        synchronized (mObbPathToStateMap) {
1726            existingState = mObbPathToStateMap.get(rawPath);
1727        }
1728
1729        if (existingState != null) {
1730            // TODO: separate state object from request data
1731            final int callingUid = Binder.getCallingUid();
1732            final ObbState newState = new ObbState(
1733                    rawPath, existingState.canonicalPath, callingUid, token, nonce);
1734            final ObbAction action = new UnmountObbAction(newState, force);
1735            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1736
1737            if (DEBUG_OBB)
1738                Slog.i(TAG, "Send to OBB handler: " + action.toString());
1739        } else {
1740            Slog.w(TAG, "Unknown OBB mount at " + rawPath);
1741        }
1742    }
1743
1744    @Override
1745    public int getEncryptionState() {
1746        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1747                "no permission to access the crypt keeper");
1748
1749        waitForReady();
1750
1751        final NativeDaemonEvent event;
1752        try {
1753            event = mConnector.execute("cryptfs", "cryptocomplete");
1754            return Integer.parseInt(event.getMessage());
1755        } catch (NumberFormatException e) {
1756            // Bad result - unexpected.
1757            Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
1758            return ENCRYPTION_STATE_ERROR_UNKNOWN;
1759        } catch (NativeDaemonConnectorException e) {
1760            // Something bad happened.
1761            Slog.w(TAG, "Error in communicating with cryptfs in validating");
1762            return ENCRYPTION_STATE_ERROR_UNKNOWN;
1763        }
1764    }
1765
1766    private static String toHex(String password) {
1767        if (password == null) {
1768            return "";
1769        }
1770        byte[] bytes = password.getBytes(StandardCharsets.UTF_8);
1771        return new String(HexEncoding.encode(bytes));
1772    }
1773
1774    private static String fromHex(String hexPassword) throws IllegalArgumentException {
1775        if (hexPassword == null) {
1776            return null;
1777        }
1778
1779        final byte[] bytes = HexEncoding.decode(hexPassword.toCharArray(), false);
1780        return new String(bytes, StandardCharsets.UTF_8);
1781    }
1782
1783    @Override
1784    public int decryptStorage(String password) {
1785        if (TextUtils.isEmpty(password)) {
1786            throw new IllegalArgumentException("password cannot be empty");
1787        }
1788
1789        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1790                "no permission to access the crypt keeper");
1791
1792        waitForReady();
1793
1794        if (DEBUG_EVENTS) {
1795            Slog.i(TAG, "decrypting storage...");
1796        }
1797
1798        final NativeDaemonEvent event;
1799        try {
1800            event = mConnector.execute("cryptfs", "checkpw", new SensitiveArg(toHex(password)));
1801
1802            final int code = Integer.parseInt(event.getMessage());
1803            if (code == 0) {
1804                // Decrypt was successful. Post a delayed message before restarting in order
1805                // to let the UI to clear itself
1806                mHandler.postDelayed(new Runnable() {
1807                    public void run() {
1808                        try {
1809                            mConnector.execute("cryptfs", "restart");
1810                        } catch (NativeDaemonConnectorException e) {
1811                            Slog.e(TAG, "problem executing in background", e);
1812                        }
1813                    }
1814                }, 1000); // 1 second
1815            }
1816
1817            return code;
1818        } catch (NativeDaemonConnectorException e) {
1819            // Decryption failed
1820            return e.getCode();
1821        }
1822    }
1823
1824    public int encryptStorage(int type, String password) {
1825        if (TextUtils.isEmpty(password) && type != StorageManager.CRYPT_TYPE_DEFAULT) {
1826            throw new IllegalArgumentException("password cannot be empty");
1827        }
1828
1829        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1830            "no permission to access the crypt keeper");
1831
1832        waitForReady();
1833
1834        if (DEBUG_EVENTS) {
1835            Slog.i(TAG, "encrypting storage...");
1836        }
1837
1838        try {
1839            mConnector.execute("cryptfs", "enablecrypto", "inplace", CRYPTO_TYPES[type],
1840                               new SensitiveArg(toHex(password)));
1841        } catch (NativeDaemonConnectorException e) {
1842            // Encryption failed
1843            return e.getCode();
1844        }
1845
1846        return 0;
1847    }
1848
1849    /** Set the password for encrypting the master key.
1850     *  @param type One of the CRYPTO_TYPE_XXX consts defined in StorageManager.
1851     *  @param password The password to set.
1852     */
1853    public int changeEncryptionPassword(int type, String password) {
1854        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1855            "no permission to access the crypt keeper");
1856
1857        waitForReady();
1858
1859        if (DEBUG_EVENTS) {
1860            Slog.i(TAG, "changing encryption password...");
1861        }
1862
1863        try {
1864            NativeDaemonEvent event = mConnector.execute("cryptfs", "changepw", CRYPTO_TYPES[type],
1865                        new SensitiveArg(toHex(password)));
1866            return Integer.parseInt(event.getMessage());
1867        } catch (NativeDaemonConnectorException e) {
1868            // Encryption failed
1869            return e.getCode();
1870        }
1871    }
1872
1873    /**
1874     * Validate a user-supplied password string with cryptfs
1875     */
1876    @Override
1877    public int verifyEncryptionPassword(String password) throws RemoteException {
1878        // Only the system process is permitted to validate passwords
1879        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
1880            throw new SecurityException("no permission to access the crypt keeper");
1881        }
1882
1883        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1884            "no permission to access the crypt keeper");
1885
1886        if (TextUtils.isEmpty(password)) {
1887            throw new IllegalArgumentException("password cannot be empty");
1888        }
1889
1890        waitForReady();
1891
1892        if (DEBUG_EVENTS) {
1893            Slog.i(TAG, "validating encryption password...");
1894        }
1895
1896        final NativeDaemonEvent event;
1897        try {
1898            event = mConnector.execute("cryptfs", "verifypw", new SensitiveArg(toHex(password)));
1899            Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
1900            return Integer.parseInt(event.getMessage());
1901        } catch (NativeDaemonConnectorException e) {
1902            // Encryption failed
1903            return e.getCode();
1904        }
1905    }
1906
1907    /**
1908     * Get the type of encryption used to encrypt the master key.
1909     * @return The type, one of the CRYPT_TYPE_XXX consts from StorageManager.
1910     */
1911    @Override
1912    public int getPasswordType() {
1913
1914        waitForReady();
1915
1916        final NativeDaemonEvent event;
1917        try {
1918            event = mConnector.execute("cryptfs", "getpwtype");
1919            for (int i = 0; i < CRYPTO_TYPES.length; ++i) {
1920                if (CRYPTO_TYPES[i].equals(event.getMessage()))
1921                    return i;
1922            }
1923
1924            throw new IllegalStateException("unexpected return from cryptfs");
1925        } catch (NativeDaemonConnectorException e) {
1926            throw e.rethrowAsParcelableException();
1927        }
1928    }
1929
1930    /**
1931     * Set a field in the crypto header.
1932     * @param field field to set
1933     * @param contents contents to set in field
1934     */
1935    @Override
1936    public void setField(String field, String contents) throws RemoteException {
1937
1938        waitForReady();
1939
1940        final NativeDaemonEvent event;
1941        try {
1942            event = mConnector.execute("cryptfs", "setfield", field, contents);
1943        } catch (NativeDaemonConnectorException e) {
1944            throw e.rethrowAsParcelableException();
1945        }
1946    }
1947
1948    /**
1949     * Gets a field from the crypto header.
1950     * @param field field to get
1951     * @return contents of field
1952     */
1953    @Override
1954    public String getField(String field) throws RemoteException {
1955
1956        waitForReady();
1957
1958        final NativeDaemonEvent event;
1959        try {
1960            final String[] contents = NativeDaemonEvent.filterMessageList(
1961                    mConnector.executeForList("cryptfs", "getfield", field),
1962                    VoldResponseCode.CryptfsGetfieldResult);
1963            String result = new String();
1964            for (String content : contents) {
1965                result += content;
1966            }
1967            return result;
1968        } catch (NativeDaemonConnectorException e) {
1969            throw e.rethrowAsParcelableException();
1970        }
1971    }
1972
1973    @Override
1974    public String getPassword() throws RemoteException {
1975        if (!isReady()) {
1976            return new String();
1977        }
1978
1979        final NativeDaemonEvent event;
1980        try {
1981            event = mConnector.execute("cryptfs", "getpw");
1982            if ("-1".equals(event.getMessage())) {
1983                // -1 equals no password
1984                return null;
1985            }
1986            return fromHex(event.getMessage());
1987        } catch (NativeDaemonConnectorException e) {
1988            throw e.rethrowAsParcelableException();
1989        } catch (IllegalArgumentException e) {
1990            Slog.e(TAG, "Invalid response to getPassword");
1991            return null;
1992        }
1993    }
1994
1995    @Override
1996    public void clearPassword() throws RemoteException {
1997        if (!isReady()) {
1998            return;
1999        }
2000
2001        final NativeDaemonEvent event;
2002        try {
2003            event = mConnector.execute("cryptfs", "clearpw");
2004        } catch (NativeDaemonConnectorException e) {
2005            throw e.rethrowAsParcelableException();
2006        }
2007    }
2008
2009    @Override
2010    public int mkdirs(String callingPkg, String appPath) {
2011        final int userId = UserHandle.getUserId(Binder.getCallingUid());
2012        final UserEnvironment userEnv = new UserEnvironment(userId);
2013
2014        // Validate that reported package name belongs to caller
2015        final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
2016                Context.APP_OPS_SERVICE);
2017        appOps.checkPackage(Binder.getCallingUid(), callingPkg);
2018
2019        File appFile = null;
2020        try {
2021            appFile = new File(appPath).getCanonicalFile();
2022        } catch (IOException e) {
2023            Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
2024            return -1;
2025        }
2026
2027        // Try translating the app path into a vold path, but require that it
2028        // belong to the calling package.
2029        if (FileUtils.contains(userEnv.buildExternalStorageAppDataDirs(callingPkg), appFile) ||
2030                FileUtils.contains(userEnv.buildExternalStorageAppObbDirs(callingPkg), appFile) ||
2031                FileUtils.contains(userEnv.buildExternalStorageAppMediaDirs(callingPkg), appFile)) {
2032            appPath = appFile.getAbsolutePath();
2033            if (!appPath.endsWith("/")) {
2034                appPath = appPath + "/";
2035            }
2036
2037            try {
2038                mConnector.execute("volume", "mkdirs", appPath);
2039                return 0;
2040            } catch (NativeDaemonConnectorException e) {
2041                return e.getCode();
2042            }
2043        }
2044
2045        throw new SecurityException("Invalid mkdirs path: " + appFile);
2046    }
2047
2048    @Override
2049    public StorageVolume[] getVolumeList(int userId) {
2050        final ArrayList<StorageVolume> res = new ArrayList<>();
2051        boolean foundPrimary = false;
2052
2053        synchronized (mLock) {
2054            for (int i = 0; i < mVolumes.size(); i++) {
2055                final VolumeInfo vol = mVolumes.valueAt(i);
2056                if (vol.isVisibleToUser(userId)) {
2057                    final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
2058                    if (vol.isPrimary()) {
2059                        res.add(0, userVol);
2060                        foundPrimary = true;
2061                    } else {
2062                        res.add(userVol);
2063                    }
2064                }
2065            }
2066        }
2067
2068        if (!foundPrimary) {
2069            Log.w(TAG, "No primary storage defined yet; hacking together a stub");
2070
2071            final boolean primaryPhysical = SystemProperties.getBoolean(
2072                    StorageManager.PROP_PRIMARY_PHYSICAL, false);
2073
2074            final String id = "stub_primary";
2075            final File path = Environment.getLegacyExternalStorageDirectory();
2076            final String description = mContext.getString(android.R.string.unknownName);
2077            final boolean primary = true;
2078            final boolean removable = primaryPhysical;
2079            final boolean emulated = !primaryPhysical;
2080            final long mtpReserveSize = 0L;
2081            final boolean allowMassStorage = false;
2082            final long maxFileSize = 0L;
2083            final UserHandle owner = new UserHandle(userId);
2084            final String uuid = null;
2085            final String state = Environment.MEDIA_REMOVED;
2086
2087            res.add(0, new StorageVolume(id, MtpStorage.getStorageIdForIndex(0), path,
2088                    description, primary, removable, emulated, mtpReserveSize,
2089                    allowMassStorage, maxFileSize, owner, uuid, state));
2090        }
2091
2092        return res.toArray(new StorageVolume[res.size()]);
2093    }
2094
2095    @Override
2096    public DiskInfo[] getDisks() {
2097        synchronized (mLock) {
2098            final DiskInfo[] res = new DiskInfo[mDisks.size()];
2099            for (int i = 0; i < mDisks.size(); i++) {
2100                res[i] = mDisks.valueAt(i);
2101            }
2102            return res;
2103        }
2104    }
2105
2106    @Override
2107    public VolumeInfo[] getVolumes(int flags) {
2108        if ((flags & StorageManager.FLAG_ALL_METADATA) != 0) {
2109            // TODO: implement support for returning all metadata
2110            throw new UnsupportedOperationException();
2111        }
2112
2113        synchronized (mLock) {
2114            final VolumeInfo[] res = new VolumeInfo[mVolumes.size()];
2115            for (int i = 0; i < mVolumes.size(); i++) {
2116                res[i] = mVolumes.valueAt(i);
2117            }
2118            return res;
2119        }
2120    }
2121
2122    private void addObbStateLocked(ObbState obbState) throws RemoteException {
2123        final IBinder binder = obbState.getBinder();
2124        List<ObbState> obbStates = mObbMounts.get(binder);
2125
2126        if (obbStates == null) {
2127            obbStates = new ArrayList<ObbState>();
2128            mObbMounts.put(binder, obbStates);
2129        } else {
2130            for (final ObbState o : obbStates) {
2131                if (o.rawPath.equals(obbState.rawPath)) {
2132                    throw new IllegalStateException("Attempt to add ObbState twice. "
2133                            + "This indicates an error in the MountService logic.");
2134                }
2135            }
2136        }
2137
2138        obbStates.add(obbState);
2139        try {
2140            obbState.link();
2141        } catch (RemoteException e) {
2142            /*
2143             * The binder died before we could link it, so clean up our state
2144             * and return failure.
2145             */
2146            obbStates.remove(obbState);
2147            if (obbStates.isEmpty()) {
2148                mObbMounts.remove(binder);
2149            }
2150
2151            // Rethrow the error so mountObb can get it
2152            throw e;
2153        }
2154
2155        mObbPathToStateMap.put(obbState.rawPath, obbState);
2156    }
2157
2158    private void removeObbStateLocked(ObbState obbState) {
2159        final IBinder binder = obbState.getBinder();
2160        final List<ObbState> obbStates = mObbMounts.get(binder);
2161        if (obbStates != null) {
2162            if (obbStates.remove(obbState)) {
2163                obbState.unlink();
2164            }
2165            if (obbStates.isEmpty()) {
2166                mObbMounts.remove(binder);
2167            }
2168        }
2169
2170        mObbPathToStateMap.remove(obbState.rawPath);
2171    }
2172
2173    private class ObbActionHandler extends Handler {
2174        private boolean mBound = false;
2175        private final List<ObbAction> mActions = new LinkedList<ObbAction>();
2176
2177        ObbActionHandler(Looper l) {
2178            super(l);
2179        }
2180
2181        @Override
2182        public void handleMessage(Message msg) {
2183            switch (msg.what) {
2184                case OBB_RUN_ACTION: {
2185                    final ObbAction action = (ObbAction) msg.obj;
2186
2187                    if (DEBUG_OBB)
2188                        Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
2189
2190                    // If a bind was already initiated we don't really
2191                    // need to do anything. The pending install
2192                    // will be processed later on.
2193                    if (!mBound) {
2194                        // If this is the only one pending we might
2195                        // have to bind to the service again.
2196                        if (!connectToService()) {
2197                            Slog.e(TAG, "Failed to bind to media container service");
2198                            action.handleError();
2199                            return;
2200                        }
2201                    }
2202
2203                    mActions.add(action);
2204                    break;
2205                }
2206                case OBB_MCS_BOUND: {
2207                    if (DEBUG_OBB)
2208                        Slog.i(TAG, "OBB_MCS_BOUND");
2209                    if (msg.obj != null) {
2210                        mContainerService = (IMediaContainerService) msg.obj;
2211                    }
2212                    if (mContainerService == null) {
2213                        // Something seriously wrong. Bail out
2214                        Slog.e(TAG, "Cannot bind to media container service");
2215                        for (ObbAction action : mActions) {
2216                            // Indicate service bind error
2217                            action.handleError();
2218                        }
2219                        mActions.clear();
2220                    } else if (mActions.size() > 0) {
2221                        final ObbAction action = mActions.get(0);
2222                        if (action != null) {
2223                            action.execute(this);
2224                        }
2225                    } else {
2226                        // Should never happen ideally.
2227                        Slog.w(TAG, "Empty queue");
2228                    }
2229                    break;
2230                }
2231                case OBB_MCS_RECONNECT: {
2232                    if (DEBUG_OBB)
2233                        Slog.i(TAG, "OBB_MCS_RECONNECT");
2234                    if (mActions.size() > 0) {
2235                        if (mBound) {
2236                            disconnectService();
2237                        }
2238                        if (!connectToService()) {
2239                            Slog.e(TAG, "Failed to bind to media container service");
2240                            for (ObbAction action : mActions) {
2241                                // Indicate service bind error
2242                                action.handleError();
2243                            }
2244                            mActions.clear();
2245                        }
2246                    }
2247                    break;
2248                }
2249                case OBB_MCS_UNBIND: {
2250                    if (DEBUG_OBB)
2251                        Slog.i(TAG, "OBB_MCS_UNBIND");
2252
2253                    // Delete pending install
2254                    if (mActions.size() > 0) {
2255                        mActions.remove(0);
2256                    }
2257                    if (mActions.size() == 0) {
2258                        if (mBound) {
2259                            disconnectService();
2260                        }
2261                    } else {
2262                        // There are more pending requests in queue.
2263                        // Just post MCS_BOUND message to trigger processing
2264                        // of next pending install.
2265                        mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
2266                    }
2267                    break;
2268                }
2269                case OBB_FLUSH_MOUNT_STATE: {
2270                    final String path = (String) msg.obj;
2271
2272                    if (DEBUG_OBB)
2273                        Slog.i(TAG, "Flushing all OBB state for path " + path);
2274
2275                    synchronized (mObbMounts) {
2276                        final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
2277
2278                        final Iterator<ObbState> i = mObbPathToStateMap.values().iterator();
2279                        while (i.hasNext()) {
2280                            final ObbState state = i.next();
2281
2282                            /*
2283                             * If this entry's source file is in the volume path
2284                             * that got unmounted, remove it because it's no
2285                             * longer valid.
2286                             */
2287                            if (state.canonicalPath.startsWith(path)) {
2288                                obbStatesToRemove.add(state);
2289                            }
2290                        }
2291
2292                        for (final ObbState obbState : obbStatesToRemove) {
2293                            if (DEBUG_OBB)
2294                                Slog.i(TAG, "Removing state for " + obbState.rawPath);
2295
2296                            removeObbStateLocked(obbState);
2297
2298                            try {
2299                                obbState.token.onObbResult(obbState.rawPath, obbState.nonce,
2300                                        OnObbStateChangeListener.UNMOUNTED);
2301                            } catch (RemoteException e) {
2302                                Slog.i(TAG, "Couldn't send unmount notification for  OBB: "
2303                                        + obbState.rawPath);
2304                            }
2305                        }
2306                    }
2307                    break;
2308                }
2309            }
2310        }
2311
2312        private boolean connectToService() {
2313            if (DEBUG_OBB)
2314                Slog.i(TAG, "Trying to bind to DefaultContainerService");
2315
2316            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
2317            if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
2318                mBound = true;
2319                return true;
2320            }
2321            return false;
2322        }
2323
2324        private void disconnectService() {
2325            mContainerService = null;
2326            mBound = false;
2327            mContext.unbindService(mDefContainerConn);
2328        }
2329    }
2330
2331    abstract class ObbAction {
2332        private static final int MAX_RETRIES = 3;
2333        private int mRetries;
2334
2335        ObbState mObbState;
2336
2337        ObbAction(ObbState obbState) {
2338            mObbState = obbState;
2339        }
2340
2341        public void execute(ObbActionHandler handler) {
2342            try {
2343                if (DEBUG_OBB)
2344                    Slog.i(TAG, "Starting to execute action: " + toString());
2345                mRetries++;
2346                if (mRetries > MAX_RETRIES) {
2347                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
2348                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2349                    handleError();
2350                    return;
2351                } else {
2352                    handleExecute();
2353                    if (DEBUG_OBB)
2354                        Slog.i(TAG, "Posting install MCS_UNBIND");
2355                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2356                }
2357            } catch (RemoteException e) {
2358                if (DEBUG_OBB)
2359                    Slog.i(TAG, "Posting install MCS_RECONNECT");
2360                mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT);
2361            } catch (Exception e) {
2362                if (DEBUG_OBB)
2363                    Slog.d(TAG, "Error handling OBB action", e);
2364                handleError();
2365                mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2366            }
2367        }
2368
2369        abstract void handleExecute() throws RemoteException, IOException;
2370        abstract void handleError();
2371
2372        protected ObbInfo getObbInfo() throws IOException {
2373            ObbInfo obbInfo;
2374            try {
2375                obbInfo = mContainerService.getObbInfo(mObbState.ownerPath);
2376            } catch (RemoteException e) {
2377                Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
2378                        + mObbState.ownerPath);
2379                obbInfo = null;
2380            }
2381            if (obbInfo == null) {
2382                throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath);
2383            }
2384            return obbInfo;
2385        }
2386
2387        protected void sendNewStatusOrIgnore(int status) {
2388            if (mObbState == null || mObbState.token == null) {
2389                return;
2390            }
2391
2392            try {
2393                mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status);
2394            } catch (RemoteException e) {
2395                Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
2396            }
2397        }
2398    }
2399
2400    class MountObbAction extends ObbAction {
2401        private final String mKey;
2402        private final int mCallingUid;
2403
2404        MountObbAction(ObbState obbState, String key, int callingUid) {
2405            super(obbState);
2406            mKey = key;
2407            mCallingUid = callingUid;
2408        }
2409
2410        @Override
2411        public void handleExecute() throws IOException, RemoteException {
2412            waitForReady();
2413            warnOnNotMounted();
2414
2415            final ObbInfo obbInfo = getObbInfo();
2416
2417            if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) {
2418                Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename
2419                        + " which is owned by " + obbInfo.packageName);
2420                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
2421                return;
2422            }
2423
2424            final boolean isMounted;
2425            synchronized (mObbMounts) {
2426                isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath);
2427            }
2428            if (isMounted) {
2429                Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename);
2430                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED);
2431                return;
2432            }
2433
2434            final String hashedKey;
2435            if (mKey == null) {
2436                hashedKey = "none";
2437            } else {
2438                try {
2439                    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
2440
2441                    KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt,
2442                            PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE);
2443                    SecretKey key = factory.generateSecret(ks);
2444                    BigInteger bi = new BigInteger(key.getEncoded());
2445                    hashedKey = bi.toString(16);
2446                } catch (NoSuchAlgorithmException e) {
2447                    Slog.e(TAG, "Could not load PBKDF2 algorithm", e);
2448                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2449                    return;
2450                } catch (InvalidKeySpecException e) {
2451                    Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e);
2452                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2453                    return;
2454                }
2455            }
2456
2457            int rc = StorageResultCode.OperationSucceeded;
2458            try {
2459                mConnector.execute("obb", "mount", mObbState.voldPath, new SensitiveArg(hashedKey),
2460                        mObbState.ownerGid);
2461            } catch (NativeDaemonConnectorException e) {
2462                int code = e.getCode();
2463                if (code != VoldResponseCode.OpFailedStorageBusy) {
2464                    rc = StorageResultCode.OperationFailedInternalError;
2465                }
2466            }
2467
2468            if (rc == StorageResultCode.OperationSucceeded) {
2469                if (DEBUG_OBB)
2470                    Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath);
2471
2472                synchronized (mObbMounts) {
2473                    addObbStateLocked(mObbState);
2474                }
2475
2476                sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED);
2477            } else {
2478                Slog.e(TAG, "Couldn't mount OBB file: " + rc);
2479
2480                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT);
2481            }
2482        }
2483
2484        @Override
2485        public void handleError() {
2486            sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2487        }
2488
2489        @Override
2490        public String toString() {
2491            StringBuilder sb = new StringBuilder();
2492            sb.append("MountObbAction{");
2493            sb.append(mObbState);
2494            sb.append('}');
2495            return sb.toString();
2496        }
2497    }
2498
2499    class UnmountObbAction extends ObbAction {
2500        private final boolean mForceUnmount;
2501
2502        UnmountObbAction(ObbState obbState, boolean force) {
2503            super(obbState);
2504            mForceUnmount = force;
2505        }
2506
2507        @Override
2508        public void handleExecute() throws IOException {
2509            waitForReady();
2510            warnOnNotMounted();
2511
2512            final ObbInfo obbInfo = getObbInfo();
2513
2514            final ObbState existingState;
2515            synchronized (mObbMounts) {
2516                existingState = mObbPathToStateMap.get(mObbState.rawPath);
2517            }
2518
2519            if (existingState == null) {
2520                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED);
2521                return;
2522            }
2523
2524            if (existingState.ownerGid != mObbState.ownerGid) {
2525                Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath
2526                        + " (owned by GID " + existingState.ownerGid + ")");
2527                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
2528                return;
2529            }
2530
2531            int rc = StorageResultCode.OperationSucceeded;
2532            try {
2533                final Command cmd = new Command("obb", "unmount", mObbState.voldPath);
2534                if (mForceUnmount) {
2535                    cmd.appendArg("force");
2536                }
2537                mConnector.execute(cmd);
2538            } catch (NativeDaemonConnectorException e) {
2539                int code = e.getCode();
2540                if (code == VoldResponseCode.OpFailedStorageBusy) {
2541                    rc = StorageResultCode.OperationFailedStorageBusy;
2542                } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
2543                    // If it's not mounted then we've already won.
2544                    rc = StorageResultCode.OperationSucceeded;
2545                } else {
2546                    rc = StorageResultCode.OperationFailedInternalError;
2547                }
2548            }
2549
2550            if (rc == StorageResultCode.OperationSucceeded) {
2551                synchronized (mObbMounts) {
2552                    removeObbStateLocked(existingState);
2553                }
2554
2555                sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED);
2556            } else {
2557                Slog.w(TAG, "Could not unmount OBB: " + existingState);
2558                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT);
2559            }
2560        }
2561
2562        @Override
2563        public void handleError() {
2564            sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2565        }
2566
2567        @Override
2568        public String toString() {
2569            StringBuilder sb = new StringBuilder();
2570            sb.append("UnmountObbAction{");
2571            sb.append(mObbState);
2572            sb.append(",force=");
2573            sb.append(mForceUnmount);
2574            sb.append('}');
2575            return sb.toString();
2576        }
2577    }
2578
2579    @VisibleForTesting
2580    public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) {
2581        // TODO: allow caller to provide Environment for full testing
2582        // TODO: extend to support OBB mounts on secondary external storage
2583
2584        // Only adjust paths when storage is emulated
2585        if (!Environment.isExternalStorageEmulated()) {
2586            return canonicalPath;
2587        }
2588
2589        String path = canonicalPath.toString();
2590
2591        // First trim off any external storage prefix
2592        final UserEnvironment userEnv = new UserEnvironment(userId);
2593
2594        // /storage/emulated/0
2595        final String externalPath = userEnv.getExternalStorageDirectory().getAbsolutePath();
2596        // /storage/emulated_legacy
2597        final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory()
2598                .getAbsolutePath();
2599
2600        if (path.startsWith(externalPath)) {
2601            path = path.substring(externalPath.length() + 1);
2602        } else if (path.startsWith(legacyExternalPath)) {
2603            path = path.substring(legacyExternalPath.length() + 1);
2604        } else {
2605            return canonicalPath;
2606        }
2607
2608        // Handle special OBB paths on emulated storage
2609        final String obbPath = "Android/obb";
2610        if (path.startsWith(obbPath)) {
2611            path = path.substring(obbPath.length() + 1);
2612
2613            final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
2614            return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
2615                    .getAbsolutePath();
2616        }
2617
2618        // Handle normal external storage paths
2619        return new File(userEnv.getExternalStorageDirectory(), path).getAbsolutePath();
2620    }
2621
2622    private static class Callbacks extends Handler {
2623        private static final int MSG_STORAGE_STATE_CHANGED = 1;
2624        private static final int MSG_VOLUME_STATE_CHANGED = 2;
2625        private static final int MSG_VOLUME_METADATA_CHANGED = 3;
2626        private static final int MSG_DISK_UNSUPPORTED = 4;
2627
2628        private final RemoteCallbackList<IMountServiceListener>
2629                mCallbacks = new RemoteCallbackList<>();
2630
2631        public Callbacks(Looper looper) {
2632            super(looper);
2633        }
2634
2635        public void register(IMountServiceListener callback) {
2636            mCallbacks.register(callback);
2637        }
2638
2639        public void unregister(IMountServiceListener callback) {
2640            mCallbacks.unregister(callback);
2641        }
2642
2643        @Override
2644        public void handleMessage(Message msg) {
2645            final SomeArgs args = (SomeArgs) msg.obj;
2646            final int n = mCallbacks.beginBroadcast();
2647            for (int i = 0; i < n; i++) {
2648                final IMountServiceListener callback = mCallbacks.getBroadcastItem(i);
2649                try {
2650                    invokeCallback(callback, msg.what, args);
2651                } catch (RemoteException ignored) {
2652                }
2653            }
2654            mCallbacks.finishBroadcast();
2655            args.recycle();
2656        }
2657
2658        private void invokeCallback(IMountServiceListener callback, int what, SomeArgs args)
2659                throws RemoteException {
2660            switch (what) {
2661                case MSG_STORAGE_STATE_CHANGED: {
2662                    callback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
2663                            (String) args.arg3);
2664                    break;
2665                }
2666                case MSG_VOLUME_STATE_CHANGED: {
2667                    callback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
2668                    break;
2669                }
2670                case MSG_VOLUME_METADATA_CHANGED: {
2671                    callback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
2672                    break;
2673                }
2674                case MSG_DISK_UNSUPPORTED: {
2675                    callback.onDiskUnsupported((DiskInfo) args.arg1);
2676                    break;
2677                }
2678            }
2679        }
2680
2681        private void notifyStorageStateChanged(String path, String oldState, String newState) {
2682            final SomeArgs args = SomeArgs.obtain();
2683            args.arg1 = path;
2684            args.arg2 = oldState;
2685            args.arg3 = newState;
2686            obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
2687        }
2688
2689        private void notifyVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
2690            final SomeArgs args = SomeArgs.obtain();
2691            args.arg1 = vol;
2692            args.argi2 = oldState;
2693            args.argi3 = newState;
2694            obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
2695        }
2696
2697        private void notifyVolumeMetadataChanged(VolumeInfo vol) {
2698            final SomeArgs args = SomeArgs.obtain();
2699            args.arg1 = vol;
2700            obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget();
2701        }
2702
2703        private void notifyDiskUnsupported(DiskInfo disk) {
2704            final SomeArgs args = SomeArgs.obtain();
2705            args.arg1 = disk;
2706            obtainMessage(MSG_DISK_UNSUPPORTED, args).sendToTarget();
2707        }
2708    }
2709
2710    @Override
2711    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
2712        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
2713
2714        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
2715
2716        synchronized (mObbMounts) {
2717            pw.println("mObbMounts:");
2718            pw.increaseIndent();
2719            final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet()
2720                    .iterator();
2721            while (binders.hasNext()) {
2722                Entry<IBinder, List<ObbState>> e = binders.next();
2723                pw.println(e.getKey() + ":");
2724                pw.increaseIndent();
2725                final List<ObbState> obbStates = e.getValue();
2726                for (final ObbState obbState : obbStates) {
2727                    pw.println(obbState);
2728                }
2729                pw.decreaseIndent();
2730            }
2731            pw.decreaseIndent();
2732
2733            pw.println();
2734            pw.println("mObbPathToStateMap:");
2735            pw.increaseIndent();
2736            final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
2737            while (maps.hasNext()) {
2738                final Entry<String, ObbState> e = maps.next();
2739                pw.print(e.getKey());
2740                pw.print(" -> ");
2741                pw.println(e.getValue());
2742            }
2743            pw.decreaseIndent();
2744        }
2745
2746        synchronized (mLock) {
2747            pw.println();
2748            pw.println("Disks:");
2749            pw.increaseIndent();
2750            for (int i = 0; i < mDisks.size(); i++) {
2751                final DiskInfo disk = mDisks.valueAt(i);
2752                disk.dump(pw);
2753            }
2754            pw.decreaseIndent();
2755
2756            pw.println();
2757            pw.println("Volumes:");
2758            pw.increaseIndent();
2759            for (int i = 0; i < mVolumes.size(); i++) {
2760                final VolumeInfo vol = mVolumes.valueAt(i);
2761                if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) continue;
2762                vol.dump(pw);
2763            }
2764            pw.decreaseIndent();
2765
2766            pw.println();
2767            pw.println("Metadata:");
2768            pw.increaseIndent();
2769            for (int i = 0; i < mMetadata.size(); i++) {
2770                final VolumeMetadata meta = mMetadata.valueAt(i);
2771                meta.dump(pw);
2772            }
2773            pw.decreaseIndent();
2774        }
2775
2776        pw.println();
2777        pw.println("mConnection:");
2778        pw.increaseIndent();
2779        mConnector.dump(fd, pw, args);
2780        pw.decreaseIndent();
2781
2782        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
2783
2784        pw.println();
2785        pw.print("Last maintenance: ");
2786        pw.println(sdf.format(new Date(mLastMaintenance)));
2787    }
2788
2789    /** {@inheritDoc} */
2790    @Override
2791    public void monitor() {
2792        if (mConnector != null) {
2793            mConnector.monitor();
2794        }
2795    }
2796}
2797