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