blob: 06696febad803320949ce53ee0cf1eb7539e9d9e [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;
Joe Onorato8a9b2202010-02-26 18:56:32 -080032import android.util.Slog;
San Mehat64e6a452010-02-04 20:53:48 -080033
San Mehatb1043402010-02-05 08:26:50 -080034public class StorageNotification extends StorageEventListener {
San Mehat64e6a452010-02-04 20:53:48 -080035 private static final String TAG = "StorageNotification";
Dianne Hackborn40e9f292012-11-27 19:12:23 -080036 private static final boolean DEBUG = false;
San Mehat64e6a452010-02-04 20:53:48 -080037
Daniel Sandlerc07907e2010-02-22 15:08:41 -050038 private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true;
39
San Mehat64e6a452010-02-04 20:53:48 -080040 /**
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 */
San Mehatb1043402010-02-05 08:26:50 -080063 private Notification mMediaStorageNotification;
64 private boolean mUmsAvailable;
65 private StorageManager mStorageManager;
San Mehat64e6a452010-02-04 20:53:48 -080066
Daniel Sandler5b8743f2010-11-03 09:43:46 -040067 private Handler mAsyncEventHandler;
68
San Mehat64e6a452010-02-04 20:53:48 -080069 public StorageNotification(Context context) {
70 mContext = context;
71
San Mehatb1043402010-02-05 08:26:50 -080072 mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Joe Onoratofe4f3ae2010-06-04 11:25:26 -070073 final boolean connected = mStorageManager.isUsbMassStorageConnected();
Dianne Hackborn40e9f292012-11-27 19:12:23 -080074 if (DEBUG) Slog.d(TAG, String.format( "Startup with UMS connection %s (media state %s)",
75 mUmsAvailable, Environment.getExternalStorageState()));
Daniel Sandler5b8743f2010-11-03 09:43:46 -040076
77 HandlerThread thr = new HandlerThread("SystemUI StorageNotification");
78 thr.start();
79 mAsyncEventHandler = new Handler(thr.getLooper());
80
Joe Onoratofe4f3ae2010-06-04 11:25:26 -070081 onUsbMassStorageConnectionChanged(connected);
San Mehat64e6a452010-02-04 20:53:48 -080082 }
83
San Mehatb1043402010-02-05 08:26:50 -080084 /*
85 * @override com.android.os.storage.StorageEventListener
86 */
87 @Override
Daniel Sandler5b8743f2010-11-03 09:43:46 -040088 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) {
San Mehatb1043402010-02-05 08:26:50 -080098 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
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800105 if (DEBUG) Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)",
106 connected, st));
San Mehatb1043402010-02-05 08:26:50 -0800107
108 if (connected && (st.equals(
109 Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) {
San Mehat64e6a452010-02-04 20:53:48 -0800110 /*
San Mehatb1043402010-02-05 08:26:50 -0800111 * No card or card being checked = don't display
San Mehat64e6a452010-02-04 20:53:48 -0800112 */
San Mehatb1043402010-02-05 08:26:50 -0800113 connected = false;
San Mehat64e6a452010-02-04 20:53:48 -0800114 }
San Mehatb1043402010-02-05 08:26:50 -0800115 updateUsbMassStorageNotification(connected);
San Mehat64e6a452010-02-04 20:53:48 -0800116 }
117
San Mehatb1043402010-02-05 08:26:50 -0800118 /*
119 * @override com.android.os.storage.StorageEventListener
120 */
121 @Override
Daniel Sandler5b8743f2010-11-03 09:43:46 -0400122 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) {
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800132 if (DEBUG) Slog.i(TAG, String.format(
San Mehatb1043402010-02-05 08:26:50 -0800133 "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState));
San Mehat64e6a452010-02-04 20:53:48 -0800134 if (newState.equals(Environment.MEDIA_SHARED)) {
San Mehatb1043402010-02-05 08:26:50 -0800135 /*
136 * Storage is now shared. Modify the UMS notification
137 * for stopping UMS.
138 */
San Mehat64e6a452010-02-04 20:53:48 -0800139 Intent intent = new Intent();
Joe Onoratofe4f3ae2010-06-04 11:25:26 -0700140 intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
San Mehat64e6a452010-02-04 20:53:48 -0800141 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)) {
San Mehatb1043402010-02-05 08:26:50 -0800147 /*
148 * Storage is now checking. Update media notification and disable
149 * UMS notification.
150 */
San Mehat64e6a452010-02-04 20:53:48 -0800151 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)) {
San Mehatb1043402010-02-05 08:26:50 -0800157 /*
158 * Storage is now mounted. Dismiss any media notifications,
159 * and enable UMS notification if connected.
160 */
San Mehat64e6a452010-02-04 20:53:48 -0800161 setMediaStorageNotification(0, 0, 0, false, false, null);
162 updateUsbMassStorageNotification(mUmsAvailable);
163 } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) {
San Mehatb1043402010-02-05 08:26:50 -0800164 /*
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 */
Dianne Hackbornd39d5152010-10-11 17:14:31 -0700182 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 }
San Mehatb1043402010-02-05 08:26:50 -0800192 updateUsbMassStorageNotification(mUmsAvailable);
193 }
San Mehat64e6a452010-02-04 20:53:48 -0800194 } else {
San Mehatb1043402010-02-05 08:26:50 -0800195 /*
196 * The unmount was due to UMS being enabled. Dismiss any
197 * media notifications, and disable the UMS notification
198 */
San Mehat64e6a452010-02-04 20:53:48 -0800199 setMediaStorageNotification(0, 0, 0, false, false, null);
San Mehatb1043402010-02-05 08:26:50 -0800200 updateUsbMassStorageNotification(false);
San Mehat64e6a452010-02-04 20:53:48 -0800201 }
San Mehat64e6a452010-02-04 20:53:48 -0800202 } else if (newState.equals(Environment.MEDIA_NOFS)) {
San Mehatb1043402010-02-05 08:26:50 -0800203 /*
204 * Storage has no filesystem. Show blank media notification,
205 * and enable UMS notification if connected.
206 */
San Mehat64e6a452010-02-04 20:53:48 -0800207 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)) {
San Mehatb1043402010-02-05 08:26:50 -0800217 /*
218 * Storage is corrupt. Show corrupt media notification,
219 * and enable UMS notification if connected.
220 */
San Mehat64e6a452010-02-04 20:53:48 -0800221 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);
San Mehatb1043402010-02-05 08:26:50 -0800230 } 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 {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800253 Slog.w(TAG, String.format("Ignoring unknown state {%s}", newState));
San Mehat64e6a452010-02-04 20:53:48 -0800254 }
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();
Joe Onoratofe4f3ae2010-06-04 11:25:26 -0700264 intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
San Mehat64e6a452010-02-04 20:53:48 -0800265 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Daniel Sandlerc07907e2010-02-22 15:08:41 -0500266
San Mehat64e6a452010-02-04 20:53:48 -0800267 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 */
San Mehat4154c072010-02-09 18:37:54 -0800281 private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon,
282 boolean sound, boolean visible, PendingIntent pi) {
San Mehat64e6a452010-02-04 20:53:48 -0800283
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();
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700317 pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
318 UserHandle.CURRENT);
San Mehat64e6a452010-02-04 20:53:48 -0800319 }
320
321 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700322 final boolean adbOn = 1 == Settings.Global.getInt(
Daniel Sandlerf7d2b4a2010-07-08 13:07:41 -0400323 mContext.getContentResolver(),
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700324 Settings.Global.ADB_ENABLED,
Daniel Sandlerf7d2b4a2010-07-08 13:07:41 -0400325 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 }
San Mehat64e6a452010-02-04 20:53:48 -0800339 }
340
341 final int notificationId = mUsbStorageNotification.icon;
342 if (visible) {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700343 notificationManager.notifyAsUser(null, notificationId, mUsbStorageNotification,
344 UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800345 } else {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700346 notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800347 }
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();
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700406 pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
407 UserHandle.CURRENT);
San Mehat64e6a452010-02-04 20:53:48 -0800408 }
409
410 mMediaStorageNotification.icon = icon;
411 mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
412 }
413
414 final int notificationId = mMediaStorageNotification.icon;
415 if (visible) {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700416 notificationManager.notifyAsUser(null, notificationId,
417 mMediaStorageNotification, UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800418 } else {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700419 notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800420 }
421 }
422}