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