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