StorageNotification.java revision 906b830d1d61cbd8b1f9c2dc87e564cfb0f315fd
1/*
2 * Copyright (C) 2010 Google Inc.
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.systemui.usb;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.Resources;
25import android.os.Environment;
26import android.os.storage.StorageEventListener;
27import android.os.storage.StorageManager;
28import android.provider.Settings;
29import android.util.Slog;
30
31public class StorageNotification extends StorageEventListener {
32    private static final String TAG = "StorageNotification";
33
34    private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true;
35
36    /**
37     * Binder context for this service
38     */
39    private Context mContext;
40
41    /**
42     * The notification that is shown when a USB mass storage host
43     * is connected.
44     * <p>
45     * This is lazily created, so use {@link #setUsbStorageNotification()}.
46     */
47    private Notification mUsbStorageNotification;
48
49    /**
50     * The notification that is shown when the following media events occur:
51     *     - Media is being checked
52     *     - Media is blank (or unknown filesystem)
53     *     - Media is corrupt
54     *     - Media is safe to unmount
55     *     - Media is missing
56     * <p>
57     * This is lazily created, so use {@link #setMediaStorageNotification()}.
58     */
59    private Notification   mMediaStorageNotification;
60    private boolean        mUmsAvailable;
61    private StorageManager mStorageManager;
62
63    public StorageNotification(Context context) {
64        mContext = context;
65
66        mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
67        final boolean connected = mStorageManager.isUsbMassStorageConnected();
68        Slog.d(TAG, String.format( "Startup with UMS connection %s (media state %s)", mUmsAvailable,
69                Environment.getExternalStorageState()));
70        onUsbMassStorageConnectionChanged(connected);
71    }
72
73    /*
74     * @override com.android.os.storage.StorageEventListener
75     */
76    @Override
77    public void onUsbMassStorageConnectionChanged(boolean connected) {
78        mUmsAvailable = connected;
79        /*
80         * Even though we may have a UMS host connected, we the SD card
81         * may not be in a state for export.
82         */
83        String st = Environment.getExternalStorageState();
84
85        Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st));
86
87        if (connected && (st.equals(
88                Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) {
89            /*
90             * No card or card being checked = don't display
91             */
92            connected = false;
93        }
94        updateUsbMassStorageNotification(connected);
95    }
96
97    /*
98     * @override com.android.os.storage.StorageEventListener
99     */
100    @Override
101    public void onStorageStateChanged(String path, String oldState, String newState) {
102        Slog.i(TAG, String.format(
103                "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState));
104        if (newState.equals(Environment.MEDIA_SHARED)) {
105            /*
106             * Storage is now shared. Modify the UMS notification
107             * for stopping UMS.
108             */
109            Intent intent = new Intent();
110            intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
111            PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
112            setUsbStorageNotification(
113                    com.android.internal.R.string.usb_storage_stop_notification_title,
114                    com.android.internal.R.string.usb_storage_stop_notification_message,
115                    com.android.internal.R.drawable.stat_sys_warning, false, true, pi);
116        } else if (newState.equals(Environment.MEDIA_CHECKING)) {
117            /*
118             * Storage is now checking. Update media notification and disable
119             * UMS notification.
120             */
121            setMediaStorageNotification(
122                    com.android.internal.R.string.ext_media_checking_notification_title,
123                    com.android.internal.R.string.ext_media_checking_notification_message,
124                    com.android.internal.R.drawable.stat_notify_sdcard_prepare, true, false, null);
125            updateUsbMassStorageNotification(false);
126        } else if (newState.equals(Environment.MEDIA_MOUNTED)) {
127            /*
128             * Storage is now mounted. Dismiss any media notifications,
129             * and enable UMS notification if connected.
130             */
131            setMediaStorageNotification(0, 0, 0, false, false, null);
132            updateUsbMassStorageNotification(mUmsAvailable);
133        } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) {
134            /*
135             * Storage is now unmounted. We may have been unmounted
136             * because the user is enabling/disabling UMS, in which case we don't
137             * want to display the 'safe to unmount' notification.
138             */
139            if (!mStorageManager.isUsbMassStorageEnabled()) {
140                if (oldState.equals(Environment.MEDIA_SHARED)) {
141                    /*
142                     * The unmount was due to UMS being enabled. Dismiss any
143                     * media notifications, and enable UMS notification if connected
144                     */
145                    setMediaStorageNotification(0, 0, 0, false, false, null);
146                    updateUsbMassStorageNotification(mUmsAvailable);
147                } else {
148                    /*
149                     * Show safe to unmount media notification, and enable UMS
150                     * notification if connected.
151                     */
152                    if (Environment.isExternalStorageRemovable()) {
153                        setMediaStorageNotification(
154                                com.android.internal.R.string.ext_media_safe_unmount_notification_title,
155                                com.android.internal.R.string.ext_media_safe_unmount_notification_message,
156                                com.android.internal.R.drawable.stat_notify_sdcard, true, true, null);
157                    } else {
158                        // This device does not have removable storage, so
159                        // don't tell the user they can remove it.
160                        setMediaStorageNotification(0, 0, 0, false, false, null);
161                    }
162                    updateUsbMassStorageNotification(mUmsAvailable);
163                }
164            } else {
165                /*
166                 * The unmount was due to UMS being enabled. Dismiss any
167                 * media notifications, and disable the UMS notification
168                 */
169                setMediaStorageNotification(0, 0, 0, false, false, null);
170                updateUsbMassStorageNotification(false);
171            }
172        } else if (newState.equals(Environment.MEDIA_NOFS)) {
173            /*
174             * Storage has no filesystem. Show blank media notification,
175             * and enable UMS notification if connected.
176             */
177            Intent intent = new Intent();
178            intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
179            PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
180
181            setMediaStorageNotification(
182                    com.android.internal.R.string.ext_media_nofs_notification_title,
183                    com.android.internal.R.string.ext_media_nofs_notification_message,
184                    com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi);
185            updateUsbMassStorageNotification(mUmsAvailable);
186        } else if (newState.equals(Environment.MEDIA_UNMOUNTABLE)) {
187            /*
188             * Storage is corrupt. Show corrupt media notification,
189             * and enable UMS notification if connected.
190             */
191            Intent intent = new Intent();
192            intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
193            PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
194
195            setMediaStorageNotification(
196                    com.android.internal.R.string.ext_media_unmountable_notification_title,
197                    com.android.internal.R.string.ext_media_unmountable_notification_message,
198                    com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi);
199            updateUsbMassStorageNotification(mUmsAvailable);
200        } else if (newState.equals(Environment.MEDIA_REMOVED)) {
201            /*
202             * Storage has been removed. Show nomedia media notification,
203             * and disable UMS notification regardless of connection state.
204             */
205            setMediaStorageNotification(
206                    com.android.internal.R.string.ext_media_nomedia_notification_title,
207                    com.android.internal.R.string.ext_media_nomedia_notification_message,
208                    com.android.internal.R.drawable.stat_notify_sdcard_usb,
209                    true, false, null);
210            updateUsbMassStorageNotification(false);
211        } else if (newState.equals(Environment.MEDIA_BAD_REMOVAL)) {
212            /*
213             * Storage has been removed unsafely. Show bad removal media notification,
214             * and disable UMS notification regardless of connection state.
215             */
216            setMediaStorageNotification(
217                    com.android.internal.R.string.ext_media_badremoval_notification_title,
218                    com.android.internal.R.string.ext_media_badremoval_notification_message,
219                    com.android.internal.R.drawable.stat_sys_warning,
220                    true, true, null);
221            updateUsbMassStorageNotification(false);
222        } else {
223            Slog.w(TAG, String.format("Ignoring unknown state {%s}", newState));
224        }
225    }
226
227    /**
228     * Update the state of the USB mass storage notification
229     */
230    void updateUsbMassStorageNotification(boolean available) {
231
232        if (available) {
233            Intent intent = new Intent();
234            intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
235            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
236
237            PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
238            setUsbStorageNotification(
239                    com.android.internal.R.string.usb_storage_notification_title,
240                    com.android.internal.R.string.usb_storage_notification_message,
241                    com.android.internal.R.drawable.stat_sys_data_usb,
242                    false, true, pi);
243        } else {
244            setUsbStorageNotification(0, 0, 0, false, false, null);
245        }
246    }
247
248    /**
249     * Sets the USB storage notification.
250     */
251    private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon,
252            boolean sound, boolean visible, PendingIntent pi) {
253
254        if (!visible && mUsbStorageNotification == null) {
255            return;
256        }
257
258        NotificationManager notificationManager = (NotificationManager) mContext
259                .getSystemService(Context.NOTIFICATION_SERVICE);
260
261        if (notificationManager == null) {
262            return;
263        }
264
265        if (visible) {
266            Resources r = Resources.getSystem();
267            CharSequence title = r.getText(titleId);
268            CharSequence message = r.getText(messageId);
269
270            if (mUsbStorageNotification == null) {
271                mUsbStorageNotification = new Notification();
272                mUsbStorageNotification.icon = icon;
273                mUsbStorageNotification.when = 0;
274            }
275
276            if (sound) {
277                mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
278            } else {
279                mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
280            }
281
282            mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
283
284            mUsbStorageNotification.tickerText = title;
285            if (pi == null) {
286                Intent intent = new Intent();
287                pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
288            }
289
290            mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
291            final boolean adbOn = 1 == Settings.Secure.getInt(
292                mContext.getContentResolver(),
293                Settings.Secure.ADB_ENABLED,
294                0);
295
296            if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) {
297                // Pop up a full-screen alert to coach the user through enabling UMS. The average
298                // user has attached the device to USB either to charge the phone (in which case
299                // this is harmless) or transfer files, and in the latter case this alert saves
300                // several steps (as well as subtly indicates that you shouldn't mix UMS with other
301                // activities on the device).
302                //
303                // If ADB is enabled, however, we suppress this dialog (under the assumption that a
304                // developer (a) knows how to enable UMS, and (b) is probably using USB to install
305                // builds or use adb commands.
306                mUsbStorageNotification.fullScreenIntent = pi;
307            }
308        }
309
310        final int notificationId = mUsbStorageNotification.icon;
311        if (visible) {
312            notificationManager.notify(notificationId, mUsbStorageNotification);
313        } else {
314            notificationManager.cancel(notificationId);
315        }
316    }
317
318    private synchronized boolean getMediaStorageNotificationDismissable() {
319        if ((mMediaStorageNotification != null) &&
320            ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
321                    Notification.FLAG_AUTO_CANCEL))
322            return true;
323
324        return false;
325    }
326
327    /**
328     * Sets the media storage notification.
329     */
330    private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
331                                                          boolean dismissable, PendingIntent pi) {
332
333        if (!visible && mMediaStorageNotification == null) {
334            return;
335        }
336
337        NotificationManager notificationManager = (NotificationManager) mContext
338                .getSystemService(Context.NOTIFICATION_SERVICE);
339
340        if (notificationManager == null) {
341            return;
342        }
343
344        if (mMediaStorageNotification != null && visible) {
345            /*
346             * Dismiss the previous notification - we're about to
347             * re-use it.
348             */
349            final int notificationId = mMediaStorageNotification.icon;
350            notificationManager.cancel(notificationId);
351        }
352
353        if (visible) {
354            Resources r = Resources.getSystem();
355            CharSequence title = r.getText(titleId);
356            CharSequence message = r.getText(messageId);
357
358            if (mMediaStorageNotification == null) {
359                mMediaStorageNotification = new Notification();
360                mMediaStorageNotification.when = 0;
361            }
362
363            mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
364
365            if (dismissable) {
366                mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
367            } else {
368                mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
369            }
370
371            mMediaStorageNotification.tickerText = title;
372            if (pi == null) {
373                Intent intent = new Intent();
374                pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
375            }
376
377            mMediaStorageNotification.icon = icon;
378            mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
379        }
380
381        final int notificationId = mMediaStorageNotification.icon;
382        if (visible) {
383            notificationManager.notify(notificationId, mMediaStorageNotification);
384        } else {
385            notificationManager.cancel(notificationId);
386        }
387    }
388}
389