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