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