MountService.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.res.Resources;
26import android.net.Uri;
27import android.os.IMountService;
28import android.os.Environment;
29import android.os.RemoteException;
30import android.os.UEventObserver;
31import android.util.Log;
32
33import java.io.File;
34import java.io.FileReader;
35
36/**
37 * MountService implements an to the mount service daemon
38 * @hide
39 */
40class MountService extends IMountService.Stub {
41
42    private static final String TAG = "MountService";
43
44    /**
45     * Binder context for this service
46     */
47    private Context mContext;
48
49    /**
50     * listener object for communicating with the mount service daemon
51     */
52    private MountListener mListener;
53
54    /**
55     * The notification that is shown when USB is connected. It leads the user
56     * to a dialog to enable mass storage mode.
57     * <p>
58     * This is lazily created, so use {@link #getUsbStorageNotification()}.
59     */
60    private Notification mUsbStorageNotification;
61
62    private class SdDoorListener extends UEventObserver {
63        static final String SD_DOOR_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/sd-door";
64        static final String SD_DOOR_SWITCH_NAME = "sd-door";
65
66        public void onUEvent(UEvent event) {
67            if (SD_DOOR_SWITCH_NAME.equals(event.get("SWITCH_NAME"))) {
68                sdDoorStateChanged(event.get("SWITCH_STATE"));
69            }
70        }
71    };
72
73    /**
74     * Constructs a new MountService instance
75     *
76     * @param context  Binder context for this service
77     */
78    public MountService(Context context) {
79        mContext = context;
80        mListener =  new MountListener(this);
81        Thread thread = new Thread(mListener, MountListener.class.getName());
82        thread.start();
83        SdDoorListener sdDoorListener = new SdDoorListener();
84        sdDoorListener.startObserving(SdDoorListener.SD_DOOR_UEVENT_MATCH);
85    }
86
87    /**
88     * @return true if USB mass storage support is enabled.
89     */
90    public boolean getMassStorageEnabled() throws RemoteException {
91        return mListener.getMassStorageEnabled();
92    }
93
94    /**
95     * Enables or disables USB mass storage support.
96     *
97     * @param enable  true to enable USB mass storage support
98     */
99    public void setMassStorageEnabled(boolean enable) throws RemoteException {
100        mListener.setMassStorageEnabled(enable);
101    }
102
103    /**
104     * @return true if USB mass storage is connected.
105     */
106    public boolean getMassStorageConnected() throws RemoteException {
107        return mListener.getMassStorageConnected();
108    }
109
110    /**
111     * Attempt to mount external media
112     */
113    public void mountMedia(String mountPath) throws RemoteException {
114        if (mContext.checkCallingOrSelfPermission(
115                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
116                != PackageManager.PERMISSION_GRANTED) {
117            throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
118        }
119        mListener.mountMedia(mountPath);
120    }
121
122    /**
123     * Attempt to unmount external media to prepare for eject
124     */
125    public void unmountMedia(String mountPath) throws RemoteException {
126        if (mContext.checkCallingOrSelfPermission(
127                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
128                != PackageManager.PERMISSION_GRANTED) {
129            throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
130        }
131
132        // tell mountd to unmount the media
133        mListener.ejectMedia(mountPath);
134    }
135
136    /**
137     * Broadcasts the USB mass storage connected event to all clients.
138     */
139    void notifyUmsConnected() {
140        setUsbStorageNotificationVisibility(true);
141        Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED);
142        mContext.sendBroadcast(intent);
143    }
144
145    /**
146     * Broadcasts the USB mass storage disconnected event to all clients.
147     */
148    void notifyUmsDisconnected() {
149        setUsbStorageNotificationVisibility(false);
150        Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
151        mContext.sendBroadcast(intent);
152    }
153
154    /**
155     * Broadcasts the media removed event to all clients.
156     */
157    void notifyMediaRemoved(String path) {
158        Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED,
159                Uri.parse("file://" + path));
160        mContext.sendBroadcast(intent);
161    }
162
163    /**
164     * Broadcasts the media unmounted event to all clients.
165     */
166    void notifyMediaUnmounted(String path) {
167        Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
168                Uri.parse("file://" + path));
169        mContext.sendBroadcast(intent);
170    }
171
172    /**
173     * Broadcasts the media mounted event to all clients.
174     */
175    void notifyMediaMounted(String path, boolean readOnly) {
176        Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED,
177                Uri.parse("file://" + path));
178        intent.putExtra("read-only", readOnly);
179        mContext.sendBroadcast(intent);
180    }
181
182    /**
183     * Broadcasts the media shared event to all clients.
184     */
185    void notifyMediaShared(String path) {
186        Intent intent = new Intent(Intent.ACTION_MEDIA_SHARED,
187                Uri.parse("file://" + path));
188        mContext.sendBroadcast(intent);
189    }
190
191    /**
192     * Broadcasts the media bad removal event to all clients.
193     */
194    void notifyMediaBadRemoval(String path) {
195        Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL,
196                Uri.parse("file://" + path));
197        mContext.sendBroadcast(intent);
198    }
199
200    /**
201     * Broadcasts the media unmountable event to all clients.
202     */
203    void notifyMediaUnmountable(String path) {
204        Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE,
205                Uri.parse("file://" + path));
206        mContext.sendBroadcast(intent);
207    }
208
209    /**
210     * Broadcasts the media eject event to all clients.
211     */
212    void notifyMediaEject(String path) {
213        Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT,
214                Uri.parse("file://" + path));
215        mContext.sendBroadcast(intent);
216    }
217
218    private void sdDoorStateChanged(String doorState) {
219        File directory = Environment.getExternalStorageDirectory();
220        String storageState = Environment.getExternalStorageState();
221
222        if (directory != null) {
223            try {
224                if (doorState.equals("open") && (storageState.equals(Environment.MEDIA_MOUNTED) ||
225                        storageState.equals(Environment.MEDIA_MOUNTED_READ_ONLY))) {
226                    // request SD card unmount if SD card door is opened
227                    unmountMedia(directory.getPath());
228                } else if (doorState.equals("closed") && storageState.equals(Environment.MEDIA_UNMOUNTED)) {
229                    // attempt to remount SD card
230                    mountMedia(directory.getPath());
231                }
232            } catch (RemoteException e) {
233                // Nothing to do.
234            }
235        }
236    }
237
238    /**
239     * Sets the visibility of the USB storage notification. This should be
240     * called when a USB cable is connected and also when it is disconnected.
241     *
242     * @param visible Whether to show or hide the notification.
243     */
244    private void setUsbStorageNotificationVisibility(boolean visible) {
245        NotificationManager notificationManager = (NotificationManager) mContext
246                .getSystemService(Context.NOTIFICATION_SERVICE);
247        if (notificationManager == null) {
248            return;
249        }
250
251        /*
252         * The convention for notification IDs is to use the icon's resource ID
253         * when the icon is only used by a single notification type, which is
254         * the case here.
255         */
256        Notification notification = getUsbStorageNotification();
257        final int notificationId = notification.icon;
258
259        if (visible) {
260            notificationManager.notify(notificationId, notification);
261        } else {
262            notificationManager.cancel(notificationId);
263        }
264    }
265
266    /**
267     * Gets the USB storage notification.
268     *
269     * @return A {@link Notification} that leads to the dialog to enable USB storage.
270     */
271    private synchronized Notification getUsbStorageNotification() {
272        Resources r = Resources.getSystem();
273        CharSequence title =
274                r.getText(com.android.internal.R.string.usb_storage_notification_title);
275        CharSequence message =
276                r.getText(com.android.internal.R.string.usb_storage_notification_message);
277
278        if (mUsbStorageNotification == null) {
279            mUsbStorageNotification = new Notification();
280            mUsbStorageNotification.icon = com.android.internal.R.drawable.stat_sys_data_usb;
281            mUsbStorageNotification.when = 0;
282            mUsbStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
283            mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
284        }
285
286        mUsbStorageNotification.tickerText = title;
287        mUsbStorageNotification.setLatestEventInfo(mContext, title, message,
288                getUsbStorageDialogIntent());
289
290        return mUsbStorageNotification;
291    }
292
293    /**
294     * Creates a pending intent to start the USB storage activity.
295     *
296     * @return A {@link PendingIntent} that start the USB storage activity.
297     */
298    private PendingIntent getUsbStorageDialogIntent() {
299        Intent intent = new Intent();
300        intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);
301        return PendingIntent.getActivity(mContext, 0, intent, 0);
302    }
303}
304
305