OSDN Git Service

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