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