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