blob: 2c36ab7c0c3b786daa60a1151c4afc8d25997c75 [file] [log] [blame]
San Mehat64e6a452010-02-04 20:53:48 -08001/*
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
Joe Onoratofe4f3ae2010-06-04 11:25:26 -070017package com.android.systemui.usb;
San Mehat64e6a452010-02-04 20:53:48 -080018
San Mehat64e6a452010-02-04 20:53:48 -080019import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
San Mehat64e6a452010-02-04 20:53:48 -080022import android.content.Context;
San Mehat64e6a452010-02-04 20:53:48 -080023import android.content.Intent;
San Mehat64e6a452010-02-04 20:53:48 -080024import android.content.res.Resources;
San Mehat64e6a452010-02-04 20:53:48 -080025import android.os.Environment;
Daniel Sandler5b8743f2010-11-03 09:43:46 -040026import android.os.Handler;
27import android.os.HandlerThread;
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -070028import android.os.UserHandle;
San Mehatb1043402010-02-05 08:26:50 -080029import android.os.storage.StorageEventListener;
30import android.os.storage.StorageManager;
Daniel Sandlerc07907e2010-02-22 15:08:41 -050031import android.provider.Settings;
John Spurlockcd686b52013-06-05 10:13:46 -040032import android.util.Log;
San Mehat64e6a452010-02-04 20:53:48 -080033
John Spurlock3e309b22013-06-25 11:01:29 -040034import com.android.systemui.SystemUI;
35
36public class StorageNotification extends SystemUI {
San Mehat64e6a452010-02-04 20:53:48 -080037 private static final String TAG = "StorageNotification";
Dianne Hackborn40e9f292012-11-27 19:12:23 -080038 private static final boolean DEBUG = false;
San Mehat64e6a452010-02-04 20:53:48 -080039
Daniel Sandlerc07907e2010-02-22 15:08:41 -050040 private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true;
41
San Mehat64e6a452010-02-04 20:53:48 -080042 /**
San Mehat64e6a452010-02-04 20:53:48 -080043 * The notification that is shown when a USB mass storage host
John Spurlock209bede2013-07-17 12:23:27 -040044 * is connected.
San Mehat64e6a452010-02-04 20:53:48 -080045 * <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 */
San Mehatb1043402010-02-05 08:26:50 -080060 private Notification mMediaStorageNotification;
61 private boolean mUmsAvailable;
62 private StorageManager mStorageManager;
San Mehat64e6a452010-02-04 20:53:48 -080063
Daniel Sandler5b8743f2010-11-03 09:43:46 -040064 private Handler mAsyncEventHandler;
65
John Spurlock3e309b22013-06-25 11:01:29 -040066 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 }
San Mehat64e6a452010-02-04 20:53:48 -080085
John Spurlock3e309b22013-06-25 11:01:29 -040086 @Override
87 public void start() {
88 mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
Joe Onoratofe4f3ae2010-06-04 11:25:26 -070089 final boolean connected = mStorageManager.isUsbMassStorageConnected();
John Spurlockcd686b52013-06-05 10:13:46 -040090 if (DEBUG) Log.d(TAG, String.format( "Startup with UMS connection %s (media state %s)",
Dianne Hackborn40e9f292012-11-27 19:12:23 -080091 mUmsAvailable, Environment.getExternalStorageState()));
John Spurlock3e309b22013-06-25 11:01:29 -040092
Daniel Sandler5b8743f2010-11-03 09:43:46 -040093 HandlerThread thr = new HandlerThread("SystemUI StorageNotification");
94 thr.start();
95 mAsyncEventHandler = new Handler(thr.getLooper());
96
John Spurlock3e309b22013-06-25 11:01:29 -040097 StorageNotificationEventListener listener = new StorageNotificationEventListener();
98 listener.onUsbMassStorageConnectionChanged(connected);
99 mStorageManager.registerListener(listener);
Daniel Sandler5b8743f2010-11-03 09:43:46 -0400100 }
101
102 private void onUsbMassStorageConnectionChangedAsync(boolean connected) {
San Mehatb1043402010-02-05 08:26:50 -0800103 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
John Spurlockcd686b52013-06-05 10:13:46 -0400110 if (DEBUG) Log.i(TAG, String.format("UMS connection changed to %s (media state %s)",
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800111 connected, st));
San Mehatb1043402010-02-05 08:26:50 -0800112
113 if (connected && (st.equals(
114 Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) {
San Mehat64e6a452010-02-04 20:53:48 -0800115 /*
San Mehatb1043402010-02-05 08:26:50 -0800116 * No card or card being checked = don't display
San Mehat64e6a452010-02-04 20:53:48 -0800117 */
San Mehatb1043402010-02-05 08:26:50 -0800118 connected = false;
San Mehat64e6a452010-02-04 20:53:48 -0800119 }
San Mehatb1043402010-02-05 08:26:50 -0800120 updateUsbMassStorageNotification(connected);
San Mehat64e6a452010-02-04 20:53:48 -0800121 }
122
Daniel Sandler5b8743f2010-11-03 09:43:46 -0400123 private void onStorageStateChangedAsync(String path, String oldState, String newState) {
John Spurlockcd686b52013-06-05 10:13:46 -0400124 if (DEBUG) Log.i(TAG, String.format(
San Mehatb1043402010-02-05 08:26:50 -0800125 "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState));
San Mehat64e6a452010-02-04 20:53:48 -0800126 if (newState.equals(Environment.MEDIA_SHARED)) {
San Mehatb1043402010-02-05 08:26:50 -0800127 /*
128 * Storage is now shared. Modify the UMS notification
129 * for stopping UMS.
130 */
San Mehat64e6a452010-02-04 20:53:48 -0800131 Intent intent = new Intent();
Joe Onoratofe4f3ae2010-06-04 11:25:26 -0700132 intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
San Mehat64e6a452010-02-04 20:53:48 -0800133 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)) {
San Mehatb1043402010-02-05 08:26:50 -0800139 /*
140 * Storage is now checking. Update media notification and disable
141 * UMS notification.
142 */
San Mehat64e6a452010-02-04 20:53:48 -0800143 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)) {
San Mehatb1043402010-02-05 08:26:50 -0800149 /*
150 * Storage is now mounted. Dismiss any media notifications,
151 * and enable UMS notification if connected.
152 */
San Mehat64e6a452010-02-04 20:53:48 -0800153 setMediaStorageNotification(0, 0, 0, false, false, null);
154 updateUsbMassStorageNotification(mUmsAvailable);
155 } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) {
San Mehatb1043402010-02-05 08:26:50 -0800156 /*
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 */
Dianne Hackbornd39d5152010-10-11 17:14:31 -0700174 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 }
San Mehatb1043402010-02-05 08:26:50 -0800184 updateUsbMassStorageNotification(mUmsAvailable);
185 }
San Mehat64e6a452010-02-04 20:53:48 -0800186 } else {
San Mehatb1043402010-02-05 08:26:50 -0800187 /*
188 * The unmount was due to UMS being enabled. Dismiss any
189 * media notifications, and disable the UMS notification
190 */
San Mehat64e6a452010-02-04 20:53:48 -0800191 setMediaStorageNotification(0, 0, 0, false, false, null);
San Mehatb1043402010-02-05 08:26:50 -0800192 updateUsbMassStorageNotification(false);
San Mehat64e6a452010-02-04 20:53:48 -0800193 }
San Mehat64e6a452010-02-04 20:53:48 -0800194 } else if (newState.equals(Environment.MEDIA_NOFS)) {
San Mehatb1043402010-02-05 08:26:50 -0800195 /*
196 * Storage has no filesystem. Show blank media notification,
197 * and enable UMS notification if connected.
198 */
San Mehat64e6a452010-02-04 20:53:48 -0800199 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)) {
San Mehatb1043402010-02-05 08:26:50 -0800209 /*
210 * Storage is corrupt. Show corrupt media notification,
211 * and enable UMS notification if connected.
212 */
San Mehat64e6a452010-02-04 20:53:48 -0800213 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,
John Spurlock209bede2013-07-17 12:23:27 -0400220 com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi);
San Mehat64e6a452010-02-04 20:53:48 -0800221 updateUsbMassStorageNotification(mUmsAvailable);
San Mehatb1043402010-02-05 08:26:50 -0800222 } 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 {
John Spurlockcd686b52013-06-05 10:13:46 -0400245 Log.w(TAG, String.format("Ignoring unknown state {%s}", newState));
San Mehat64e6a452010-02-04 20:53:48 -0800246 }
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();
Joe Onoratofe4f3ae2010-06-04 11:25:26 -0700256 intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
San Mehat64e6a452010-02-04 20:53:48 -0800257 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Daniel Sandlerc07907e2010-02-22 15:08:41 -0500258
San Mehat64e6a452010-02-04 20:53:48 -0800259 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 */
San Mehat4154c072010-02-09 18:37:54 -0800273 private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon,
274 boolean sound, boolean visible, PendingIntent pi) {
San Mehat64e6a452010-02-04 20:53:48 -0800275
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 }
John Spurlock209bede2013-07-17 12:23:27 -0400286
San Mehat64e6a452010-02-04 20:53:48 -0800287 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 }
John Spurlock209bede2013-07-17 12:23:27 -0400303
San Mehat64e6a452010-02-04 20:53:48 -0800304 mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
305
306 mUsbStorageNotification.tickerText = title;
307 if (pi == null) {
308 Intent intent = new Intent();
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700309 pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
310 UserHandle.CURRENT);
San Mehat64e6a452010-02-04 20:53:48 -0800311 }
312
313 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700314 final boolean adbOn = 1 == Settings.Global.getInt(
Daniel Sandlerf7d2b4a2010-07-08 13:07:41 -0400315 mContext.getContentResolver(),
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700316 Settings.Global.ADB_ENABLED,
Daniel Sandlerf7d2b4a2010-07-08 13:07:41 -0400317 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 }
San Mehat64e6a452010-02-04 20:53:48 -0800331 }
John Spurlock209bede2013-07-17 12:23:27 -0400332
San Mehat64e6a452010-02-04 20:53:48 -0800333 final int notificationId = mUsbStorageNotification.icon;
334 if (visible) {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700335 notificationManager.notifyAsUser(null, notificationId, mUsbStorageNotification,
336 UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800337 } else {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700338 notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800339 }
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 }
John Spurlock209bede2013-07-17 12:23:27 -0400376
San Mehat64e6a452010-02-04 20:53:48 -0800377 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();
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700398 pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
399 UserHandle.CURRENT);
San Mehat64e6a452010-02-04 20:53:48 -0800400 }
401
402 mMediaStorageNotification.icon = icon;
403 mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
404 }
John Spurlock209bede2013-07-17 12:23:27 -0400405
San Mehat64e6a452010-02-04 20:53:48 -0800406 final int notificationId = mMediaStorageNotification.icon;
407 if (visible) {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700408 notificationManager.notifyAsUser(null, notificationId,
409 mMediaStorageNotification, UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800410 } else {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700411 notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800412 }
413 }
414}