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