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