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