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