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