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