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