Home | History | Annotate | Download | only in usb
      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 {
     67         public void onUsbMassStorageConnectionChanged(final boolean connected) {
     68             mAsyncEventHandler.post(new Runnable() {
     69                 @Override
     70                 public void run() {
     71                     onUsbMassStorageConnectionChangedAsync(connected);
     72                 }
     73             });
     74         }
     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
     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 
    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 
    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      */
    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      */
    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 
    313             mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
    314             final boolean adbOn = 1 == Settings.Global.getInt(
    315                 mContext.getContentResolver(),
    316                 Settings.Global.ADB_ENABLED,
    317                 0);
    318 
    319             if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) {
    320                 // Pop up a full-screen alert to coach the user through enabling UMS. The average
    321                 // user has attached the device to USB either to charge the phone (in which case
    322                 // this is harmless) or transfer files, and in the latter case this alert saves
    323                 // several steps (as well as subtly indicates that you shouldn't mix UMS with other
    324                 // activities on the device).
    325                 //
    326                 // If ADB is enabled, however, we suppress this dialog (under the assumption that a
    327                 // developer (a) knows how to enable UMS, and (b) is probably using USB to install
    328                 // builds or use adb commands.
    329                 mUsbStorageNotification.fullScreenIntent = pi;
    330             }
    331         }
    332 
    333         final int notificationId = mUsbStorageNotification.icon;
    334         if (visible) {
    335             notificationManager.notifyAsUser(null, notificationId, mUsbStorageNotification,
    336                     UserHandle.ALL);
    337         } else {
    338             notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
    339         }
    340     }
    341 
    342     private synchronized boolean getMediaStorageNotificationDismissable() {
    343         if ((mMediaStorageNotification != null) &&
    344             ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
    345                     Notification.FLAG_AUTO_CANCEL))
    346             return true;
    347 
    348         return false;
    349     }
    350 
    351     /**
    352      * Sets the media storage notification.
    353      */
    354     private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
    355                                                           boolean dismissable, PendingIntent pi) {
    356 
    357         if (!visible && mMediaStorageNotification == null) {
    358             return;
    359         }
    360 
    361         NotificationManager notificationManager = (NotificationManager) mContext
    362                 .getSystemService(Context.NOTIFICATION_SERVICE);
    363 
    364         if (notificationManager == null) {
    365             return;
    366         }
    367 
    368         if (mMediaStorageNotification != null && visible) {
    369             /*
    370              * Dismiss the previous notification - we're about to
    371              * re-use it.
    372              */
    373             final int notificationId = mMediaStorageNotification.icon;
    374             notificationManager.cancel(notificationId);
    375         }
    376 
    377         if (visible) {
    378             Resources r = Resources.getSystem();
    379             CharSequence title = r.getText(titleId);
    380             CharSequence message = r.getText(messageId);
    381 
    382             if (mMediaStorageNotification == null) {
    383                 mMediaStorageNotification = new Notification();
    384                 mMediaStorageNotification.when = 0;
    385             }
    386 
    387             mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
    388 
    389             if (dismissable) {
    390                 mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
    391             } else {
    392                 mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
    393             }
    394 
    395             mMediaStorageNotification.tickerText = title;
    396             if (pi == null) {
    397                 Intent intent = new Intent();
    398                 pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
    399                         UserHandle.CURRENT);
    400             }
    401 
    402             mMediaStorageNotification.icon = icon;
    403             mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
    404         }
    405 
    406         final int notificationId = mMediaStorageNotification.icon;
    407         if (visible) {
    408             notificationManager.notifyAsUser(null, notificationId,
    409                     mMediaStorageNotification, UserHandle.ALL);
    410         } else {
    411             notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
    412         }
    413     }
    414 }
    415