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