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