MountService.java revision a02b8b05dd1e8b8cf169e1f89542ef835b11fc13
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.server.am.ActivityManagerService;
21
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.ServiceConnection;
28import android.content.pm.PackageManager;
29import android.content.res.ObbInfo;
30import android.net.Uri;
31import android.os.Binder;
32import android.os.Environment;
33import android.os.Handler;
34import android.os.HandlerThread;
35import android.os.IBinder;
36import android.os.Looper;
37import android.os.Message;
38import android.os.RemoteException;
39import android.os.ServiceManager;
40import android.os.SystemClock;
41import android.os.SystemProperties;
42import android.os.storage.IMountService;
43import android.os.storage.IMountServiceListener;
44import android.os.storage.IMountShutdownObserver;
45import android.os.storage.IObbActionListener;
46import android.os.storage.StorageResultCode;
47import android.util.Slog;
48
49import java.util.ArrayList;
50import java.util.HashMap;
51import java.util.HashSet;
52import java.util.LinkedList;
53import java.util.List;
54import java.util.Map;
55
56/**
57 * MountService implements back-end services for platform storage
58 * management.
59 * @hide - Applications should use android.os.storage.StorageManager
60 * to access the MountService.
61 */
62class MountService extends IMountService.Stub
63        implements INativeDaemonConnectorCallbacks {
64    private static final boolean LOCAL_LOGD = false;
65    private static final boolean DEBUG_UNMOUNT = false;
66    private static final boolean DEBUG_EVENTS = false;
67    private static final boolean DEBUG_OBB = true;
68
69    private static final String TAG = "MountService";
70
71    /*
72     * Internal vold volume state constants
73     */
74    class VolumeState {
75        public static final int Init       = -1;
76        public static final int NoMedia    = 0;
77        public static final int Idle       = 1;
78        public static final int Pending    = 2;
79        public static final int Checking   = 3;
80        public static final int Mounted    = 4;
81        public static final int Unmounting = 5;
82        public static final int Formatting = 6;
83        public static final int Shared     = 7;
84        public static final int SharedMnt  = 8;
85    }
86
87    /*
88     * Internal vold response code constants
89     */
90    class VoldResponseCode {
91        /*
92         * 100 series - Requestion action was initiated; expect another reply
93         *              before proceeding with a new command.
94         */
95        public static final int VolumeListResult               = 110;
96        public static final int AsecListResult                 = 111;
97        public static final int StorageUsersListResult         = 112;
98
99        /*
100         * 200 series - Requestion action has been successfully completed.
101         */
102        public static final int ShareStatusResult              = 210;
103        public static final int AsecPathResult                 = 211;
104        public static final int ShareEnabledResult             = 212;
105
106        /*
107         * 400 series - Command was accepted, but the requested action
108         *              did not take place.
109         */
110        public static final int OpFailedNoMedia                = 401;
111        public static final int OpFailedMediaBlank             = 402;
112        public static final int OpFailedMediaCorrupt           = 403;
113        public static final int OpFailedVolNotMounted          = 404;
114        public static final int OpFailedStorageBusy            = 405;
115        public static final int OpFailedStorageNotFound        = 406;
116
117        /*
118         * 600 series - Unsolicited broadcasts.
119         */
120        public static final int VolumeStateChange              = 605;
121        public static final int ShareAvailabilityChange        = 620;
122        public static final int VolumeDiskInserted             = 630;
123        public static final int VolumeDiskRemoved              = 631;
124        public static final int VolumeBadRemoval               = 632;
125    }
126
127    private Context                               mContext;
128    private NativeDaemonConnector                 mConnector;
129    private String                                mLegacyState = Environment.MEDIA_REMOVED;
130    private PackageManagerService                 mPms;
131    private boolean                               mUmsEnabling;
132    // Used as a lock for methods that register/unregister listeners.
133    final private ArrayList<MountServiceBinderListener> mListeners =
134            new ArrayList<MountServiceBinderListener>();
135    private boolean                               mBooted = false;
136    private boolean                               mReady = false;
137    private boolean                               mSendUmsConnectedOnBoot = false;
138
139    /**
140     * Private hash of currently mounted secure containers.
141     * Used as a lock in methods to manipulate secure containers.
142     */
143    final private HashSet<String> mAsecMountSet = new HashSet<String>();
144
145    /**
146     * Mounted OBB tracking information. Used to track the current state of all
147     * OBBs.
148     */
149    final private Map<IObbActionListener, ObbState> mObbMounts = new HashMap<IObbActionListener, ObbState>();
150    final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
151
152    class ObbState implements IBinder.DeathRecipient {
153        public ObbState(String filename, IObbActionListener token, int callerUid) {
154            this.filename = filename;
155            this.token = token;
156            this.callerUid = callerUid;
157            mounted = false;
158        }
159
160        // OBB source filename
161        String filename;
162
163        // Token of remote Binder caller
164        IObbActionListener token;
165
166        // Binder.callingUid()
167        public int callerUid;
168
169        // Whether this is mounted currently.
170        boolean mounted;
171
172        @Override
173        public void binderDied() {
174            ObbAction action = new UnmountObbAction(this, true);
175            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
176
177            removeObbState(this);
178
179            token.asBinder().unlinkToDeath(this, 0);
180        }
181    }
182
183    // OBB Action Handler
184    final private ObbActionHandler mObbActionHandler;
185
186    // OBB action handler messages
187    private static final int OBB_RUN_ACTION = 1;
188    private static final int OBB_MCS_BOUND = 2;
189    private static final int OBB_MCS_UNBIND = 3;
190    private static final int OBB_MCS_RECONNECT = 4;
191    private static final int OBB_MCS_GIVE_UP = 5;
192
193    /*
194     * Default Container Service information
195     */
196    static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
197            "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService");
198
199    final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
200
201    class DefaultContainerConnection implements ServiceConnection {
202        public void onServiceConnected(ComponentName name, IBinder service) {
203            if (DEBUG_OBB)
204                Slog.i(TAG, "onServiceConnected");
205            IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
206            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
207        }
208
209        public void onServiceDisconnected(ComponentName name) {
210            if (DEBUG_OBB)
211                Slog.i(TAG, "onServiceDisconnected");
212        }
213    };
214
215    // Used in the ObbActionHandler
216    private IMediaContainerService mContainerService = null;
217
218    // Handler messages
219    private static final int H_UNMOUNT_PM_UPDATE = 1;
220    private static final int H_UNMOUNT_PM_DONE = 2;
221    private static final int H_UNMOUNT_MS = 3;
222    private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
223    private static final int MAX_UNMOUNT_RETRIES = 4;
224
225    class UnmountCallBack {
226        String path;
227        int retries;
228        boolean force;
229
230        UnmountCallBack(String path, boolean force) {
231            retries = 0;
232            this.path = path;
233            this.force = force;
234        }
235
236        void handleFinished() {
237            if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path);
238            doUnmountVolume(path, true);
239        }
240    }
241
242    class UmsEnableCallBack extends UnmountCallBack {
243        String method;
244
245        UmsEnableCallBack(String path, String method, boolean force) {
246            super(path, force);
247            this.method = method;
248        }
249
250        @Override
251        void handleFinished() {
252            super.handleFinished();
253            doShareUnshareVolume(path, method, true);
254        }
255    }
256
257    class ShutdownCallBack extends UnmountCallBack {
258        IMountShutdownObserver observer;
259        ShutdownCallBack(String path, IMountShutdownObserver observer) {
260            super(path, true);
261            this.observer = observer;
262        }
263
264        @Override
265        void handleFinished() {
266            int ret = doUnmountVolume(path, true);
267            if (observer != null) {
268                try {
269                    observer.onShutDownComplete(ret);
270                } catch (RemoteException e) {
271                    Slog.w(TAG, "RemoteException when shutting down");
272                }
273            }
274        }
275    }
276
277    class MountServiceHandler extends Handler {
278        ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
279        boolean mUpdatingStatus = false;
280
281        MountServiceHandler(Looper l) {
282            super(l);
283        }
284
285        public void handleMessage(Message msg) {
286            switch (msg.what) {
287                case H_UNMOUNT_PM_UPDATE: {
288                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE");
289                    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
290                    mForceUnmounts.add(ucb);
291                    if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus);
292                    // Register only if needed.
293                    if (!mUpdatingStatus) {
294                        if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
295                        mUpdatingStatus = true;
296                        mPms.updateExternalMediaStatus(false, true);
297                    }
298                    break;
299                }
300                case H_UNMOUNT_PM_DONE: {
301                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE");
302                    if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests");
303                    mUpdatingStatus = false;
304                    int size = mForceUnmounts.size();
305                    int sizeArr[] = new int[size];
306                    int sizeArrN = 0;
307                    // Kill processes holding references first
308                    ActivityManagerService ams = (ActivityManagerService)
309                    ServiceManager.getService("activity");
310                    for (int i = 0; i < size; i++) {
311                        UnmountCallBack ucb = mForceUnmounts.get(i);
312                        String path = ucb.path;
313                        boolean done = false;
314                        if (!ucb.force) {
315                            done = true;
316                        } else {
317                            int pids[] = getStorageUsers(path);
318                            if (pids == null || pids.length == 0) {
319                                done = true;
320                            } else {
321                                // Eliminate system process here?
322                                ams.killPids(pids, "unmount media");
323                                // Confirm if file references have been freed.
324                                pids = getStorageUsers(path);
325                                if (pids == null || pids.length == 0) {
326                                    done = true;
327                                }
328                            }
329                        }
330                        if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
331                            // Retry again
332                            Slog.i(TAG, "Retrying to kill storage users again");
333                            mHandler.sendMessageDelayed(
334                                    mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
335                                            ucb.retries++),
336                                    RETRY_UNMOUNT_DELAY);
337                        } else {
338                            if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
339                                Slog.i(TAG, "Failed to unmount media inspite of " +
340                                        MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
341                            }
342                            sizeArr[sizeArrN++] = i;
343                            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
344                                    ucb));
345                        }
346                    }
347                    // Remove already processed elements from list.
348                    for (int i = (sizeArrN-1); i >= 0; i--) {
349                        mForceUnmounts.remove(sizeArr[i]);
350                    }
351                    break;
352                }
353                case H_UNMOUNT_MS : {
354                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS");
355                    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
356                    ucb.handleFinished();
357                    break;
358                }
359            }
360        }
361    };
362    final private HandlerThread mHandlerThread;
363    final private Handler mHandler;
364
365    private void waitForReady() {
366        while (mReady == false) {
367            for (int retries = 5; retries > 0; retries--) {
368                if (mReady) {
369                    return;
370                }
371                SystemClock.sleep(1000);
372            }
373            Slog.w(TAG, "Waiting too long for mReady!");
374        }
375    }
376
377    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
378        public void onReceive(Context context, Intent intent) {
379            String action = intent.getAction();
380
381            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
382                mBooted = true;
383
384                /*
385                 * In the simulator, we need to broadcast a volume mounted event
386                 * to make the media scanner run.
387                 */
388                if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
389                    notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted);
390                    return;
391                }
392                new Thread() {
393                    public void run() {
394                        try {
395                            String path = Environment.getExternalStorageDirectory().getPath();
396                            String state = getVolumeState(path);
397
398                            if (state.equals(Environment.MEDIA_UNMOUNTED)) {
399                                int rc = doMountVolume(path);
400                                if (rc != StorageResultCode.OperationSucceeded) {
401                                    Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc));
402                                }
403                            } else if (state.equals(Environment.MEDIA_SHARED)) {
404                                /*
405                                 * Bootstrap UMS enabled state since vold indicates
406                                 * the volume is shared (runtime restart while ums enabled)
407                                 */
408                                notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared);
409                            }
410
411                            /*
412                             * If UMS was connected on boot, send the connected event
413                             * now that we're up.
414                             */
415                            if (mSendUmsConnectedOnBoot) {
416                                sendUmsIntent(true);
417                                mSendUmsConnectedOnBoot = false;
418                            }
419                        } catch (Exception ex) {
420                            Slog.e(TAG, "Boot-time mount exception", ex);
421                        }
422                    }
423                }.start();
424            }
425        }
426    };
427
428    private final class MountServiceBinderListener implements IBinder.DeathRecipient {
429        final IMountServiceListener mListener;
430
431        MountServiceBinderListener(IMountServiceListener listener) {
432            mListener = listener;
433
434        }
435
436        public void binderDied() {
437            if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
438            synchronized (mListeners) {
439                mListeners.remove(this);
440                mListener.asBinder().unlinkToDeath(this, 0);
441            }
442        }
443    }
444
445    private void doShareUnshareVolume(String path, String method, boolean enable) {
446        // TODO: Add support for multiple share methods
447        if (!method.equals("ums")) {
448            throw new IllegalArgumentException(String.format("Method %s not supported", method));
449        }
450
451        try {
452            mConnector.doCommand(String.format(
453                    "volume %sshare %s %s", (enable ? "" : "un"), path, method));
454        } catch (NativeDaemonConnectorException e) {
455            Slog.e(TAG, "Failed to share/unshare", e);
456        }
457    }
458
459    private void updatePublicVolumeState(String path, String state) {
460        if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
461            Slog.w(TAG, "Multiple volumes not currently supported");
462            return;
463        }
464
465        if (mLegacyState.equals(state)) {
466            Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
467            return;
468        }
469        // Update state on PackageManager
470        if (Environment.MEDIA_UNMOUNTED.equals(state)) {
471            mPms.updateExternalMediaStatus(false, false);
472        } else if (Environment.MEDIA_MOUNTED.equals(state)) {
473            mPms.updateExternalMediaStatus(true, false);
474        }
475        String oldState = mLegacyState;
476        mLegacyState = state;
477
478        synchronized (mListeners) {
479            for (int i = mListeners.size() -1; i >= 0; i--) {
480                MountServiceBinderListener bl = mListeners.get(i);
481                try {
482                    bl.mListener.onStorageStateChanged(path, oldState, state);
483                } catch (RemoteException rex) {
484                    Slog.e(TAG, "Listener dead");
485                    mListeners.remove(i);
486                } catch (Exception ex) {
487                    Slog.e(TAG, "Listener failed", ex);
488                }
489            }
490        }
491    }
492
493    /**
494     *
495     * Callback from NativeDaemonConnector
496     */
497    public void onDaemonConnected() {
498        /*
499         * Since we'll be calling back into the NativeDaemonConnector,
500         * we need to do our work in a new thread.
501         */
502        new Thread() {
503            public void run() {
504                /**
505                 * Determine media state and UMS detection status
506                 */
507                String path = Environment.getExternalStorageDirectory().getPath();
508                String state = Environment.MEDIA_REMOVED;
509
510                try {
511                    String[] vols = mConnector.doListCommand(
512                        "volume list", VoldResponseCode.VolumeListResult);
513                    for (String volstr : vols) {
514                        String[] tok = volstr.split(" ");
515                        // FMT: <label> <mountpoint> <state>
516                        if (!tok[1].equals(path)) {
517                            Slog.w(TAG, String.format(
518                                    "Skipping unknown volume '%s'",tok[1]));
519                            continue;
520                        }
521                        int st = Integer.parseInt(tok[2]);
522                        if (st == VolumeState.NoMedia) {
523                            state = Environment.MEDIA_REMOVED;
524                        } else if (st == VolumeState.Idle) {
525                            state = Environment.MEDIA_UNMOUNTED;
526                        } else if (st == VolumeState.Mounted) {
527                            state = Environment.MEDIA_MOUNTED;
528                            Slog.i(TAG, "Media already mounted on daemon connection");
529                        } else if (st == VolumeState.Shared) {
530                            state = Environment.MEDIA_SHARED;
531                            Slog.i(TAG, "Media shared on daemon connection");
532                        } else {
533                            throw new Exception(String.format("Unexpected state %d", st));
534                        }
535                    }
536                    if (state != null) {
537                        if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
538                        updatePublicVolumeState(path, state);
539                    }
540                } catch (Exception e) {
541                    Slog.e(TAG, "Error processing initial volume state", e);
542                    updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
543                }
544
545                try {
546                    boolean avail = doGetShareMethodAvailable("ums");
547                    notifyShareAvailabilityChange("ums", avail);
548                } catch (Exception ex) {
549                    Slog.w(TAG, "Failed to get share availability");
550                }
551                /*
552                 * Now that we've done our initialization, release
553                 * the hounds!
554                 */
555                mReady = true;
556            }
557        }.start();
558    }
559
560    /**
561     * Callback from NativeDaemonConnector
562     */
563    public boolean onEvent(int code, String raw, String[] cooked) {
564        Intent in = null;
565
566        if (DEBUG_EVENTS) {
567            StringBuilder builder = new StringBuilder();
568            builder.append("onEvent::");
569            builder.append(" raw= " + raw);
570            if (cooked != null) {
571                builder.append(" cooked = " );
572                for (String str : cooked) {
573                    builder.append(" " + str);
574                }
575            }
576            Slog.i(TAG, builder.toString());
577        }
578        if (code == VoldResponseCode.VolumeStateChange) {
579            /*
580             * One of the volumes we're managing has changed state.
581             * Format: "NNN Volume <label> <path> state changed
582             * from <old_#> (<old_str>) to <new_#> (<new_str>)"
583             */
584            notifyVolumeStateChange(
585                    cooked[2], cooked[3], Integer.parseInt(cooked[7]),
586                            Integer.parseInt(cooked[10]));
587        } else if (code == VoldResponseCode.ShareAvailabilityChange) {
588            // FMT: NNN Share method <method> now <available|unavailable>
589            boolean avail = false;
590            if (cooked[5].equals("available")) {
591                avail = true;
592            }
593            notifyShareAvailabilityChange(cooked[3], avail);
594        } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
595                   (code == VoldResponseCode.VolumeDiskRemoved) ||
596                   (code == VoldResponseCode.VolumeBadRemoval)) {
597            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
598            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
599            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
600            final String label = cooked[2];
601            final String path = cooked[3];
602            int major = -1;
603            int minor = -1;
604
605            try {
606                String devComp = cooked[6].substring(1, cooked[6].length() -1);
607                String[] devTok = devComp.split(":");
608                major = Integer.parseInt(devTok[0]);
609                minor = Integer.parseInt(devTok[1]);
610            } catch (Exception ex) {
611                Slog.e(TAG, "Failed to parse major/minor", ex);
612            }
613
614            if (code == VoldResponseCode.VolumeDiskInserted) {
615                new Thread() {
616                    public void run() {
617                        try {
618                            int rc;
619                            if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
620                                Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
621                            }
622                        } catch (Exception ex) {
623                            Slog.w(TAG, "Failed to mount media on insertion", ex);
624                        }
625                    }
626                }.start();
627            } else if (code == VoldResponseCode.VolumeDiskRemoved) {
628                /*
629                 * This event gets trumped if we're already in BAD_REMOVAL state
630                 */
631                if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
632                    return true;
633                }
634                /* Send the media unmounted event first */
635                if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
636                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
637                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
638                mContext.sendBroadcast(in);
639
640                if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
641                updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
642                in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
643            } else if (code == VoldResponseCode.VolumeBadRemoval) {
644                if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
645                /* Send the media unmounted event first */
646                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
647                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
648                mContext.sendBroadcast(in);
649
650                if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
651                updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
652                in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
653            } else {
654                Slog.e(TAG, String.format("Unknown code {%d}", code));
655            }
656        } else {
657            return false;
658        }
659
660        if (in != null) {
661            mContext.sendBroadcast(in);
662        }
663        return true;
664    }
665
666    private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
667        String vs = getVolumeState(path);
668        if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs);
669
670        Intent in = null;
671
672        if (oldState == VolumeState.Shared && newState != oldState) {
673            if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
674            mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_UNSHARED,
675                                                Uri.parse("file://" + path)));
676        }
677
678        if (newState == VolumeState.Init) {
679        } else if (newState == VolumeState.NoMedia) {
680            // NoMedia is handled via Disk Remove events
681        } else if (newState == VolumeState.Idle) {
682            /*
683             * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
684             * if we're in the process of enabling UMS
685             */
686            if (!vs.equals(
687                    Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
688                            Environment.MEDIA_NOFS) && !vs.equals(
689                                    Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
690                if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
691                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
692                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
693            }
694        } else if (newState == VolumeState.Pending) {
695        } else if (newState == VolumeState.Checking) {
696            if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
697            updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
698            in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
699        } else if (newState == VolumeState.Mounted) {
700            if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
701            updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
702            in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
703            in.putExtra("read-only", false);
704        } else if (newState == VolumeState.Unmounting) {
705            in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
706        } else if (newState == VolumeState.Formatting) {
707        } else if (newState == VolumeState.Shared) {
708            if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
709            /* Send the media unmounted event first */
710            updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
711            in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
712            mContext.sendBroadcast(in);
713
714            if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
715            updatePublicVolumeState(path, Environment.MEDIA_SHARED);
716            in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
717            if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
718        } else if (newState == VolumeState.SharedMnt) {
719            Slog.e(TAG, "Live shared mounts not supported yet!");
720            return;
721        } else {
722            Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
723        }
724
725        if (in != null) {
726            mContext.sendBroadcast(in);
727        }
728    }
729
730    private boolean doGetShareMethodAvailable(String method) {
731        ArrayList<String> rsp;
732        try {
733            rsp = mConnector.doCommand("share status " + method);
734        } catch (NativeDaemonConnectorException ex) {
735            Slog.e(TAG, "Failed to determine whether share method " + method + " is available.");
736            return false;
737        }
738
739        for (String line : rsp) {
740            String[] tok = line.split(" ");
741            if (tok.length < 3) {
742                Slog.e(TAG, "Malformed response to share status " + method);
743                return false;
744            }
745
746            int code;
747            try {
748                code = Integer.parseInt(tok[0]);
749            } catch (NumberFormatException nfe) {
750                Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
751                return false;
752            }
753            if (code == VoldResponseCode.ShareStatusResult) {
754                if (tok[2].equals("available"))
755                    return true;
756                return false;
757            } else {
758                Slog.e(TAG, String.format("Unexpected response code %d", code));
759                return false;
760            }
761        }
762        Slog.e(TAG, "Got an empty response");
763        return false;
764    }
765
766    private int doMountVolume(String path) {
767        int rc = StorageResultCode.OperationSucceeded;
768
769        if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
770        try {
771            mConnector.doCommand(String.format("volume mount %s", path));
772        } catch (NativeDaemonConnectorException e) {
773            /*
774             * Mount failed for some reason
775             */
776            Intent in = null;
777            int code = e.getCode();
778            if (code == VoldResponseCode.OpFailedNoMedia) {
779                /*
780                 * Attempt to mount but no media inserted
781                 */
782                rc = StorageResultCode.OperationFailedNoMedia;
783            } else if (code == VoldResponseCode.OpFailedMediaBlank) {
784                if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs");
785                /*
786                 * Media is blank or does not contain a supported filesystem
787                 */
788                updatePublicVolumeState(path, Environment.MEDIA_NOFS);
789                in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
790                rc = StorageResultCode.OperationFailedMediaBlank;
791            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
792                if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
793                /*
794                 * Volume consistency check failed
795                 */
796                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
797                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
798                rc = StorageResultCode.OperationFailedMediaCorrupt;
799            } else {
800                rc = StorageResultCode.OperationFailedInternalError;
801            }
802
803            /*
804             * Send broadcast intent (if required for the failure)
805             */
806            if (in != null) {
807                mContext.sendBroadcast(in);
808            }
809        }
810
811        return rc;
812    }
813
814    /*
815     * If force is not set, we do not unmount if there are
816     * processes holding references to the volume about to be unmounted.
817     * If force is set, all the processes holding references need to be
818     * killed via the ActivityManager before actually unmounting the volume.
819     * This might even take a while and might be retried after timed delays
820     * to make sure we dont end up in an instable state and kill some core
821     * processes.
822     */
823    private int doUnmountVolume(String path, boolean force) {
824        if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
825            return VoldResponseCode.OpFailedVolNotMounted;
826        }
827        // Redundant probably. But no harm in updating state again.
828        mPms.updateExternalMediaStatus(false, false);
829        try {
830            mConnector.doCommand(String.format(
831                    "volume unmount %s%s", path, (force ? " force" : "")));
832            // We unmounted the volume. None of the asec containers are available now.
833            synchronized (mAsecMountSet) {
834                mAsecMountSet.clear();
835            }
836            return StorageResultCode.OperationSucceeded;
837        } catch (NativeDaemonConnectorException e) {
838            // Don't worry about mismatch in PackageManager since the
839            // call back will handle the status changes any way.
840            int code = e.getCode();
841            if (code == VoldResponseCode.OpFailedVolNotMounted) {
842                return StorageResultCode.OperationFailedStorageNotMounted;
843            } else if (code == VoldResponseCode.OpFailedStorageBusy) {
844                return StorageResultCode.OperationFailedStorageBusy;
845            } else {
846                return StorageResultCode.OperationFailedInternalError;
847            }
848        }
849    }
850
851    private int doFormatVolume(String path) {
852        try {
853            String cmd = String.format("volume format %s", path);
854            mConnector.doCommand(cmd);
855            return StorageResultCode.OperationSucceeded;
856        } catch (NativeDaemonConnectorException e) {
857            int code = e.getCode();
858            if (code == VoldResponseCode.OpFailedNoMedia) {
859                return StorageResultCode.OperationFailedNoMedia;
860            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
861                return StorageResultCode.OperationFailedMediaCorrupt;
862            } else {
863                return StorageResultCode.OperationFailedInternalError;
864            }
865        }
866    }
867
868    private boolean doGetVolumeShared(String path, String method) {
869        String cmd = String.format("volume shared %s %s", path, method);
870        ArrayList<String> rsp;
871
872        try {
873            rsp = mConnector.doCommand(cmd);
874        } catch (NativeDaemonConnectorException ex) {
875            Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method);
876            return false;
877        }
878
879        for (String line : rsp) {
880            String[] tok = line.split(" ");
881            if (tok.length < 3) {
882                Slog.e(TAG, "Malformed response to volume shared " + path + " " + method + " command");
883                return false;
884            }
885
886            int code;
887            try {
888                code = Integer.parseInt(tok[0]);
889            } catch (NumberFormatException nfe) {
890                Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
891                return false;
892            }
893            if (code == VoldResponseCode.ShareEnabledResult) {
894                return "enabled".equals(tok[2]);
895            } else {
896                Slog.e(TAG, String.format("Unexpected response code %d", code));
897                return false;
898            }
899        }
900        Slog.e(TAG, "Got an empty response");
901        return false;
902    }
903
904    private void notifyShareAvailabilityChange(String method, final boolean avail) {
905        if (!method.equals("ums")) {
906           Slog.w(TAG, "Ignoring unsupported share method {" + method + "}");
907           return;
908        }
909
910        synchronized (mListeners) {
911            for (int i = mListeners.size() -1; i >= 0; i--) {
912                MountServiceBinderListener bl = mListeners.get(i);
913                try {
914                    bl.mListener.onUsbMassStorageConnectionChanged(avail);
915                } catch (RemoteException rex) {
916                    Slog.e(TAG, "Listener dead");
917                    mListeners.remove(i);
918                } catch (Exception ex) {
919                    Slog.e(TAG, "Listener failed", ex);
920                }
921            }
922        }
923
924        if (mBooted == true) {
925            sendUmsIntent(avail);
926        } else {
927            mSendUmsConnectedOnBoot = avail;
928        }
929
930        final String path = Environment.getExternalStorageDirectory().getPath();
931        if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) {
932            /*
933             * USB mass storage disconnected while enabled
934             */
935            new Thread() {
936                public void run() {
937                    try {
938                        int rc;
939                        Slog.w(TAG, "Disabling UMS after cable disconnect");
940                        doShareUnshareVolume(path, "ums", false);
941                        if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
942                            Slog.e(TAG, String.format(
943                                    "Failed to remount {%s} on UMS enabled-disconnect (%d)",
944                                            path, rc));
945                        }
946                    } catch (Exception ex) {
947                        Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
948                    }
949                }
950            }.start();
951        }
952    }
953
954    private void sendUmsIntent(boolean c) {
955        mContext.sendBroadcast(
956                new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)));
957    }
958
959    private void validatePermission(String perm) {
960        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
961            throw new SecurityException(String.format("Requires %s permission", perm));
962        }
963    }
964
965    /**
966     * Constructs a new MountService instance
967     *
968     * @param context  Binder context for this service
969     */
970    public MountService(Context context) {
971        mContext = context;
972
973        // XXX: This will go away soon in favor of IMountServiceObserver
974        mPms = (PackageManagerService) ServiceManager.getService("package");
975
976        mContext.registerReceiver(mBroadcastReceiver,
977                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
978
979        mHandlerThread = new HandlerThread("MountService");
980        mHandlerThread.start();
981        mHandler = new MountServiceHandler(mHandlerThread.getLooper());
982
983        // Add OBB Action Handler to MountService thread.
984        mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
985
986        /*
987         * Vold does not run in the simulator, so pretend the connector thread
988         * ran and did its thing.
989         */
990        if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
991            mReady = true;
992            mUmsEnabling = true;
993            return;
994        }
995
996        mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
997        mReady = false;
998        Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
999        thread.start();
1000    }
1001
1002    /**
1003     * Exposed API calls below here
1004     */
1005
1006    public void registerListener(IMountServiceListener listener) {
1007        synchronized (mListeners) {
1008            MountServiceBinderListener bl = new MountServiceBinderListener(listener);
1009            try {
1010                listener.asBinder().linkToDeath(bl, 0);
1011                mListeners.add(bl);
1012            } catch (RemoteException rex) {
1013                Slog.e(TAG, "Failed to link to listener death");
1014            }
1015        }
1016    }
1017
1018    public void unregisterListener(IMountServiceListener listener) {
1019        synchronized (mListeners) {
1020            for(MountServiceBinderListener bl : mListeners) {
1021                if (bl.mListener == listener) {
1022                    mListeners.remove(mListeners.indexOf(bl));
1023                    return;
1024                }
1025            }
1026        }
1027    }
1028
1029    public void shutdown(final IMountShutdownObserver observer) {
1030        validatePermission(android.Manifest.permission.SHUTDOWN);
1031
1032        Slog.i(TAG, "Shutting down");
1033
1034        String path = Environment.getExternalStorageDirectory().getPath();
1035        String state = getVolumeState(path);
1036
1037        if (state.equals(Environment.MEDIA_SHARED)) {
1038            /*
1039             * If the media is currently shared, unshare it.
1040             * XXX: This is still dangerous!. We should not
1041             * be rebooting at *all* if UMS is enabled, since
1042             * the UMS host could have dirty FAT cache entries
1043             * yet to flush.
1044             */
1045            setUsbMassStorageEnabled(false);
1046        } else if (state.equals(Environment.MEDIA_CHECKING)) {
1047            /*
1048             * If the media is being checked, then we need to wait for
1049             * it to complete before being able to proceed.
1050             */
1051            // XXX: @hackbod - Should we disable the ANR timer here?
1052            int retries = 30;
1053            while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
1054                try {
1055                    Thread.sleep(1000);
1056                } catch (InterruptedException iex) {
1057                    Slog.e(TAG, "Interrupted while waiting for media", iex);
1058                    break;
1059                }
1060                state = Environment.getExternalStorageState();
1061            }
1062            if (retries == 0) {
1063                Slog.e(TAG, "Timed out waiting for media to check");
1064            }
1065        }
1066
1067        if (state.equals(Environment.MEDIA_MOUNTED)) {
1068            // Post a unmount message.
1069            ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
1070            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
1071        }
1072    }
1073
1074    private boolean getUmsEnabling() {
1075        synchronized (mListeners) {
1076            return mUmsEnabling;
1077        }
1078    }
1079
1080    private void setUmsEnabling(boolean enable) {
1081        synchronized (mListeners) {
1082            mUmsEnabling = true;
1083        }
1084    }
1085
1086    public boolean isUsbMassStorageConnected() {
1087        waitForReady();
1088
1089        if (getUmsEnabling()) {
1090            return true;
1091        }
1092        return doGetShareMethodAvailable("ums");
1093    }
1094
1095    public void setUsbMassStorageEnabled(boolean enable) {
1096        waitForReady();
1097        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1098
1099        // TODO: Add support for multiple share methods
1100
1101        /*
1102         * If the volume is mounted and we're enabling then unmount it
1103         */
1104        String path = Environment.getExternalStorageDirectory().getPath();
1105        String vs = getVolumeState(path);
1106        String method = "ums";
1107        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
1108            // Override for isUsbMassStorageEnabled()
1109            setUmsEnabling(enable);
1110            UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
1111            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
1112            // Clear override
1113            setUmsEnabling(false);
1114        }
1115        /*
1116         * If we disabled UMS then mount the volume
1117         */
1118        if (!enable) {
1119            doShareUnshareVolume(path, method, enable);
1120            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
1121                Slog.e(TAG, "Failed to remount " + path +
1122                        " after disabling share method " + method);
1123                /*
1124                 * Even though the mount failed, the unshare didn't so don't indicate an error.
1125                 * The mountVolume() call will have set the storage state and sent the necessary
1126                 * broadcasts.
1127                 */
1128            }
1129        }
1130    }
1131
1132    public boolean isUsbMassStorageEnabled() {
1133        waitForReady();
1134        return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
1135    }
1136
1137    /**
1138     * @return state of the volume at the specified mount point
1139     */
1140    public String getVolumeState(String mountPoint) {
1141        /*
1142         * XXX: Until we have multiple volume discovery, just hardwire
1143         * this to /sdcard
1144         */
1145        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
1146            Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
1147            throw new IllegalArgumentException();
1148        }
1149
1150        return mLegacyState;
1151    }
1152
1153    public int mountVolume(String path) {
1154        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1155
1156        waitForReady();
1157        return doMountVolume(path);
1158    }
1159
1160    public void unmountVolume(String path, boolean force) {
1161        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1162        waitForReady();
1163
1164        String volState = getVolumeState(path);
1165        if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path + " force = " + force);
1166        if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
1167                Environment.MEDIA_REMOVED.equals(volState) ||
1168                Environment.MEDIA_SHARED.equals(volState) ||
1169                Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
1170            // Media already unmounted or cannot be unmounted.
1171            // TODO return valid return code when adding observer call back.
1172            return;
1173        }
1174        UnmountCallBack ucb = new UnmountCallBack(path, force);
1175        mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
1176    }
1177
1178    public int formatVolume(String path) {
1179        validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
1180        waitForReady();
1181
1182        return doFormatVolume(path);
1183    }
1184
1185    public int []getStorageUsers(String path) {
1186        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
1187        waitForReady();
1188        try {
1189            String[] r = mConnector.doListCommand(
1190                    String.format("storage users %s", path),
1191                            VoldResponseCode.StorageUsersListResult);
1192            // FMT: <pid> <process name>
1193            int[] data = new int[r.length];
1194            for (int i = 0; i < r.length; i++) {
1195                String []tok = r[i].split(" ");
1196                try {
1197                    data[i] = Integer.parseInt(tok[0]);
1198                } catch (NumberFormatException nfe) {
1199                    Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
1200                    return new int[0];
1201                }
1202            }
1203            return data;
1204        } catch (NativeDaemonConnectorException e) {
1205            Slog.e(TAG, "Failed to retrieve storage users list", e);
1206            return new int[0];
1207        }
1208    }
1209
1210    private void warnOnNotMounted() {
1211        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1212            Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
1213        }
1214    }
1215
1216    public String[] getSecureContainerList() {
1217        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1218        waitForReady();
1219        warnOnNotMounted();
1220
1221        try {
1222            return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
1223        } catch (NativeDaemonConnectorException e) {
1224            return new String[0];
1225        }
1226    }
1227
1228    public int createSecureContainer(String id, int sizeMb, String fstype,
1229                                    String key, int ownerUid) {
1230        validatePermission(android.Manifest.permission.ASEC_CREATE);
1231        waitForReady();
1232        warnOnNotMounted();
1233
1234        int rc = StorageResultCode.OperationSucceeded;
1235        String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
1236        try {
1237            mConnector.doCommand(cmd);
1238        } catch (NativeDaemonConnectorException e) {
1239            rc = StorageResultCode.OperationFailedInternalError;
1240        }
1241
1242        if (rc == StorageResultCode.OperationSucceeded) {
1243            synchronized (mAsecMountSet) {
1244                mAsecMountSet.add(id);
1245            }
1246        }
1247        return rc;
1248    }
1249
1250    public int finalizeSecureContainer(String id) {
1251        validatePermission(android.Manifest.permission.ASEC_CREATE);
1252        warnOnNotMounted();
1253
1254        int rc = StorageResultCode.OperationSucceeded;
1255        try {
1256            mConnector.doCommand(String.format("asec finalize %s", id));
1257            /*
1258             * Finalization does a remount, so no need
1259             * to update mAsecMountSet
1260             */
1261        } catch (NativeDaemonConnectorException e) {
1262            rc = StorageResultCode.OperationFailedInternalError;
1263        }
1264        return rc;
1265    }
1266
1267    public int destroySecureContainer(String id, boolean force) {
1268        validatePermission(android.Manifest.permission.ASEC_DESTROY);
1269        waitForReady();
1270        warnOnNotMounted();
1271
1272        int rc = StorageResultCode.OperationSucceeded;
1273        try {
1274            mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : "")));
1275        } catch (NativeDaemonConnectorException e) {
1276            int code = e.getCode();
1277            if (code == VoldResponseCode.OpFailedStorageBusy) {
1278                rc = StorageResultCode.OperationFailedStorageBusy;
1279            } else {
1280                rc = StorageResultCode.OperationFailedInternalError;
1281            }
1282        }
1283
1284        if (rc == StorageResultCode.OperationSucceeded) {
1285            synchronized (mAsecMountSet) {
1286                if (mAsecMountSet.contains(id)) {
1287                    mAsecMountSet.remove(id);
1288                }
1289            }
1290        }
1291
1292        return rc;
1293    }
1294
1295    public int mountSecureContainer(String id, String key, int ownerUid) {
1296        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1297        waitForReady();
1298        warnOnNotMounted();
1299
1300        synchronized (mAsecMountSet) {
1301            if (mAsecMountSet.contains(id)) {
1302                return StorageResultCode.OperationFailedStorageMounted;
1303            }
1304        }
1305
1306        int rc = StorageResultCode.OperationSucceeded;
1307        String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
1308        try {
1309            mConnector.doCommand(cmd);
1310        } catch (NativeDaemonConnectorException e) {
1311            int code = e.getCode();
1312            if (code != VoldResponseCode.OpFailedStorageBusy) {
1313                rc = StorageResultCode.OperationFailedInternalError;
1314            }
1315        }
1316
1317        if (rc == StorageResultCode.OperationSucceeded) {
1318            synchronized (mAsecMountSet) {
1319                mAsecMountSet.add(id);
1320            }
1321        }
1322        return rc;
1323    }
1324
1325    public int unmountSecureContainer(String id, boolean force) {
1326        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
1327        waitForReady();
1328        warnOnNotMounted();
1329
1330        synchronized (mAsecMountSet) {
1331            if (!mAsecMountSet.contains(id)) {
1332                return StorageResultCode.OperationFailedStorageNotMounted;
1333            }
1334         }
1335
1336        int rc = StorageResultCode.OperationSucceeded;
1337        String cmd = String.format("asec unmount %s%s", id, (force ? " force" : ""));
1338        try {
1339            mConnector.doCommand(cmd);
1340        } catch (NativeDaemonConnectorException e) {
1341            int code = e.getCode();
1342            if (code == VoldResponseCode.OpFailedStorageBusy) {
1343                rc = StorageResultCode.OperationFailedStorageBusy;
1344            } else {
1345                rc = StorageResultCode.OperationFailedInternalError;
1346            }
1347        }
1348
1349        if (rc == StorageResultCode.OperationSucceeded) {
1350            synchronized (mAsecMountSet) {
1351                mAsecMountSet.remove(id);
1352            }
1353        }
1354        return rc;
1355    }
1356
1357    public boolean isSecureContainerMounted(String id) {
1358        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1359        waitForReady();
1360        warnOnNotMounted();
1361
1362        synchronized (mAsecMountSet) {
1363            return mAsecMountSet.contains(id);
1364        }
1365    }
1366
1367    public int renameSecureContainer(String oldId, String newId) {
1368        validatePermission(android.Manifest.permission.ASEC_RENAME);
1369        waitForReady();
1370        warnOnNotMounted();
1371
1372        synchronized (mAsecMountSet) {
1373            /*
1374             * Because a mounted container has active internal state which cannot be
1375             * changed while active, we must ensure both ids are not currently mounted.
1376             */
1377            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
1378                return StorageResultCode.OperationFailedStorageMounted;
1379            }
1380        }
1381
1382        int rc = StorageResultCode.OperationSucceeded;
1383        String cmd = String.format("asec rename %s %s", oldId, newId);
1384        try {
1385            mConnector.doCommand(cmd);
1386        } catch (NativeDaemonConnectorException e) {
1387            rc = StorageResultCode.OperationFailedInternalError;
1388        }
1389
1390        return rc;
1391    }
1392
1393    public String getSecureContainerPath(String id) {
1394        validatePermission(android.Manifest.permission.ASEC_ACCESS);
1395        waitForReady();
1396        warnOnNotMounted();
1397
1398        try {
1399            ArrayList<String> rsp = mConnector.doCommand(String.format("asec path %s", id));
1400            String []tok = rsp.get(0).split(" ");
1401            int code = Integer.parseInt(tok[0]);
1402            if (code != VoldResponseCode.AsecPathResult) {
1403                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1404            }
1405            return tok[1];
1406        } catch (NativeDaemonConnectorException e) {
1407            int code = e.getCode();
1408            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1409                throw new IllegalArgumentException(String.format("Container '%s' not found", id));
1410            } else {
1411                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1412            }
1413        }
1414    }
1415
1416    public void finishMediaUpdate() {
1417        mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
1418    }
1419
1420    private boolean isCallerOwnerOfPackageOrSystem(String packageName) {
1421        final int callerUid = Binder.getCallingUid();
1422        return isUidOwnerOfPackageOrSystem(packageName, callerUid);
1423    }
1424
1425    private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
1426        if (callerUid == android.os.Process.SYSTEM_UID) {
1427            return true;
1428        }
1429
1430        if (packageName == null) {
1431            return false;
1432        }
1433
1434        final int packageUid = mPms.getPackageUid(packageName);
1435
1436        if (DEBUG_OBB) {
1437            Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
1438                    packageUid + ", callerUid = " + callerUid);
1439        }
1440
1441        return callerUid == packageUid;
1442    }
1443
1444    public String getMountedObbPath(String filename) {
1445        waitForReady();
1446        warnOnNotMounted();
1447
1448        try {
1449            ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename));
1450            String []tok = rsp.get(0).split(" ");
1451            int code = Integer.parseInt(tok[0]);
1452            if (code != VoldResponseCode.AsecPathResult) {
1453                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1454            }
1455            return tok[1];
1456        } catch (NativeDaemonConnectorException e) {
1457            int code = e.getCode();
1458            if (code == VoldResponseCode.OpFailedStorageNotFound) {
1459                return null;
1460            } else {
1461                throw new IllegalStateException(String.format("Unexpected response code %d", code));
1462            }
1463        }
1464    }
1465
1466    public boolean isObbMounted(String filename) {
1467        synchronized (mObbMounts) {
1468            return mObbPathToStateMap.containsKey(filename);
1469        }
1470    }
1471
1472    public void mountObb(String filename, String key, IObbActionListener token) {
1473        waitForReady();
1474        warnOnNotMounted();
1475
1476        final ObbState obbState;
1477
1478        synchronized (mObbMounts) {
1479            if (isObbMounted(filename)) {
1480                throw new IllegalArgumentException("OBB file is already mounted");
1481            }
1482
1483            if (mObbMounts.containsKey(token)) {
1484                throw new IllegalArgumentException("You may only have one OBB mounted at a time");
1485            }
1486
1487            final int callerUid = Binder.getCallingUid();
1488            obbState = new ObbState(filename, token, callerUid);
1489            addObbState(obbState);
1490        }
1491
1492        try {
1493            token.asBinder().linkToDeath(obbState, 0);
1494        } catch (RemoteException rex) {
1495            Slog.e(TAG, "Failed to link to listener death");
1496        }
1497
1498        MountObbAction action = new MountObbAction(obbState, key);
1499        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1500
1501        if (DEBUG_OBB)
1502            Slog.i(TAG, "Send to OBB handler: " + action.toString());
1503    }
1504
1505    public void unmountObb(String filename, boolean force, IObbActionListener token) {
1506        final ObbState obbState;
1507
1508        synchronized (mObbMounts) {
1509            if (!isObbMounted(filename)) {
1510                throw new IllegalArgumentException("OBB is not mounted");
1511            }
1512            obbState = mObbPathToStateMap.get(filename);
1513        }
1514
1515        UnmountObbAction action = new UnmountObbAction(obbState, force);
1516        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
1517
1518        if (DEBUG_OBB)
1519            Slog.i(TAG, "Send to OBB handler: " + action.toString());
1520    }
1521
1522    private void addObbState(ObbState obbState) {
1523        synchronized (mObbMounts) {
1524            mObbMounts.put(obbState.token, obbState);
1525            mObbPathToStateMap.put(obbState.filename, obbState);
1526        }
1527    }
1528
1529    private void removeObbState(ObbState obbState) {
1530        synchronized (mObbMounts) {
1531            mObbMounts.remove(obbState.token);
1532            mObbPathToStateMap.remove(obbState.filename);
1533        }
1534    }
1535
1536    private class ObbActionHandler extends Handler {
1537        private boolean mBound = false;
1538        private List<ObbAction> mActions = new LinkedList<ObbAction>();
1539
1540        ObbActionHandler(Looper l) {
1541            super(l);
1542        }
1543
1544        @Override
1545        public void handleMessage(Message msg) {
1546            switch (msg.what) {
1547                case OBB_RUN_ACTION: {
1548                    ObbAction action = (ObbAction) msg.obj;
1549
1550                    if (DEBUG_OBB)
1551                        Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
1552
1553                    // If a bind was already initiated we don't really
1554                    // need to do anything. The pending install
1555                    // will be processed later on.
1556                    if (!mBound) {
1557                        // If this is the only one pending we might
1558                        // have to bind to the service again.
1559                        if (!connectToService()) {
1560                            Slog.e(TAG, "Failed to bind to media container service");
1561                            action.handleError();
1562                            return;
1563                        } else {
1564                            // Once we bind to the service, the first
1565                            // pending request will be processed.
1566                            mActions.add(action);
1567                        }
1568                    } else {
1569                        // Already bound to the service. Just make
1570                        // sure we trigger off processing the first request.
1571                        if (mActions.size() == 0) {
1572                            mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
1573                        }
1574
1575                        mActions.add(action);
1576                    }
1577                    break;
1578                }
1579                case OBB_MCS_BOUND: {
1580                    if (DEBUG_OBB)
1581                        Slog.i(TAG, "OBB_MCS_BOUND");
1582                    if (msg.obj != null) {
1583                        mContainerService = (IMediaContainerService) msg.obj;
1584                    }
1585                    if (mContainerService == null) {
1586                        // Something seriously wrong. Bail out
1587                        Slog.e(TAG, "Cannot bind to media container service");
1588                        for (ObbAction action : mActions) {
1589                            // Indicate service bind error
1590                            action.handleError();
1591                        }
1592                        mActions.clear();
1593                    } else if (mActions.size() > 0) {
1594                        ObbAction action = mActions.get(0);
1595                        if (action != null) {
1596                            action.execute(this);
1597                        }
1598                    } else {
1599                        // Should never happen ideally.
1600                        Slog.w(TAG, "Empty queue");
1601                    }
1602                    break;
1603                }
1604                case OBB_MCS_RECONNECT: {
1605                    if (DEBUG_OBB)
1606                        Slog.i(TAG, "OBB_MCS_RECONNECT");
1607                    if (mActions.size() > 0) {
1608                        if (mBound) {
1609                            disconnectService();
1610                        }
1611                        if (!connectToService()) {
1612                            Slog.e(TAG, "Failed to bind to media container service");
1613                            for (ObbAction action : mActions) {
1614                                // Indicate service bind error
1615                                action.handleError();
1616                            }
1617                            mActions.clear();
1618                        }
1619                    }
1620                    break;
1621                }
1622                case OBB_MCS_UNBIND: {
1623                    if (DEBUG_OBB)
1624                        Slog.i(TAG, "OBB_MCS_UNBIND");
1625
1626                    // Delete pending install
1627                    if (mActions.size() > 0) {
1628                        mActions.remove(0);
1629                    }
1630                    if (mActions.size() == 0) {
1631                        if (mBound) {
1632                            disconnectService();
1633                        }
1634                    } else {
1635                        // There are more pending requests in queue.
1636                        // Just post MCS_BOUND message to trigger processing
1637                        // of next pending install.
1638                        mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
1639                    }
1640                    break;
1641                }
1642                case OBB_MCS_GIVE_UP: {
1643                    if (DEBUG_OBB)
1644                        Slog.i(TAG, "OBB_MCS_GIVE_UP");
1645                    mActions.remove(0);
1646                    break;
1647                }
1648            }
1649        }
1650
1651        private boolean connectToService() {
1652            if (DEBUG_OBB)
1653                Slog.i(TAG, "Trying to bind to DefaultContainerService");
1654
1655            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
1656            if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
1657                mBound = true;
1658                return true;
1659            }
1660            return false;
1661        }
1662
1663        private void disconnectService() {
1664            mContainerService = null;
1665            mBound = false;
1666            mContext.unbindService(mDefContainerConn);
1667        }
1668    }
1669
1670    abstract class ObbAction {
1671        private static final int MAX_RETRIES = 3;
1672        private int mRetries;
1673
1674        ObbState mObbState;
1675
1676        ObbAction(ObbState obbState) {
1677            mObbState = obbState;
1678        }
1679
1680        public void execute(ObbActionHandler handler) {
1681            try {
1682                if (DEBUG_OBB)
1683                    Slog.i(TAG, "Starting to execute action: " + this.toString());
1684                mRetries++;
1685                if (mRetries > MAX_RETRIES) {
1686                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
1687                    mObbActionHandler.sendEmptyMessage(OBB_MCS_GIVE_UP);
1688                    handleError();
1689                    return;
1690                } else {
1691                    handleExecute();
1692                    if (DEBUG_OBB)
1693                        Slog.i(TAG, "Posting install MCS_UNBIND");
1694                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
1695                }
1696            } catch (RemoteException e) {
1697                if (DEBUG_OBB)
1698                    Slog.i(TAG, "Posting install MCS_RECONNECT");
1699                mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT);
1700            } catch (Exception e) {
1701                if (DEBUG_OBB)
1702                    Slog.d(TAG, "Error handling OBB action", e);
1703                handleError();
1704            }
1705        }
1706
1707        abstract void handleExecute() throws RemoteException;
1708        abstract void handleError();
1709    }
1710
1711    class MountObbAction extends ObbAction {
1712        private String mKey;
1713
1714        MountObbAction(ObbState obbState, String key) {
1715            super(obbState);
1716            mKey = key;
1717        }
1718
1719        public void handleExecute() throws RemoteException {
1720            ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
1721            if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
1722                throw new IllegalArgumentException("Caller package does not match OBB file");
1723            }
1724
1725            if (mKey == null) {
1726                mKey = "none";
1727            }
1728
1729            int rc = StorageResultCode.OperationSucceeded;
1730            String cmd = String.format("obb mount %s %s %d", mObbState.filename, mKey,
1731                    mObbState.callerUid);
1732            try {
1733                mConnector.doCommand(cmd);
1734            } catch (NativeDaemonConnectorException e) {
1735                int code = e.getCode();
1736                if (code != VoldResponseCode.OpFailedStorageBusy) {
1737                    rc = StorageResultCode.OperationFailedInternalError;
1738                }
1739            }
1740
1741            if (rc == StorageResultCode.OperationSucceeded) {
1742                try {
1743                    mObbState.token.onObbResult(mObbState.filename, "mounted");
1744                } catch (RemoteException e) {
1745                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
1746                }
1747            } else {
1748                Slog.e(TAG, "Couldn't mount OBB file");
1749
1750                // We didn't succeed, so remove this from the mount-set.
1751                removeObbState(mObbState);
1752            }
1753        }
1754
1755        public void handleError() {
1756            removeObbState(mObbState);
1757
1758            try {
1759                mObbState.token.onObbResult(mObbState.filename, "error");
1760            } catch (RemoteException e) {
1761                Slog.e(TAG, "Couldn't send back OBB mount error for " + mObbState.filename);
1762            }
1763        }
1764
1765        @Override
1766        public String toString() {
1767            StringBuilder sb = new StringBuilder();
1768            sb.append("MountObbAction{");
1769            sb.append("filename=");
1770            sb.append(mObbState.filename);
1771            sb.append(",callerUid=");
1772            sb.append(mObbState.callerUid);
1773            sb.append(",token=");
1774            sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL");
1775            sb.append('}');
1776            return sb.toString();
1777        }
1778    }
1779
1780    class UnmountObbAction extends ObbAction {
1781        private boolean mForceUnmount;
1782
1783        UnmountObbAction(ObbState obbState, boolean force) {
1784            super(obbState);
1785            mForceUnmount = force;
1786        }
1787
1788        public void handleExecute() throws RemoteException {
1789            ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
1790
1791            if (!isCallerOwnerOfPackageOrSystem(obbInfo.packageName)) {
1792                throw new IllegalArgumentException("Caller package does not match OBB file");
1793            }
1794
1795            int rc = StorageResultCode.OperationSucceeded;
1796            String cmd = String.format("obb unmount %s%s", mObbState.filename,
1797                    (mForceUnmount ? " force" : ""));
1798            try {
1799                mConnector.doCommand(cmd);
1800            } catch (NativeDaemonConnectorException e) {
1801                int code = e.getCode();
1802                if (code == VoldResponseCode.OpFailedStorageBusy) {
1803                    rc = StorageResultCode.OperationFailedStorageBusy;
1804                } else {
1805                    rc = StorageResultCode.OperationFailedInternalError;
1806                }
1807            }
1808
1809            if (rc == StorageResultCode.OperationSucceeded) {
1810                removeObbState(mObbState);
1811
1812                try {
1813                    mObbState.token.onObbResult(mObbState.filename, "unmounted");
1814                } catch (RemoteException e) {
1815                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
1816                }
1817            } else {
1818                try {
1819                    mObbState.token.onObbResult(mObbState.filename, "error");
1820                } catch (RemoteException e) {
1821                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
1822                }
1823            }
1824        }
1825
1826        public void handleError() {
1827            removeObbState(mObbState);
1828
1829            try {
1830                mObbState.token.onObbResult(mObbState.filename, "error");
1831            } catch (RemoteException e) {
1832                Slog.e(TAG, "Couldn't send back OBB unmount error for " + mObbState.filename);
1833            }
1834        }
1835
1836        @Override
1837        public String toString() {
1838            StringBuilder sb = new StringBuilder();
1839            sb.append("UnmountObbAction{");
1840            sb.append("filename=");
1841            sb.append(mObbState.filename != null ? mObbState.filename : "null");
1842            sb.append(",force=");
1843            sb.append(mForceUnmount);
1844            sb.append(",callerUid=");
1845            sb.append(mObbState.callerUid);
1846            sb.append(",token=");
1847            sb.append(mObbState.token != null ? mObbState.token.toString() : "null");
1848            sb.append('}');
1849            return sb.toString();
1850        }
1851    }
1852}
1853
1854