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