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