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