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