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