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