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