MountService.java revision f919cd02dfd40ad1939e429c3f5e7e36538d839e
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 android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.pm.PackageManager;
24import android.content.res.Resources;
25import android.net.Uri;
26import android.os.IMountService;
27import android.os.IMountServiceListener;
28import android.os.MountServiceResultCode;
29import android.os.RemoteException;
30import android.os.IBinder;
31import android.os.Environment;
32import android.os.ServiceManager;
33import android.os.SystemProperties;
34import android.os.UEventObserver;
35import android.os.Handler;
36import android.text.TextUtils;
37import android.util.Log;
38import java.util.ArrayList;
39
40import java.io.File;
41import java.io.FileReader;
42
43/**
44 * MountService implements an to the mount service daemon
45 * @hide
46 */
47class MountService extends IMountService.Stub
48        implements INativeDaemonConnectorCallbacks {
49
50    private static final String TAG = "MountService";
51
52    /*
53     * Internal vold volume state constants
54     */
55    class VolumeState {
56        public static final int Init       = -1;
57        public static final int NoMedia    = 0;
58        public static final int Idle       = 1;
59        public static final int Pending    = 2;
60        public static final int Checking   = 3;
61        public static final int Mounted    = 4;
62        public static final int Unmounting = 5;
63        public static final int Formatting = 6;
64        public static final int Shared     = 7;
65        public static final int SharedMnt  = 8;
66    }
67
68    /*
69     * Internal vold response code constants
70     */
71    class VoldResponseCode {
72        /*
73         * 100 series - Requestion action was initiated; expect another reply
74         *              before proceeding with a new command.
75         */
76        public static final int VolumeListResult               = 110;
77        public static final int AsecListResult                 = 111;
78
79        /*
80         * 200 series - Requestion action has been successfully completed.
81         */
82        public static final int ShareStatusResult              = 210;
83        public static final int AsecPathResult                 = 211;
84        public static final int ShareEnabledResult             = 212;
85
86        /*
87         * 400 series - Command was accepted, but the requested action
88         *              did not take place.
89         */
90        public static final int OpFailedNoMedia                = 401;
91        public static final int OpFailedMediaBlank             = 402;
92        public static final int OpFailedMediaCorrupt           = 403;
93        public static final int OpFailedVolNotMounted          = 404;
94        public static final int OpFailedVolBusy                = 405;
95
96        /*
97         * 600 series - Unsolicited broadcasts.
98         */
99        public static final int VolumeStateChange              = 605;
100        public static final int ShareAvailabilityChange        = 620;
101        public static final int VolumeDiskInserted             = 630;
102        public static final int VolumeDiskRemoved              = 631;
103        public static final int VolumeBadRemoval               = 632;
104    }
105
106    private Context                               mContext;
107    private NativeDaemonConnector                 mConnector;
108    private String                                mLegacyState = Environment.MEDIA_REMOVED;
109    private PackageManagerService                 mPms;
110    private boolean                               mUmsEnabling;
111    private ArrayList<MountServiceBinderListener> mListeners;
112
113    /**
114     * Constructs a new MountService instance
115     *
116     * @param context  Binder context for this service
117     */
118    public MountService(Context context) {
119        mContext = context;
120
121        // XXX: This will go away soon in favor of IMountServiceObserver
122        mPms = (PackageManagerService) ServiceManager.getService("package");
123
124        // Register a BOOT_COMPLETED handler so that we can start
125        // our NativeDaemonConnector. We defer the startup so that we don't
126        // start processing events before we ought-to
127        mContext.registerReceiver(mBroadcastReceiver,
128                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
129
130        mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
131        mListeners = new ArrayList<MountServiceBinderListener>();
132    }
133
134    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
135        public void onReceive(Context context, Intent intent) {
136            String action = intent.getAction();
137
138            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
139                /*
140                 * Vold does not run in the simulator, so fake out a mounted
141                 * event to trigger MediaScanner
142                 */
143                if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
144                    updatePublicVolumeState("/sdcard", Environment.MEDIA_MOUNTED);
145                    return;
146                }
147
148                Thread thread = new Thread(
149                        mConnector, NativeDaemonConnector.class.getName());
150                thread.start();
151            }
152        }
153    };
154
155    private final class MountServiceBinderListener implements IBinder.DeathRecipient {
156        final IMountServiceListener mListener;
157
158        MountServiceBinderListener(IMountServiceListener listener) {
159            mListener = listener;
160
161        }
162
163        public void binderDied() {
164            Log.d(TAG, "An IMountServiceListener has died!");
165            synchronized(mListeners) {
166                mListeners.remove(this);
167                mListener.asBinder().unlinkToDeath(this, 0);
168            }
169        }
170    }
171
172    int doShareUnshareVolume(String path, String method, boolean enable) {
173        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
174
175        // TODO: Add support for multiple share methods
176        if (!method.equals("ums")) {
177            throw new IllegalArgumentException(String.format("Method %s not supported", method));
178        }
179
180        /*
181         * If the volume is mounted and we're enabling then unmount it
182         */
183        String vs = getVolumeState(path);
184        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
185            mUmsEnabling = enable; // Supress unmounted events
186            unmountVolume(path);
187            mUmsEnabling = false; // Unsupress unmounted events
188        }
189
190        try {
191            mConnector.doCommand(String.format(
192                    "volume %sshare %s %s", (enable ? "" : "un"), path, method));
193        } catch (NativeDaemonConnectorException e) {
194            Log.e(TAG, "Failed to share/unshare", e);
195            return MountServiceResultCode.OperationFailedInternalError;
196        }
197
198        /*
199         * If we disabled UMS then mount the volume
200         */
201        if (!enable) {
202            if (mountVolume(path) != MountServiceResultCode.OperationSucceeded) {
203                Log.e(TAG, String.format(
204                        "Failed to remount %s after disabling share method %s", path, method));
205                /*
206                 * Even though the mount failed, the unshare didn't so don't indicate an error.
207                 * The mountVolume() call will have set the storage state and sent the necessary
208                 * broadcasts.
209                 */
210            }
211        }
212
213        return MountServiceResultCode.OperationSucceeded;
214    }
215
216    void updatePublicVolumeState(String path, String state) {
217        if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
218            Log.w(TAG, "Multiple volumes not currently supported");
219            return;
220        }
221        Log.i(TAG, "State for {" + path + "} = {" + state + "}");
222
223        String oldState = mLegacyState;
224        mLegacyState = state;
225
226        synchronized (mListeners) {
227            for (int i = mListeners.size() -1; i >= 0; i--) {
228                MountServiceBinderListener bl = mListeners.get(i);
229                try {
230                    bl.mListener.onVolumeStateChanged("", path, oldState, state);
231                } catch (RemoteException rex) {
232                    Log.e(TAG, "Listener dead");
233                    mListeners.remove(i);
234                } catch (Exception ex) {
235                    Log.e(TAG, "Listener failed", ex);
236                }
237            }
238        }
239    }
240
241    /**
242     *
243     * Callback from NativeDaemonConnector
244     */
245    public void onDaemonConnected() {
246        /*
247         * Since we'll be calling back into the NativeDaemonConnector,
248         * we need to do our work in a new thread.
249         */
250        new Thread() {
251            public void run() {
252                /**
253                 * Determine media state and UMS detection status
254                 */
255                String path = Environment.getExternalStorageDirectory().getPath();
256                String state = Environment.MEDIA_REMOVED;
257
258                try {
259                    String[] vols = mConnector.doListCommand(
260                        "volume list", VoldResponseCode.VolumeListResult);
261                    for (String volstr : vols) {
262                        String[] tok = volstr.split(" ");
263                        // FMT: <label> <mountpoint> <state>
264                        if (!tok[1].equals(path)) {
265                            Log.w(TAG, String.format(
266                                    "Skipping unknown volume '%s'",tok[1]));
267                            continue;
268                        }
269                        int st = Integer.parseInt(tok[2]);
270                        if (st == VolumeState.NoMedia) {
271                            state = Environment.MEDIA_REMOVED;
272                        } else if (st == VolumeState.Idle) {
273                            state = null;
274                            int rc = mountVolume(path);
275                            if (rc != MountServiceResultCode.OperationSucceeded) {
276                                Log.e(TAG, String.format("Connection-mount failed (%d)", rc));
277                            }
278                        } else if (st == VolumeState.Mounted) {
279                            state = Environment.MEDIA_MOUNTED;
280                            Log.i(TAG, "Media already mounted on daemon connection");
281                        } else if (st == VolumeState.Shared) {
282                            state = Environment.MEDIA_SHARED;
283                            Log.i(TAG, "Media shared on daemon connection");
284                        } else {
285                            throw new Exception(String.format("Unexpected state %d", st));
286                        }
287                    }
288                    if (state != null) {
289                        updatePublicVolumeState(path, state);
290                    }
291                } catch (Exception e) {
292                    Log.e(TAG, "Error processing initial volume state", e);
293                    updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
294                }
295
296                try {
297                    boolean avail = getShareMethodAvailable("ums");
298                    notifyShareAvailabilityChange("ums", avail);
299                } catch (Exception ex) {
300                    Log.w(TAG, "Failed to get share availability");
301                }
302            }
303        }.start();
304    }
305
306    /**
307     *
308     * Callback from NativeDaemonConnector
309     */
310    public boolean onEvent(int code, String raw, String[] cooked) {
311        Intent in = null;
312
313        // Log.d(TAG, "event {" + raw + "}");
314        if (code == VoldResponseCode.VolumeStateChange) {
315            /*
316             * One of the volumes we're managing has changed state.
317             * Format: "NNN Volume <label> <path> state changed
318             * from <old_#> (<old_str>) to <new_#> (<new_str>)"
319             */
320            notifyVolumeStateChange(
321                    cooked[2], cooked[3], Integer.parseInt(cooked[7]),
322                            Integer.parseInt(cooked[10]));
323        } else if (code == VoldResponseCode.ShareAvailabilityChange) {
324            // FMT: NNN Share method <method> now <available|unavailable>
325            boolean avail = false;
326            if (cooked[5].equals("available")) {
327                avail = true;
328            }
329            notifyShareAvailabilityChange(cooked[3], avail);
330        } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
331                   (code == VoldResponseCode.VolumeDiskRemoved) ||
332                   (code == VoldResponseCode.VolumeBadRemoval)) {
333            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
334            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
335            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
336            final String label = cooked[2];
337            final String path = cooked[3];
338            int major = -1;
339            int minor = -1;
340
341            try {
342                String devComp = cooked[6].substring(1, cooked[6].length() -1);
343                String[] devTok = devComp.split(":");
344                major = Integer.parseInt(devTok[0]);
345                minor = Integer.parseInt(devTok[1]);
346            } catch (Exception ex) {
347                Log.e(TAG, "Failed to parse major/minor", ex);
348            }
349
350            synchronized (mListeners) {
351                for (int i = mListeners.size() -1; i >= 0; i--) {
352                    MountServiceBinderListener bl = mListeners.get(i);
353                    try {
354                        if (code == VoldResponseCode.VolumeDiskInserted) {
355                            bl.mListener.onMediaInserted(label, path, major, minor);
356                        } else if (code == VoldResponseCode.VolumeDiskRemoved) {
357                            bl.mListener.onMediaRemoved(label, path, major, minor, true);
358                        } else if (code == VoldResponseCode.VolumeBadRemoval) {
359                            bl.mListener.onMediaRemoved(label, path, major, minor, false);
360                        } else {
361                            Log.e(TAG, String.format("Unknown code {%d}", code));
362                        }
363                    } catch (RemoteException rex) {
364                        Log.e(TAG, "Listener dead");
365                        mListeners.remove(i);
366                    } catch (Exception ex) {
367                        Log.e(TAG, "Listener failed", ex);
368                    }
369                }
370            }
371
372            if (code == VoldResponseCode.VolumeDiskInserted) {
373                new Thread() {
374                    public void run() {
375                        try {
376                            int rc;
377                            if ((rc = mountVolume(path)) != MountServiceResultCode.OperationSucceeded) {
378                                Log.w(TAG, String.format("Insertion mount failed (%d)", rc));
379                            }
380                        } catch (Exception ex) {
381                            Log.w(TAG, "Failed to mount media on insertion", ex);
382                        }
383                    }
384                }.start();
385            } else if (code == VoldResponseCode.VolumeDiskRemoved) {
386                /*
387                 * This event gets trumped if we're already in BAD_REMOVAL state
388                 */
389                if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
390                    return true;
391                }
392                /* Send the media unmounted event first */
393                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
394                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
395                mContext.sendBroadcast(in);
396
397                updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
398                in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
399            } else if (code == VoldResponseCode.VolumeBadRemoval) {
400                /* Send the media unmounted event first */
401                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
402                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
403                mContext.sendBroadcast(in);
404
405                updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
406                in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
407            } else {
408                Log.e(TAG, String.format("Unknown code {%d}", code));
409            }
410        } else {
411            return false;
412        }
413
414        if (in != null) {
415            mContext.sendBroadcast(in);
416	}
417       return true;
418    }
419
420    void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
421        String vs = getVolumeState(path);
422
423        Intent in = null;
424
425        if (newState == VolumeState.Init) {
426        } else if (newState == VolumeState.NoMedia) {
427            // NoMedia is handled via Disk Remove events
428        } else if (newState == VolumeState.Idle) {
429            /*
430             * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
431             * if we're in the process of enabling UMS
432             */
433            if (!vs.equals(
434                    Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
435                            Environment.MEDIA_NOFS) && !vs.equals(
436                                    Environment.MEDIA_UNMOUNTABLE) && !mUmsEnabling) {
437                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
438                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
439            }
440        } else if (newState == VolumeState.Pending) {
441        } else if (newState == VolumeState.Checking) {
442            updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
443            in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
444        } else if (newState == VolumeState.Mounted) {
445            updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
446            // Update media status on PackageManagerService to mount packages on sdcard
447            mPms.updateExternalMediaStatus(true);
448            in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
449            in.putExtra("read-only", false);
450        } else if (newState == VolumeState.Unmounting) {
451            mPms.updateExternalMediaStatus(false);
452            in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
453        } else if (newState == VolumeState.Formatting) {
454        } else if (newState == VolumeState.Shared) {
455            /* Send the media unmounted event first */
456            updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
457            in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
458            mContext.sendBroadcast(in);
459
460            updatePublicVolumeState(path, Environment.MEDIA_SHARED);
461            in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
462        } else if (newState == VolumeState.SharedMnt) {
463            Log.e(TAG, "Live shared mounts not supported yet!");
464            return;
465        } else {
466            Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
467        }
468
469        if (in != null) {
470            mContext.sendBroadcast(in);
471        }
472    }
473
474    void notifyShareAvailabilityChange(String method, final boolean avail) {
475        if (!method.equals("ums")) {
476           Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
477           return;
478        }
479
480        synchronized (mListeners) {
481            for (int i = mListeners.size() -1; i >= 0; i--) {
482                MountServiceBinderListener bl = mListeners.get(i);
483                try {
484                    bl.mListener.onShareAvailabilityChanged(method, avail);
485                } catch (RemoteException rex) {
486                    Log.e(TAG, "Listener dead");
487                    mListeners.remove(i);
488                } catch (Exception ex) {
489                    Log.e(TAG, "Listener failed", ex);
490                }
491            }
492        }
493
494        Intent intent;
495        if (avail) {
496            intent = new Intent(Intent.ACTION_UMS_CONNECTED);
497        } else {
498            intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
499        }
500        mContext.sendBroadcast(intent);
501    }
502
503    void validatePermission(String perm) {
504        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
505            throw new SecurityException(String.format("Requires %s permission", perm));
506        }
507    }
508
509    /**
510     * Exposed API calls below here
511     */
512
513    public void registerListener(IMountServiceListener listener) {
514        synchronized (mListeners) {
515            MountServiceBinderListener bl = new MountServiceBinderListener(listener);
516            try {
517                listener.asBinder().linkToDeath(bl, 0);
518                mListeners.add(bl);
519            } catch (RemoteException rex) {
520                Log.e(TAG, "Failed to link to listener death");
521            }
522        }
523    }
524
525    public void unregisterListener(IMountServiceListener listener) {
526        synchronized (mListeners) {
527            for(MountServiceBinderListener bl : mListeners) {
528                if (bl.mListener == listener) {
529                    mListeners.remove(mListeners.indexOf(bl));
530                    return;
531                }
532            }
533        }
534    }
535
536    public void shutdown() {
537        validatePermission(android.Manifest.permission.SHUTDOWN);
538
539        Log.i(TAG, "Shutting down");
540
541        String path = Environment.getExternalStorageDirectory().getPath();
542        String state = getVolumeState(path);
543
544        if (state.equals(Environment.MEDIA_SHARED)) {
545            /*
546             * If the media is currently shared, unshare it.
547             * XXX: This is still dangerous!. We should not
548             * be rebooting at *all* if UMS is enabled, since
549             * the UMS host could have dirty FAT cache entries
550             * yet to flush.
551             */
552            if (unshareVolume(path, "ums") != MountServiceResultCode.OperationSucceeded) {
553                Log.e(TAG, "UMS disable on shutdown failed");
554            }
555        } else if (state.equals(Environment.MEDIA_CHECKING)) {
556            /*
557             * If the media is being checked, then we need to wait for
558             * it to complete before being able to proceed.
559             */
560            // XXX: @hackbod - Should we disable the ANR timer here?
561            int retries = 30;
562            while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
563                try {
564                    Thread.sleep(1000);
565                } catch (InterruptedException iex) {
566                    Log.e(TAG, "Interrupted while waiting for media", iex);
567                    break;
568                }
569                state = Environment.getExternalStorageState();
570            }
571            if (retries == 0) {
572                Log.e(TAG, "Timed out waiting for media to check");
573            }
574        }
575
576        if (state.equals(Environment.MEDIA_MOUNTED)) {
577            /*
578             * If the media is mounted, then gracefully unmount it.
579             */
580            if (unmountVolume(path) != MountServiceResultCode.OperationSucceeded) {
581                Log.e(TAG, "Failed to unmount media for shutdown");
582            }
583        }
584    }
585
586    public String[] getShareMethodList() {
587        String[] rdata = new String[1];
588        rdata[0] = "ums";
589        return rdata;
590    }
591
592    public boolean getShareMethodAvailable(String method) {
593        ArrayList<String> rsp = mConnector.doCommand("share status " + method);
594
595        for (String line : rsp) {
596            String []tok = line.split(" ");
597            int code;
598            try {
599                code = Integer.parseInt(tok[0]);
600            } catch (NumberFormatException nfe) {
601                Log.e(TAG, String.format("Error parsing code %s", tok[0]));
602                return false;
603            }
604            if (code == VoldResponseCode.ShareStatusResult) {
605                if (tok[2].equals("available"))
606                    return true;
607                return false;
608            } else {
609                Log.e(TAG, String.format("Unexpected response code %d", code));
610                return false;
611            }
612        }
613        Log.e(TAG, "Got an empty response");
614        return false;
615    }
616
617    public int shareVolume(String path, String method) {
618        return doShareUnshareVolume(path, method, true);
619    }
620
621    public int unshareVolume(String path, String method) {
622        return doShareUnshareVolume(path, method, false);
623    }
624
625    public boolean getVolumeShared(String path, String method) {
626        String cmd = String.format("volume shared %s %s", path, method);
627        ArrayList<String> rsp = mConnector.doCommand(cmd);
628
629        for (String line : rsp) {
630            String []tok = line.split(" ");
631            int code;
632            try {
633                code = Integer.parseInt(tok[0]);
634            } catch (NumberFormatException nfe) {
635                Log.e(TAG, String.format("Error parsing code %s", tok[0]));
636                return false;
637            }
638            if (code == VoldResponseCode.ShareEnabledResult) {
639                if (tok[2].equals("enabled"))
640                    return true;
641                return false;
642            } else {
643                Log.e(TAG, String.format("Unexpected response code %d", code));
644                return false;
645            }
646        }
647        Log.e(TAG, "Got an empty response");
648        return false;
649    }
650
651    /**
652     * @return state of the volume at the specified mount point
653     */
654    public String getVolumeState(String mountPoint) {
655        /*
656         * XXX: Until we have multiple volume discovery, just hardwire
657         * this to /sdcard
658         */
659        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
660            Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
661            throw new IllegalArgumentException();
662        }
663
664        return mLegacyState;
665    }
666
667
668    /**
669     * Attempt to mount external media
670     */
671    public int mountVolume(String path) {
672        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
673        int rc = MountServiceResultCode.OperationSucceeded;
674
675        try {
676            mConnector.doCommand(String.format("volume mount %s", path));
677        } catch (NativeDaemonConnectorException e) {
678            /*
679             * Mount failed for some reason
680             */
681            Intent in = null;
682            int code = e.getCode();
683            if (code == VoldResponseCode.OpFailedNoMedia) {
684                /*
685                 * Attempt to mount but no media inserted
686                 */
687                rc = MountServiceResultCode.OperationFailedNoMedia;
688            } else if (code == VoldResponseCode.OpFailedMediaBlank) {
689                /*
690                 * Media is blank or does not contain a supported filesystem
691                 */
692                updatePublicVolumeState(path, Environment.MEDIA_NOFS);
693                in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
694                rc = MountServiceResultCode.OperationFailedMediaBlank;
695            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
696                /*
697                 * Volume consistency check failed
698                 */
699                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
700                in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
701                rc = MountServiceResultCode.OperationFailedMediaCorrupt;
702            } else {
703                rc = MountServiceResultCode.OperationFailedInternalError;
704            }
705
706            /*
707             * Send broadcast intent (if required for the failure)
708             */
709            if (in != null) {
710                mContext.sendBroadcast(in);
711            }
712        }
713
714        return rc;
715    }
716
717    /**
718     * Attempt to unmount external media to prepare for eject
719     */
720    public int unmountVolume(String path) {
721        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
722
723        // Check if media has been mounted
724        String oldState = mLegacyState;
725        if (!oldState.equals(Environment.MEDIA_MOUNTED)) {
726            return VoldResponseCode.OpFailedVolNotMounted;
727        }
728        // Notify PackageManager of potential media removal and deal with
729        // return code later on. The caller of this api should be aware or have been
730        // notified that the applications installed on the media will be killed.
731        mPms.updateExternalMediaStatus(false);
732        try {
733            mConnector.doCommand(String.format("volume unmount %s", path));
734            return MountServiceResultCode.OperationSucceeded;
735        } catch (NativeDaemonConnectorException e) {
736            // Don't worry about mismatch in PackageManager since the
737            // call back will handle the status changes any way.
738            int code = e.getCode();
739            if (code == VoldResponseCode.OpFailedVolNotMounted) {
740                return MountServiceResultCode.OperationFailedVolumeNotMounted;
741            } else {
742                return MountServiceResultCode.OperationFailedInternalError;
743            }
744        }
745    }
746
747    /**
748     * Synchronously formats a volume
749     *
750     * @param path The volume path to format
751     * @return Error code from MountServiceResultCode
752     */
753    public int formatVolume(String path) {
754        validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
755
756        try {
757            String cmd = String.format("volume format %s", path);
758            mConnector.doCommand(cmd);
759            return MountServiceResultCode.OperationSucceeded;
760        } catch (NativeDaemonConnectorException e) {
761            int code = e.getCode();
762            if (code == VoldResponseCode.OpFailedNoMedia) {
763                return MountServiceResultCode.OperationFailedNoMedia;
764            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
765                return MountServiceResultCode.OperationFailedMediaCorrupt;
766            } else {
767                return MountServiceResultCode.OperationFailedInternalError;
768            }
769        }
770    }
771
772    public String[] getSecureContainerList() {
773        validatePermission(android.Manifest.permission.ASEC_ACCESS);
774        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
775            Log.w(TAG, "getSecureContainerList() called when storage not mounted");
776        }
777
778        try {
779            return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult);
780        } catch (NativeDaemonConnectorException e) {
781            return new String[0];
782        }
783    }
784
785    public int createSecureContainer(String id, int sizeMb, String fstype,
786                                    String key, int ownerUid) {
787        validatePermission(android.Manifest.permission.ASEC_CREATE);
788        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
789            Log.w(TAG, "createSecureContainer() called when storage not mounted");
790        }
791
792        int rc = MountServiceResultCode.OperationSucceeded;
793        String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);
794        try {
795            mConnector.doCommand(cmd);
796        } catch (NativeDaemonConnectorException e) {
797            rc = MountServiceResultCode.OperationFailedInternalError;
798        }
799        return rc;
800    }
801
802    public int finalizeSecureContainer(String id) {
803        validatePermission(android.Manifest.permission.ASEC_CREATE);
804        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
805            Log.w(TAG, "finalizeSecureContainer() called when storage not mounted");
806        }
807
808        int rc = MountServiceResultCode.OperationSucceeded;
809        try {
810            mConnector.doCommand(String.format("asec finalize %s", id));
811        } catch (NativeDaemonConnectorException e) {
812            rc = MountServiceResultCode.OperationFailedInternalError;
813        }
814        return rc;
815    }
816
817    public int destroySecureContainer(String id) {
818        validatePermission(android.Manifest.permission.ASEC_DESTROY);
819
820        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
821            Log.w(TAG, "destroySecureContainer() called when storage not mounted");
822        }
823
824        int rc = MountServiceResultCode.OperationSucceeded;
825        try {
826            mConnector.doCommand(String.format("asec destroy %s", id));
827        } catch (NativeDaemonConnectorException e) {
828            rc = MountServiceResultCode.OperationFailedInternalError;
829        }
830        return rc;
831    }
832
833    public int mountSecureContainer(String id, String key, int ownerUid) {
834        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
835        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
836            Log.w(TAG, "mountSecureContainer() called when storage not mounted");
837        }
838
839        int rc = MountServiceResultCode.OperationSucceeded;
840        String cmd = String.format("asec mount %s %s %d", id, key, ownerUid);
841        try {
842            mConnector.doCommand(cmd);
843        } catch (NativeDaemonConnectorException e) {
844            rc = MountServiceResultCode.OperationFailedInternalError;
845        }
846        return rc;
847    }
848
849    public int unmountSecureContainer(String id) {
850        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
851        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
852            Log.w(TAG, "unmountSecureContainer() called when storage not mounted");
853        }
854
855        int rc = MountServiceResultCode.OperationSucceeded;
856        String cmd = String.format("asec unmount %s", id);
857        try {
858            mConnector.doCommand(cmd);
859        } catch (NativeDaemonConnectorException e) {
860            rc = MountServiceResultCode.OperationFailedInternalError;
861        }
862        return rc;
863    }
864
865    public int renameSecureContainer(String oldId, String newId) {
866        validatePermission(android.Manifest.permission.ASEC_RENAME);
867        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
868            Log.w(TAG, "renameSecureContainer() called when storage not mounted");
869        }
870
871        int rc = MountServiceResultCode.OperationSucceeded;
872        String cmd = String.format("asec rename %s %s", oldId, newId);
873        try {
874            mConnector.doCommand(cmd);
875        } catch (NativeDaemonConnectorException e) {
876            rc = MountServiceResultCode.OperationFailedInternalError;
877        }
878        return rc;
879    }
880
881    public String getSecureContainerPath(String id) {
882        validatePermission(android.Manifest.permission.ASEC_ACCESS);
883        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
884            Log.w(TAG, "getSecureContainerPath() called when storage not mounted");
885        }
886
887        ArrayList<String> rsp = mConnector.doCommand("asec path " + id);
888
889        for (String line : rsp) {
890            String []tok = line.split(" ");
891            int code = Integer.parseInt(tok[0]);
892            if (code == VoldResponseCode.AsecPathResult) {
893                return tok[1];
894            } else {
895                Log.e(TAG, String.format("Unexpected response code %d", code));
896                return "";
897            }
898        }
899
900        Log.e(TAG, "Got an empty response");
901        return "";
902    }
903}
904
905