MountService.java revision 5ac72a29593ab9a20337a2225df52bdf4754be02
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,
1118                                descriptionId, removable, emulated,
1119                                mtpReserve, allowMassStorage, maxFileSize);
1120                        if (primary) {
1121                            if (mPrimaryVolume == null) {
1122                                mPrimaryVolume = volume;
1123                            } else {
1124                                Slog.e(TAG, "multiple primary volumes in storage list");
1125                            }
1126                        }
1127                        if (mPrimaryVolume == volume) {
1128                            // primay volume must be first
1129                            mVolumes.add(0, volume);
1130                        } else {
1131                            mVolumes.add(volume);
1132                        }
1133                        mVolumeMap.put(pathString, volume);
1134                    }
1135                    a.recycle();
1136                }
1137            }
1138        } catch (XmlPullParserException e) {
1139            throw new RuntimeException(e);
1140        } catch (IOException e) {
1141            throw new RuntimeException(e);
1142        } finally {
1143            // compute storage ID for each volume
1144            int length = mVolumes.size();
1145            for (int i = 0; i < length; i++) {
1146                mVolumes.get(i).setStorageId(i);
1147            }
1148            parser.close();
1149        }
1150    }
1151
1152    /**
1153     * Constructs a new MountService instance
1154     *
1155     * @param context  Binder context for this service
1156     */
1157    public MountService(Context context) {
1158        mContext = context;
1159        readStorageList();
1160
1161        if (mPrimaryVolume != null) {
1162            mExternalStoragePath = mPrimaryVolume.getPath();
1163            mEmulateExternalStorage = mPrimaryVolume.isEmulated();
1164            if (mEmulateExternalStorage) {
1165                Slog.d(TAG, "using emulated external storage");
1166                mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED);
1167            }
1168        }
1169
1170        // XXX: This will go away soon in favor of IMountServiceObserver
1171        mPms = (PackageManagerService) ServiceManager.getService("package");
1172
1173        IntentFilter filter = new IntentFilter();
1174        filter.addAction(Intent.ACTION_BOOT_COMPLETED);
1175        // don't bother monitoring USB if mass storage is not supported on our primary volume
1176        if (mPrimaryVolume != null && mPrimaryVolume.allowMassStorage()) {
1177            filter.addAction(UsbManager.ACTION_USB_STATE);
1178        }
1179        mContext.registerReceiver(mBroadcastReceiver, filter, null, null);
1180
1181        mHandlerThread = new HandlerThread("MountService");
1182        mHandlerThread.start();
1183        mHandler = new MountServiceHandler(mHandlerThread.getLooper());
1184
1185        // Add OBB Action Handler to MountService thread.
1186        mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
1187
1188        /*
1189         * Create the connection to vold with a maximum queue of twice the
1190         * amount of containers we'd ever expect to have. This keeps an
1191         * "asec list" from blocking a thread repeatedly.
1192         */
1193        mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25);
1194
1195        Thread thread = new Thread(mConnector, VOLD_TAG);
1196        thread.start();
1197
1198        // Add ourself to the Watchdog monitors if enabled.
1199        if (WATCHDOG_ENABLE) {
1200            Watchdog.getInstance().addMonitor(this);
1201        }
1202    }
1203
1204    /**
1205     * Exposed API calls below here
1206     */
1207
1208    public void registerListener(IMountServiceListener listener) {
1209        synchronized (mListeners) {
1210            MountServiceBinderListener bl = new MountServiceBinderListener(listener);
1211            try {
1212                listener.asBinder().linkToDeath(bl, 0);
1213                mListeners.add(bl);
1214            } catch (RemoteException rex) {
1215                Slog.e(TAG, "Failed to link to listener death");
1216            }
1217        }
1218    }
1219
1220    public void unregisterListener(IMountServiceListener listener) {
1221        synchronized (mListeners) {
1222            for(MountServiceBinderListener bl : mListeners) {
1223                if (bl.mListener == listener) {
1224                    mListeners.remove(mListeners.indexOf(bl));
1225                    listener.asBinder().unlinkToDeath(bl, 0);
1226                    return;
1227                }
1228            }
1229        }
1230    }
1231
1232    public void shutdown(final IMountShutdownObserver observer) {
1233        validatePermission(android.Manifest.permission.SHUTDOWN);
1234
1235        Slog.i(TAG, "Shutting down");
1236        synchronized (mVolumeStates) {
1237            for (String path : mVolumeStates.keySet()) {
1238                String state = mVolumeStates.get(path);
1239
1240                if (state.equals(Environment.MEDIA_SHARED)) {
1241                    /*
1242                     * If the media is currently shared, unshare it.
1243                     * XXX: This is still dangerous!. We should not
1244                     * be rebooting at *all* if UMS is enabled, since
1245                     * the UMS host could have dirty FAT cache entries
1246                     * yet to flush.
1247                     */
1248                    setUsbMassStorageEnabled(false);
1249                } else if (state.equals(Environment.MEDIA_CHECKING)) {
1250                    /*
1251                     * If the media is being checked, then we need to wait for
1252                     * it to complete before being able to proceed.
1253                     */
1254                    // XXX: @hackbod - Should we disable the ANR timer here?
1255                    int retries = 30;
1256                    while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
1257                        try {
1258                            Thread.sleep(1000);
1259                        } catch (InterruptedException iex) {
1260                            Slog.e(TAG, "Interrupted while waiting for media", iex);
1261                            break;
1262                        }
1263                        state = Environment.getExternalStorageState();
1264                    }
1265                    if (retries == 0) {
1266                        Slog.e(TAG, "Timed out waiting for media to check");
1267                    }
1268                }
1269
1270                if (state.equals(Environment.MEDIA_MOUNTED)) {
1271                    // Post a unmount message.
1272                    ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
1273                    mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
1274                } else if (observer != null) {
1275                    /*
1276                     * Observer is waiting for onShutDownComplete when we are done.
1277                     * Since nothing will be done send notification directly so shutdown
1278                     * sequence can continue.
1279                     */
1280                    try {
1281                        observer.onShutDownComplete(StorageResultCode.OperationSucceeded);
1282                    } catch (RemoteException e) {
1283                        Slog.w(TAG, "RemoteException when shutting down");
1284                    }
1285                }
1286            }
1287        }
1288    }
1289
1290    private boolean getUmsEnabling() {
1291        synchronized (mListeners) {
1292            return mUmsEnabling;
1293        }
1294    }
1295
1296    private void setUmsEnabling(boolean enable) {
1297        synchronized (mListeners) {
1298            mUmsEnabling = enable;
1299        }
1300    }
1301
1302    public boolean isUsbMassStorageConnected() {
1303        waitForReady();
1304
1305        if (getUmsEnabling()) {
1306            return true;
1307        }
1308        synchronized (mListeners) {
1309            return mUmsAvailable;
1310        }
1311    }
1312
1313    public void setUsbMassStorageEnabled(boolean enable) {
1314        waitForReady();
1315        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1316
1317        // TODO: Add support for multiple share methods
1318
1319        /*
1320         * If the volume is mounted and we're enabling then unmount it
1321         */
1322        String path = Environment.getExternalStorageDirectory().getPath();
1323        String vs = getVolumeState(path);
1324        String method = "ums";
1325        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
1326            // Override for isUsbMassStorageEnabled()
1327            setUmsEnabling(enable);
1328            UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
1329            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
1330            // Clear override
1331            setUmsEnabling(false);
1332        }
1333        /*
1334         * If we disabled UMS then mount the volume
1335         */
1336        if (!enable) {
1337            doShareUnshareVolume(path, method, enable);
1338            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
1339                Slog.e(TAG, "Failed to remount " + path +
1340                        " after disabling share method " + method);
1341                /*
1342                 * Even though the mount failed, the unshare didn't so don't indicate an error.
1343                 * The mountVolume() call will have set the storage state and sent the necessary
1344                 * broadcasts.
1345                 */
1346            }
1347        }
1348    }
1349
1350    public boolean isUsbMassStorageEnabled() {
1351        waitForReady();
1352        return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
1353    }
1354
1355    /**
1356     * @return state of the volume at the specified mount point
1357     */
1358    public String getVolumeState(String mountPoint) {
1359        synchronized (mVolumeStates) {
1360            String state = mVolumeStates.get(mountPoint);
1361            if (state == null) {
1362                Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
1363                if (SystemProperties.get("vold.encrypt_progress").length() != 0) {
1364                    state = Environment.MEDIA_REMOVED;
1365                } else {
1366                    throw new IllegalArgumentException();
1367                }
1368            }
1369
1370            return state;
1371        }
1372    }
1373
1374    public boolean isExternalStorageEmulated() {
1375        return mEmulateExternalStorage;
1376    }
1377
1378    public int mountVolume(String path) {
1379        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1380
1381        waitForReady();
1382        return doMountVolume(path);
1383    }
1384
1385    public void unmountVolume(String path, boolean force, boolean removeEncryption) {
1386        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1387        waitForReady();
1388
1389        String volState = getVolumeState(path);
1390        if (DEBUG_UNMOUNT) {
1391            Slog.i(TAG, "Unmounting " + path
1392                    + " force = " + force
1393                    + " removeEncryption = " + removeEncryption);
1394        }
1395        if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
1396                Environment.MEDIA_REMOVED.equals(volState) ||
1397                Environment.MEDIA_SHARED.equals(volState) ||
1398                Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
1399            // Media already unmounted or cannot be unmounted.
1400            // TODO return valid return code when adding observer call back.
1401            return;
1402        }
1403        UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption);
1404        mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
1405    }
1406
1407    public int formatVolume(String path) {
1408        validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
1409        waitForReady();
1410
1411        return doFormatVolume(path);
1412    }
1413
1414    public int[] getStorageUsers(String path) {
1415        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1416        waitForReady();
1417        try {
1418            final String[] r = NativeDaemonEvent.filterMessageList(
1419                    mConnector.executeForList("storage", "users", path),
1420                    VoldResponseCode.StorageUsersListResult);
1421
1422            // FMT: <pid> <process name>
1423            int[] data = new int[r.length];
1424            for (int i = 0; i < r.length; i++) {
1425                String[] tok = r[i].split(" ");
1426                try {
1427                    data[i] = Integer.parseInt(tok[0]);
1428                } catch (NumberFormatException nfe) {
1429                    Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
1430                    return new int[0];
1431                }
1432            }
1433            return data;
1434        } catch (NativeDaemonConnectorException e) {
1435            Slog.e(TAG, "Failed to retrieve storage users list", e);
1436            return new int[0];
1437        }
1438    }
1439
1440    private void warnOnNotMounted() {
1441        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1442            Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
1443        }
1444    }
1445
1446    public String[] getSecureContainerList() {
1447        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1448        waitForReady();
1449        warnOnNotMounted();
1450
1451        try {
1452            return NativeDaemonEvent.filterMessageList(
1453                    mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
1454        } catch (NativeDaemonConnectorException e) {
1455            return new String[0];
1456        }
1457    }
1458
1459    public int createSecureContainer(String id, int sizeMb, String fstype, String key,
1460            int ownerUid, boolean external) {
1461        validatePermission(android.Manifest.permission.ASEC_CREATE);
1462        waitForReady();
1463        warnOnNotMounted();
1464
1465        int rc = StorageResultCode.OperationSucceeded;
1466        try {
1467            mConnector.execute("asec", "create", id, sizeMb, fstype, key, ownerUid,
1468                    external ? "1" : "0");
1469        } catch (NativeDaemonConnectorException e) {
1470            rc = StorageResultCode.OperationFailedInternalError;
1471        }
1472
1473        if (rc == StorageResultCode.OperationSucceeded) {
1474            synchronized (mAsecMountSet) {
1475                mAsecMountSet.add(id);
1476            }
1477        }
1478        return rc;
1479    }
1480
1481    public int finalizeSecureContainer(String id) {
1482        validatePermission(android.Manifest.permission.ASEC_CREATE);
1483        warnOnNotMounted();
1484
1485        int rc = StorageResultCode.OperationSucceeded;
1486        try {
1487            mConnector.execute("asec", "finalize", id);
1488            /*
1489             * Finalization does a remount, so no need
1490             * to update mAsecMountSet
1491             */
1492        } catch (NativeDaemonConnectorException e) {
1493            rc = StorageResultCode.OperationFailedInternalError;
1494        }
1495        return rc;
1496    }
1497
1498    public int fixPermissionsSecureContainer(String id, int gid, String filename) {
1499        validatePermission(android.Manifest.permission.ASEC_CREATE);
1500        warnOnNotMounted();
1501
1502        int rc = StorageResultCode.OperationSucceeded;
1503        try {
1504            mConnector.execute("asec", "fixperms", id, gid, filename);
1505            /*
1506             * Fix permissions does a remount, so no need to update
1507             * mAsecMountSet
1508             */
1509        } catch (NativeDaemonConnectorException e) {
1510            rc = StorageResultCode.OperationFailedInternalError;
1511        }
1512        return rc;
1513    }
1514
1515    public int destroySecureContainer(String id, boolean force) {
1516        validatePermission(android.Manifest.permission.ASEC_DESTROY);
1517        waitForReady();
1518        warnOnNotMounted();
1519
1520        /*
1521         * Force a GC to make sure AssetManagers in other threads of the
1522         * system_server are cleaned up. We have to do this since AssetManager
1523         * instances are kept as a WeakReference and it's possible we have files
1524         * open on the external storage.
1525         */
1526        Runtime.getRuntime().gc();
1527
1528        int rc = StorageResultCode.OperationSucceeded;
1529        try {
1530            final Command cmd = new Command("asec", "destroy", id);
1531            if (force) {
1532                cmd.appendArg("force");
1533            }
1534            mConnector.execute(cmd);
1535        } catch (NativeDaemonConnectorException e) {
1536            int code = e.getCode();
1537            if (code == VoldResponseCode.OpFailedStorageBusy) {
1538                rc = StorageResultCode.OperationFailedStorageBusy;
1539            } else {
1540                rc = StorageResultCode.OperationFailedInternalError;
1541            }
1542        }
1543
1544        if (rc == StorageResultCode.OperationSucceeded) {
1545            synchronized (mAsecMountSet) {
1546                if (mAsecMountSet.contains(id)) {
1547                    mAsecMountSet.remove(id);
1548                }
1549            }
1550        }
1551
1552        return rc;
1553    }
1554
1555    public int mountSecureContainer(String id, String key, int ownerUid) {
1556        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1557        waitForReady();
1558        warnOnNotMounted();
1559
1560        synchronized (mAsecMountSet) {
1561            if (mAsecMountSet.contains(id)) {
1562                return StorageResultCode.OperationFailedStorageMounted;
1563            }
1564        }
1565
1566        int rc = StorageResultCode.OperationSucceeded;
1567        try {
1568            mConnector.execute("asec", "mount", id, key, ownerUid);
1569        } catch (NativeDaemonConnectorException e) {
1570            int code = e.getCode();
1571            if (code != VoldResponseCode.OpFailedStorageBusy) {
1572                rc = StorageResultCode.OperationFailedInternalError;
1573            }
1574        }
1575
1576        if (rc == StorageResultCode.OperationSucceeded) {
1577            synchronized (mAsecMountSet) {
1578                mAsecMountSet.add(id);
1579            }
1580        }
1581        return rc;
1582    }
1583
1584    public int unmountSecureContainer(String id, boolean force) {
1585        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1586        waitForReady();
1587        warnOnNotMounted();
1588
1589        synchronized (mAsecMountSet) {
1590            if (!mAsecMountSet.contains(id)) {
1591                return StorageResultCode.OperationFailedStorageNotMounted;
1592            }
1593         }
1594
1595        /*
1596         * Force a GC to make sure AssetManagers in other threads of the
1597         * system_server are cleaned up. We have to do this since AssetManager
1598         * instances are kept as a WeakReference and it's possible we have files
1599         * open on the external storage.
1600         */
1601        Runtime.getRuntime().gc();
1602
1603        int rc = StorageResultCode.OperationSucceeded;
1604        try {
1605            final Command cmd = new Command("asec", "unmount", id);
1606            if (force) {
1607                cmd.appendArg("force");
1608            }
1609            mConnector.execute(cmd);
1610        } catch (NativeDaemonConnectorException e) {
1611            int code = e.getCode();
1612            if (code == VoldResponseCode.OpFailedStorageBusy) {
1613                rc = StorageResultCode.OperationFailedStorageBusy;
1614            } else {
1615                rc = StorageResultCode.OperationFailedInternalError;
1616            }
1617        }
1618
1619        if (rc == StorageResultCode.OperationSucceeded) {
1620            synchronized (mAsecMountSet) {
1621                mAsecMountSet.remove(id);
1622            }
1623        }
1624        return rc;
1625    }
1626
1627    public boolean isSecureContainerMounted(String id) {
1628        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1629        waitForReady();
1630        warnOnNotMounted();
1631
1632        synchronized (mAsecMountSet) {
1633            return mAsecMountSet.contains(id);
1634        }
1635    }
1636
1637    public int renameSecureContainer(String oldId, String newId) {
1638        validatePermission(android.Manifest.permission.ASEC_RENAME);
1639        waitForReady();
1640        warnOnNotMounted();
1641
1642        synchronized (mAsecMountSet) {
1643            /*
1644             * Because a mounted container has active internal state which cannot be
1645             * changed while active, we must ensure both ids are not currently mounted.
1646             */
1647            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
1648                return StorageResultCode.OperationFailedStorageMounted;
1649            }
1650        }
1651
1652        int rc = StorageResultCode.OperationSucceeded;
1653        try {
1654            mConnector.execute("asec", "rename", oldId, newId);
1655        } catch (NativeDaemonConnectorException e) {
1656            rc = StorageResultCode.OperationFailedInternalError;
1657        }
1658
1659        return rc;
1660    }
1661
1662    public String getSecureContainerPath(String id) {
1663        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1664        waitForReady();
1665        warnOnNotMounted();
1666
1667        final NativeDaemonEvent event;
1668        try {
1669            event = mConnector.execute("asec", "path", id);
1670            event.checkCode(VoldResponseCode.AsecPathResult);
1671            return event.getMessage();
1672        } catch (NativeDaemonConnectorException e) {
1673            int code = e.getCode();
1674            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1675                Slog.i(TAG, String.format("Container '%s' not found", id));
1676                return null;
1677            } else {
1678                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1679            }
1680        }
1681    }
1682
1683    public String getSecureContainerFilesystemPath(String id) {
1684        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1685        waitForReady();
1686        warnOnNotMounted();
1687
1688        final NativeDaemonEvent event;
1689        try {
1690            event = mConnector.execute("asec", "fspath", id);
1691            event.checkCode(VoldResponseCode.AsecPathResult);
1692            return event.getMessage();
1693        } catch (NativeDaemonConnectorException e) {
1694            int code = e.getCode();
1695            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1696                Slog.i(TAG, String.format("Container '%s' not found", id));
1697                return null;
1698            } else {
1699                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1700            }
1701        }
1702    }
1703
1704    public void finishMediaUpdate() {
1705        mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
1706    }
1707
1708    private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
1709        if (callerUid == android.os.Process.SYSTEM_UID) {
1710            return true;
1711        }
1712
1713        if (packageName == null) {
1714            return false;
1715        }
1716
1717        final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid));
1718
1719        if (DEBUG_OBB) {
1720            Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
1721                    packageUid + ", callerUid = " + callerUid);
1722        }
1723
1724        return callerUid == packageUid;
1725    }
1726
1727    public String getMountedObbPath(String filename) {
1728        if (filename == null) {
1729            throw new IllegalArgumentException("filename cannot be null");
1730        }
1731
1732        waitForReady();
1733        warnOnNotMounted();
1734
1735        final NativeDaemonEvent event;
1736        try {
1737            event = mConnector.execute("obb", "path", filename);
1738            event.checkCode(VoldResponseCode.AsecPathResult);
1739            return event.getMessage();
1740        } catch (NativeDaemonConnectorException e) {
1741            int code = e.getCode();
1742            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1743                return null;
1744            } else {
1745                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1746            }
1747        }
1748    }
1749
1750    public boolean isObbMounted(String filename) {
1751        if (filename == null) {
1752            throw new IllegalArgumentException("filename cannot be null");
1753        }
1754
1755        synchronized (mObbMounts) {
1756            return mObbPathToStateMap.containsKey(filename);
1757        }
1758    }
1759
1760    public void mountObb(String filename, String key, IObbActionListener token, int nonce)
1761            throws RemoteException {
1762        if (filename == null) {
1763            throw new IllegalArgumentException("filename cannot be null");
1764        }
1765
1766        if (token == null) {
1767            throw new IllegalArgumentException("token cannot be null");
1768        }
1769
1770        final int callerUid = Binder.getCallingUid();
1771        final ObbState obbState = new ObbState(filename, callerUid, token, nonce);
1772        final ObbAction action = new MountObbAction(obbState, key);
1773        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1774
1775        if (DEBUG_OBB)
1776            Slog.i(TAG, "Send to OBB handler: " + action.toString());
1777    }
1778
1779    public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce)
1780            throws RemoteException {
1781        if (filename == null) {
1782            throw new IllegalArgumentException("filename cannot be null");
1783        }
1784
1785        final int callerUid = Binder.getCallingUid();
1786        final ObbState obbState = new ObbState(filename, callerUid, token, nonce);
1787        final ObbAction action = new UnmountObbAction(obbState, force);
1788        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1789
1790        if (DEBUG_OBB)
1791            Slog.i(TAG, "Send to OBB handler: " + action.toString());
1792    }
1793
1794    @Override
1795    public int getEncryptionState() {
1796        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1797                "no permission to access the crypt keeper");
1798
1799        waitForReady();
1800
1801        final NativeDaemonEvent event;
1802        try {
1803            event = mConnector.execute("cryptfs", "cryptocomplete");
1804            return Integer.parseInt(event.getMessage());
1805        } catch (NumberFormatException e) {
1806            // Bad result - unexpected.
1807            Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
1808            return ENCRYPTION_STATE_ERROR_UNKNOWN;
1809        } catch (NativeDaemonConnectorException e) {
1810            // Something bad happened.
1811            Slog.w(TAG, "Error in communicating with cryptfs in validating");
1812            return ENCRYPTION_STATE_ERROR_UNKNOWN;
1813        }
1814    }
1815
1816    @Override
1817    public int decryptStorage(String password) {
1818        if (TextUtils.isEmpty(password)) {
1819            throw new IllegalArgumentException("password cannot be empty");
1820        }
1821
1822        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1823                "no permission to access the crypt keeper");
1824
1825        waitForReady();
1826
1827        if (DEBUG_EVENTS) {
1828            Slog.i(TAG, "decrypting storage...");
1829        }
1830
1831        final NativeDaemonEvent event;
1832        try {
1833            event = mConnector.execute("cryptfs", "checkpw", password);
1834
1835            final int code = Integer.parseInt(event.getMessage());
1836            if (code == 0) {
1837                // Decrypt was successful. Post a delayed message before restarting in order
1838                // to let the UI to clear itself
1839                mHandler.postDelayed(new Runnable() {
1840                    public void run() {
1841                        try {
1842                            mConnector.execute("cryptfs", "restart");
1843                        } catch (NativeDaemonConnectorException e) {
1844                            Slog.e(TAG, "problem executing in background", e);
1845                        }
1846                    }
1847                }, 1000); // 1 second
1848            }
1849
1850            return code;
1851        } catch (NativeDaemonConnectorException e) {
1852            // Decryption failed
1853            return e.getCode();
1854        }
1855    }
1856
1857    public int encryptStorage(String password) {
1858        if (TextUtils.isEmpty(password)) {
1859            throw new IllegalArgumentException("password cannot be empty");
1860        }
1861
1862        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1863            "no permission to access the crypt keeper");
1864
1865        waitForReady();
1866
1867        if (DEBUG_EVENTS) {
1868            Slog.i(TAG, "encrypting storage...");
1869        }
1870
1871        try {
1872            mConnector.execute("cryptfs", "enablecrypto", "inplace", password);
1873        } catch (NativeDaemonConnectorException e) {
1874            // Encryption failed
1875            return e.getCode();
1876        }
1877
1878        return 0;
1879    }
1880
1881    public int changeEncryptionPassword(String password) {
1882        if (TextUtils.isEmpty(password)) {
1883            throw new IllegalArgumentException("password cannot be empty");
1884        }
1885
1886        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1887            "no permission to access the crypt keeper");
1888
1889        waitForReady();
1890
1891        if (DEBUG_EVENTS) {
1892            Slog.i(TAG, "changing encryption password...");
1893        }
1894
1895        final NativeDaemonEvent event;
1896        try {
1897            event = mConnector.execute("cryptfs", "changepw", password);
1898            return Integer.parseInt(event.getMessage());
1899        } catch (NativeDaemonConnectorException e) {
1900            // Encryption failed
1901            return e.getCode();
1902        }
1903    }
1904
1905    /**
1906     * Validate a user-supplied password string with cryptfs
1907     */
1908    @Override
1909    public int verifyEncryptionPassword(String password) throws RemoteException {
1910        // Only the system process is permitted to validate passwords
1911        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
1912            throw new SecurityException("no permission to access the crypt keeper");
1913        }
1914
1915        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1916            "no permission to access the crypt keeper");
1917
1918        if (TextUtils.isEmpty(password)) {
1919            throw new IllegalArgumentException("password cannot be empty");
1920        }
1921
1922        waitForReady();
1923
1924        if (DEBUG_EVENTS) {
1925            Slog.i(TAG, "validating encryption password...");
1926        }
1927
1928        final NativeDaemonEvent event;
1929        try {
1930            event = mConnector.execute("cryptfs", "verifypw", password);
1931            Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
1932            return Integer.parseInt(event.getMessage());
1933        } catch (NativeDaemonConnectorException e) {
1934            // Encryption failed
1935            return e.getCode();
1936        }
1937    }
1938
1939    public Parcelable[] getVolumeList() {
1940        synchronized(mVolumes) {
1941            int size = mVolumes.size();
1942            Parcelable[] result = new Parcelable[size];
1943            for (int i = 0; i < size; i++) {
1944                result[i] = mVolumes.get(i);
1945            }
1946            return result;
1947        }
1948    }
1949
1950    private void addObbStateLocked(ObbState obbState) throws RemoteException {
1951        final IBinder binder = obbState.getBinder();
1952        List<ObbState> obbStates = mObbMounts.get(binder);
1953
1954        if (obbStates == null) {
1955            obbStates = new ArrayList<ObbState>();
1956            mObbMounts.put(binder, obbStates);
1957        } else {
1958            for (final ObbState o : obbStates) {
1959                if (o.filename.equals(obbState.filename)) {
1960                    throw new IllegalStateException("Attempt to add ObbState twice. "
1961                            + "This indicates an error in the MountService logic.");
1962                }
1963            }
1964        }
1965
1966        obbStates.add(obbState);
1967        try {
1968            obbState.link();
1969        } catch (RemoteException e) {
1970            /*
1971             * The binder died before we could link it, so clean up our state
1972             * and return failure.
1973             */
1974            obbStates.remove(obbState);
1975            if (obbStates.isEmpty()) {
1976                mObbMounts.remove(binder);
1977            }
1978
1979            // Rethrow the error so mountObb can get it
1980            throw e;
1981        }
1982
1983        mObbPathToStateMap.put(obbState.filename, obbState);
1984    }
1985
1986    private void removeObbStateLocked(ObbState obbState) {
1987        final IBinder binder = obbState.getBinder();
1988        final List<ObbState> obbStates = mObbMounts.get(binder);
1989        if (obbStates != null) {
1990            if (obbStates.remove(obbState)) {
1991                obbState.unlink();
1992            }
1993            if (obbStates.isEmpty()) {
1994                mObbMounts.remove(binder);
1995            }
1996        }
1997
1998        mObbPathToStateMap.remove(obbState.filename);
1999    }
2000
2001    private class ObbActionHandler extends Handler {
2002        private boolean mBound = false;
2003        private final List<ObbAction> mActions = new LinkedList<ObbAction>();
2004
2005        ObbActionHandler(Looper l) {
2006            super(l);
2007        }
2008
2009        @Override
2010        public void handleMessage(Message msg) {
2011            switch (msg.what) {
2012                case OBB_RUN_ACTION: {
2013                    final ObbAction action = (ObbAction) msg.obj;
2014
2015                    if (DEBUG_OBB)
2016                        Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
2017
2018                    // If a bind was already initiated we don't really
2019                    // need to do anything. The pending install
2020                    // will be processed later on.
2021                    if (!mBound) {
2022                        // If this is the only one pending we might
2023                        // have to bind to the service again.
2024                        if (!connectToService()) {
2025                            Slog.e(TAG, "Failed to bind to media container service");
2026                            action.handleError();
2027                            return;
2028                        }
2029                    }
2030
2031                    mActions.add(action);
2032                    break;
2033                }
2034                case OBB_MCS_BOUND: {
2035                    if (DEBUG_OBB)
2036                        Slog.i(TAG, "OBB_MCS_BOUND");
2037                    if (msg.obj != null) {
2038                        mContainerService = (IMediaContainerService) msg.obj;
2039                    }
2040                    if (mContainerService == null) {
2041                        // Something seriously wrong. Bail out
2042                        Slog.e(TAG, "Cannot bind to media container service");
2043                        for (ObbAction action : mActions) {
2044                            // Indicate service bind error
2045                            action.handleError();
2046                        }
2047                        mActions.clear();
2048                    } else if (mActions.size() > 0) {
2049                        final ObbAction action = mActions.get(0);
2050                        if (action != null) {
2051                            action.execute(this);
2052                        }
2053                    } else {
2054                        // Should never happen ideally.
2055                        Slog.w(TAG, "Empty queue");
2056                    }
2057                    break;
2058                }
2059                case OBB_MCS_RECONNECT: {
2060                    if (DEBUG_OBB)
2061                        Slog.i(TAG, "OBB_MCS_RECONNECT");
2062                    if (mActions.size() > 0) {
2063                        if (mBound) {
2064                            disconnectService();
2065                        }
2066                        if (!connectToService()) {
2067                            Slog.e(TAG, "Failed to bind to media container service");
2068                            for (ObbAction action : mActions) {
2069                                // Indicate service bind error
2070                                action.handleError();
2071                            }
2072                            mActions.clear();
2073                        }
2074                    }
2075                    break;
2076                }
2077                case OBB_MCS_UNBIND: {
2078                    if (DEBUG_OBB)
2079                        Slog.i(TAG, "OBB_MCS_UNBIND");
2080
2081                    // Delete pending install
2082                    if (mActions.size() > 0) {
2083                        mActions.remove(0);
2084                    }
2085                    if (mActions.size() == 0) {
2086                        if (mBound) {
2087                            disconnectService();
2088                        }
2089                    } else {
2090                        // There are more pending requests in queue.
2091                        // Just post MCS_BOUND message to trigger processing
2092                        // of next pending install.
2093                        mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
2094                    }
2095                    break;
2096                }
2097                case OBB_FLUSH_MOUNT_STATE: {
2098                    final String path = (String) msg.obj;
2099
2100                    if (DEBUG_OBB)
2101                        Slog.i(TAG, "Flushing all OBB state for path " + path);
2102
2103                    synchronized (mObbMounts) {
2104                        final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
2105
2106                        final Iterator<Entry<String, ObbState>> i =
2107                                mObbPathToStateMap.entrySet().iterator();
2108                        while (i.hasNext()) {
2109                            final Entry<String, ObbState> obbEntry = i.next();
2110
2111                            /*
2112                             * If this entry's source file is in the volume path
2113                             * that got unmounted, remove it because it's no
2114                             * longer valid.
2115                             */
2116                            if (obbEntry.getKey().startsWith(path)) {
2117                                obbStatesToRemove.add(obbEntry.getValue());
2118                            }
2119                        }
2120
2121                        for (final ObbState obbState : obbStatesToRemove) {
2122                            if (DEBUG_OBB)
2123                                Slog.i(TAG, "Removing state for " + obbState.filename);
2124
2125                            removeObbStateLocked(obbState);
2126
2127                            try {
2128                                obbState.token.onObbResult(obbState.filename, obbState.nonce,
2129                                        OnObbStateChangeListener.UNMOUNTED);
2130                            } catch (RemoteException e) {
2131                                Slog.i(TAG, "Couldn't send unmount notification for  OBB: "
2132                                        + obbState.filename);
2133                            }
2134                        }
2135                    }
2136                    break;
2137                }
2138            }
2139        }
2140
2141        private boolean connectToService() {
2142            if (DEBUG_OBB)
2143                Slog.i(TAG, "Trying to bind to DefaultContainerService");
2144
2145            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
2146            if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
2147                mBound = true;
2148                return true;
2149            }
2150            return false;
2151        }
2152
2153        private void disconnectService() {
2154            mContainerService = null;
2155            mBound = false;
2156            mContext.unbindService(mDefContainerConn);
2157        }
2158    }
2159
2160    abstract class ObbAction {
2161        private static final int MAX_RETRIES = 3;
2162        private int mRetries;
2163
2164        ObbState mObbState;
2165
2166        ObbAction(ObbState obbState) {
2167            mObbState = obbState;
2168        }
2169
2170        public void execute(ObbActionHandler handler) {
2171            try {
2172                if (DEBUG_OBB)
2173                    Slog.i(TAG, "Starting to execute action: " + toString());
2174                mRetries++;
2175                if (mRetries > MAX_RETRIES) {
2176                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
2177                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2178                    handleError();
2179                    return;
2180                } else {
2181                    handleExecute();
2182                    if (DEBUG_OBB)
2183                        Slog.i(TAG, "Posting install MCS_UNBIND");
2184                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2185                }
2186            } catch (RemoteException e) {
2187                if (DEBUG_OBB)
2188                    Slog.i(TAG, "Posting install MCS_RECONNECT");
2189                mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT);
2190            } catch (Exception e) {
2191                if (DEBUG_OBB)
2192                    Slog.d(TAG, "Error handling OBB action", e);
2193                handleError();
2194                mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
2195            }
2196        }
2197
2198        abstract void handleExecute() throws RemoteException, IOException;
2199        abstract void handleError();
2200
2201        protected ObbInfo getObbInfo() throws IOException {
2202            ObbInfo obbInfo;
2203            try {
2204                obbInfo = mContainerService.getObbInfo(mObbState.filename);
2205            } catch (RemoteException e) {
2206                Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
2207                        + mObbState.filename);
2208                obbInfo = null;
2209            }
2210            if (obbInfo == null) {
2211                throw new IOException("Couldn't read OBB file: " + mObbState.filename);
2212            }
2213            return obbInfo;
2214        }
2215
2216        protected void sendNewStatusOrIgnore(int status) {
2217            if (mObbState == null || mObbState.token == null) {
2218                return;
2219            }
2220
2221            try {
2222                mObbState.token.onObbResult(mObbState.filename, mObbState.nonce, status);
2223            } catch (RemoteException e) {
2224                Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
2225            }
2226        }
2227    }
2228
2229    class MountObbAction extends ObbAction {
2230        private final String mKey;
2231
2232        MountObbAction(ObbState obbState, String key) {
2233            super(obbState);
2234            mKey = key;
2235        }
2236
2237        @Override
2238        public void handleExecute() throws IOException, RemoteException {
2239            waitForReady();
2240            warnOnNotMounted();
2241
2242            final ObbInfo obbInfo = getObbInfo();
2243
2244            if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
2245                Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename
2246                        + " which is owned by " + obbInfo.packageName);
2247                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
2248                return;
2249            }
2250
2251            final boolean isMounted;
2252            synchronized (mObbMounts) {
2253                isMounted = mObbPathToStateMap.containsKey(obbInfo.filename);
2254            }
2255            if (isMounted) {
2256                Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename);
2257                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED);
2258                return;
2259            }
2260
2261            /*
2262             * The filename passed in might not be the canonical name, so just
2263             * set the filename to the canonicalized version.
2264             */
2265            mObbState.filename = obbInfo.filename;
2266
2267            final String hashedKey;
2268            if (mKey == null) {
2269                hashedKey = "none";
2270            } else {
2271                try {
2272                    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
2273
2274                    KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt,
2275                            PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE);
2276                    SecretKey key = factory.generateSecret(ks);
2277                    BigInteger bi = new BigInteger(key.getEncoded());
2278                    hashedKey = bi.toString(16);
2279                } catch (NoSuchAlgorithmException e) {
2280                    Slog.e(TAG, "Could not load PBKDF2 algorithm", e);
2281                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2282                    return;
2283                } catch (InvalidKeySpecException e) {
2284                    Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e);
2285                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2286                    return;
2287                }
2288            }
2289
2290            int rc = StorageResultCode.OperationSucceeded;
2291            try {
2292                mConnector.execute(
2293                        "obb", "mount", mObbState.filename, hashedKey, mObbState.callerUid);
2294            } catch (NativeDaemonConnectorException e) {
2295                int code = e.getCode();
2296                if (code != VoldResponseCode.OpFailedStorageBusy) {
2297                    rc = StorageResultCode.OperationFailedInternalError;
2298                }
2299            }
2300
2301            if (rc == StorageResultCode.OperationSucceeded) {
2302                if (DEBUG_OBB)
2303                    Slog.d(TAG, "Successfully mounted OBB " + mObbState.filename);
2304
2305                synchronized (mObbMounts) {
2306                    addObbStateLocked(mObbState);
2307                }
2308
2309                sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED);
2310            } else {
2311                Slog.e(TAG, "Couldn't mount OBB file: " + rc);
2312
2313                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT);
2314            }
2315        }
2316
2317        @Override
2318        public void handleError() {
2319            sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2320        }
2321
2322        @Override
2323        public String toString() {
2324            StringBuilder sb = new StringBuilder();
2325            sb.append("MountObbAction{");
2326            sb.append("filename=");
2327            sb.append(mObbState.filename);
2328            sb.append(",callerUid=");
2329            sb.append(mObbState.callerUid);
2330            sb.append(",token=");
2331            sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL");
2332            sb.append(",binder=");
2333            sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null");
2334            sb.append('}');
2335            return sb.toString();
2336        }
2337    }
2338
2339    class UnmountObbAction extends ObbAction {
2340        private final boolean mForceUnmount;
2341
2342        UnmountObbAction(ObbState obbState, boolean force) {
2343            super(obbState);
2344            mForceUnmount = force;
2345        }
2346
2347        @Override
2348        public void handleExecute() throws IOException {
2349            waitForReady();
2350            warnOnNotMounted();
2351
2352            final ObbInfo obbInfo = getObbInfo();
2353
2354            final ObbState obbState;
2355            synchronized (mObbMounts) {
2356                obbState = mObbPathToStateMap.get(obbInfo.filename);
2357            }
2358
2359            if (obbState == null) {
2360                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED);
2361                return;
2362            }
2363
2364            if (obbState.callerUid != mObbState.callerUid) {
2365                Slog.w(TAG, "Permission denied attempting to unmount OBB " + obbInfo.filename
2366                        + " (owned by " + obbInfo.packageName + ")");
2367                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
2368                return;
2369            }
2370
2371            mObbState.filename = obbInfo.filename;
2372
2373            int rc = StorageResultCode.OperationSucceeded;
2374            try {
2375                final Command cmd = new Command("obb", "unmount", mObbState.filename);
2376                if (mForceUnmount) {
2377                    cmd.appendArg("force");
2378                }
2379                mConnector.execute(cmd);
2380            } catch (NativeDaemonConnectorException e) {
2381                int code = e.getCode();
2382                if (code == VoldResponseCode.OpFailedStorageBusy) {
2383                    rc = StorageResultCode.OperationFailedStorageBusy;
2384                } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
2385                    // If it's not mounted then we've already won.
2386                    rc = StorageResultCode.OperationSucceeded;
2387                } else {
2388                    rc = StorageResultCode.OperationFailedInternalError;
2389                }
2390            }
2391
2392            if (rc == StorageResultCode.OperationSucceeded) {
2393                synchronized (mObbMounts) {
2394                    removeObbStateLocked(obbState);
2395                }
2396
2397                sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED);
2398            } else {
2399                Slog.w(TAG, "Could not mount OBB: " + mObbState.filename);
2400                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT);
2401            }
2402        }
2403
2404        @Override
2405        public void handleError() {
2406            sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
2407        }
2408
2409        @Override
2410        public String toString() {
2411            StringBuilder sb = new StringBuilder();
2412            sb.append("UnmountObbAction{");
2413            sb.append("filename=");
2414            sb.append(mObbState.filename != null ? mObbState.filename : "null");
2415            sb.append(",force=");
2416            sb.append(mForceUnmount);
2417            sb.append(",callerUid=");
2418            sb.append(mObbState.callerUid);
2419            sb.append(",token=");
2420            sb.append(mObbState.token != null ? mObbState.token.toString() : "null");
2421            sb.append(",binder=");
2422            sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null");
2423            sb.append('}');
2424            return sb.toString();
2425        }
2426    }
2427
2428    @Override
2429    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2430        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
2431            pw.println("Permission Denial: can't dump ActivityManager from from pid="
2432                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
2433                    + " without permission " + android.Manifest.permission.DUMP);
2434            return;
2435        }
2436
2437        synchronized (mObbMounts) {
2438            pw.println("  mObbMounts:");
2439
2440            final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator();
2441            while (binders.hasNext()) {
2442                Entry<IBinder, List<ObbState>> e = binders.next();
2443                pw.print("    Key="); pw.println(e.getKey().toString());
2444                final List<ObbState> obbStates = e.getValue();
2445                for (final ObbState obbState : obbStates) {
2446                    pw.print("      "); pw.println(obbState.toString());
2447                }
2448            }
2449
2450            pw.println("");
2451            pw.println("  mObbPathToStateMap:");
2452            final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
2453            while (maps.hasNext()) {
2454                final Entry<String, ObbState> e = maps.next();
2455                pw.print("    "); pw.print(e.getKey());
2456                pw.print(" -> "); pw.println(e.getValue().toString());
2457            }
2458        }
2459
2460        pw.println("");
2461
2462        synchronized (mVolumes) {
2463            pw.println("  mVolumes:");
2464
2465            final int N = mVolumes.size();
2466            for (int i = 0; i < N; i++) {
2467                final StorageVolume v = mVolumes.get(i);
2468                pw.print("    ");
2469                pw.println(v.toString());
2470            }
2471        }
2472
2473        pw.println();
2474        pw.println("  mConnection:");
2475        mConnector.dump(fd, pw, args);
2476    }
2477
2478    /** {@inheritDoc} */
2479    public void monitor() {
2480        if (mConnector != null) {
2481            mConnector.monitor();
2482        }
2483    }
2484}
2485