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