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