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