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