blob: 91fc67a84ac99be1c8f93e8f9fb221ab08dc05be [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";
36
Daniel Sandlerc07907e2010-02-22 15:08:41 -050037 private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true;
38
San Mehat64e6a452010-02-04 20:53:48 -080039 /**
40 * Binder context for this service
41 */
42 private Context mContext;
43
44 /**
45 * The notification that is shown when a USB mass storage host
46 * is connected.
47 * <p>
48 * This is lazily created, so use {@link #setUsbStorageNotification()}.
49 */
50 private Notification mUsbStorageNotification;
51
52 /**
53 * The notification that is shown when the following media events occur:
54 * - Media is being checked
55 * - Media is blank (or unknown filesystem)
56 * - Media is corrupt
57 * - Media is safe to unmount
58 * - Media is missing
59 * <p>
60 * This is lazily created, so use {@link #setMediaStorageNotification()}.
61 */
San Mehatb1043402010-02-05 08:26:50 -080062 private Notification mMediaStorageNotification;
63 private boolean mUmsAvailable;
64 private StorageManager mStorageManager;
San Mehat64e6a452010-02-04 20:53:48 -080065
Daniel Sandler5b8743f2010-11-03 09:43:46 -040066 private Handler mAsyncEventHandler;
67
San Mehat64e6a452010-02-04 20:53:48 -080068 public StorageNotification(Context context) {
69 mContext = context;
70
San Mehatb1043402010-02-05 08:26:50 -080071 mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Joe Onoratofe4f3ae2010-06-04 11:25:26 -070072 final boolean connected = mStorageManager.isUsbMassStorageConnected();
Joe Onorato8a9b2202010-02-26 18:56:32 -080073 Slog.d(TAG, String.format( "Startup with UMS connection %s (media state %s)", mUmsAvailable,
San Mehatb1043402010-02-05 08:26:50 -080074 Environment.getExternalStorageState()));
Daniel Sandler5b8743f2010-11-03 09:43:46 -040075
76 HandlerThread thr = new HandlerThread("SystemUI StorageNotification");
77 thr.start();
78 mAsyncEventHandler = new Handler(thr.getLooper());
79
Joe Onoratofe4f3ae2010-06-04 11:25:26 -070080 onUsbMassStorageConnectionChanged(connected);
San Mehat64e6a452010-02-04 20:53:48 -080081 }
82
San Mehatb1043402010-02-05 08:26:50 -080083 /*
84 * @override com.android.os.storage.StorageEventListener
85 */
86 @Override
Daniel Sandler5b8743f2010-11-03 09:43:46 -040087 public void onUsbMassStorageConnectionChanged(final boolean connected) {
88 mAsyncEventHandler.post(new Runnable() {
89 @Override
90 public void run() {
91 onUsbMassStorageConnectionChangedAsync(connected);
92 }
93 });
94 }
95
96 private void onUsbMassStorageConnectionChangedAsync(boolean connected) {
San Mehatb1043402010-02-05 08:26:50 -080097 mUmsAvailable = connected;
98 /*
99 * Even though we may have a UMS host connected, we the SD card
100 * may not be in a state for export.
101 */
102 String st = Environment.getExternalStorageState();
103
Joe Onorato8a9b2202010-02-26 18:56:32 -0800104 Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st));
San Mehatb1043402010-02-05 08:26:50 -0800105
106 if (connected && (st.equals(
107 Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) {
San Mehat64e6a452010-02-04 20:53:48 -0800108 /*
San Mehatb1043402010-02-05 08:26:50 -0800109 * No card or card being checked = don't display
San Mehat64e6a452010-02-04 20:53:48 -0800110 */
San Mehatb1043402010-02-05 08:26:50 -0800111 connected = false;
San Mehat64e6a452010-02-04 20:53:48 -0800112 }
San Mehatb1043402010-02-05 08:26:50 -0800113 updateUsbMassStorageNotification(connected);
San Mehat64e6a452010-02-04 20:53:48 -0800114 }
115
San Mehatb1043402010-02-05 08:26:50 -0800116 /*
117 * @override com.android.os.storage.StorageEventListener
118 */
119 @Override
Daniel Sandler5b8743f2010-11-03 09:43:46 -0400120 public void onStorageStateChanged(final String path, final String oldState, final String newState) {
121 mAsyncEventHandler.post(new Runnable() {
122 @Override
123 public void run() {
124 onStorageStateChangedAsync(path, oldState, newState);
125 }
126 });
127 }
128
129 private void onStorageStateChangedAsync(String path, String oldState, String newState) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800130 Slog.i(TAG, String.format(
San Mehatb1043402010-02-05 08:26:50 -0800131 "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState));
San Mehat64e6a452010-02-04 20:53:48 -0800132 if (newState.equals(Environment.MEDIA_SHARED)) {
San Mehatb1043402010-02-05 08:26:50 -0800133 /*
134 * Storage is now shared. Modify the UMS notification
135 * for stopping UMS.
136 */
San Mehat64e6a452010-02-04 20:53:48 -0800137 Intent intent = new Intent();
Joe Onoratofe4f3ae2010-06-04 11:25:26 -0700138 intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
San Mehat64e6a452010-02-04 20:53:48 -0800139 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
140 setUsbStorageNotification(
141 com.android.internal.R.string.usb_storage_stop_notification_title,
142 com.android.internal.R.string.usb_storage_stop_notification_message,
143 com.android.internal.R.drawable.stat_sys_warning, false, true, pi);
144 } else if (newState.equals(Environment.MEDIA_CHECKING)) {
San Mehatb1043402010-02-05 08:26:50 -0800145 /*
146 * Storage is now checking. Update media notification and disable
147 * UMS notification.
148 */
San Mehat64e6a452010-02-04 20:53:48 -0800149 setMediaStorageNotification(
150 com.android.internal.R.string.ext_media_checking_notification_title,
151 com.android.internal.R.string.ext_media_checking_notification_message,
152 com.android.internal.R.drawable.stat_notify_sdcard_prepare, true, false, null);
153 updateUsbMassStorageNotification(false);
154 } else if (newState.equals(Environment.MEDIA_MOUNTED)) {
San Mehatb1043402010-02-05 08:26:50 -0800155 /*
156 * Storage is now mounted. Dismiss any media notifications,
157 * and enable UMS notification if connected.
158 */
San Mehat64e6a452010-02-04 20:53:48 -0800159 setMediaStorageNotification(0, 0, 0, false, false, null);
160 updateUsbMassStorageNotification(mUmsAvailable);
161 } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) {
San Mehatb1043402010-02-05 08:26:50 -0800162 /*
163 * Storage is now unmounted. We may have been unmounted
164 * because the user is enabling/disabling UMS, in which case we don't
165 * want to display the 'safe to unmount' notification.
166 */
167 if (!mStorageManager.isUsbMassStorageEnabled()) {
168 if (oldState.equals(Environment.MEDIA_SHARED)) {
169 /*
170 * The unmount was due to UMS being enabled. Dismiss any
171 * media notifications, and enable UMS notification if connected
172 */
173 setMediaStorageNotification(0, 0, 0, false, false, null);
174 updateUsbMassStorageNotification(mUmsAvailable);
175 } else {
176 /*
177 * Show safe to unmount media notification, and enable UMS
178 * notification if connected.
179 */
Dianne Hackbornd39d5152010-10-11 17:14:31 -0700180 if (Environment.isExternalStorageRemovable()) {
181 setMediaStorageNotification(
182 com.android.internal.R.string.ext_media_safe_unmount_notification_title,
183 com.android.internal.R.string.ext_media_safe_unmount_notification_message,
184 com.android.internal.R.drawable.stat_notify_sdcard, true, true, null);
185 } else {
186 // This device does not have removable storage, so
187 // don't tell the user they can remove it.
188 setMediaStorageNotification(0, 0, 0, false, false, null);
189 }
San Mehatb1043402010-02-05 08:26:50 -0800190 updateUsbMassStorageNotification(mUmsAvailable);
191 }
San Mehat64e6a452010-02-04 20:53:48 -0800192 } else {
San Mehatb1043402010-02-05 08:26:50 -0800193 /*
194 * The unmount was due to UMS being enabled. Dismiss any
195 * media notifications, and disable the UMS notification
196 */
San Mehat64e6a452010-02-04 20:53:48 -0800197 setMediaStorageNotification(0, 0, 0, false, false, null);
San Mehatb1043402010-02-05 08:26:50 -0800198 updateUsbMassStorageNotification(false);
San Mehat64e6a452010-02-04 20:53:48 -0800199 }
San Mehat64e6a452010-02-04 20:53:48 -0800200 } else if (newState.equals(Environment.MEDIA_NOFS)) {
San Mehatb1043402010-02-05 08:26:50 -0800201 /*
202 * Storage has no filesystem. Show blank media notification,
203 * and enable UMS notification if connected.
204 */
San Mehat64e6a452010-02-04 20:53:48 -0800205 Intent intent = new Intent();
206 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
207 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
208
209 setMediaStorageNotification(
210 com.android.internal.R.string.ext_media_nofs_notification_title,
211 com.android.internal.R.string.ext_media_nofs_notification_message,
212 com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi);
213 updateUsbMassStorageNotification(mUmsAvailable);
214 } else if (newState.equals(Environment.MEDIA_UNMOUNTABLE)) {
San Mehatb1043402010-02-05 08:26:50 -0800215 /*
216 * Storage is corrupt. Show corrupt media notification,
217 * and enable UMS notification if connected.
218 */
San Mehat64e6a452010-02-04 20:53:48 -0800219 Intent intent = new Intent();
220 intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
221 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
222
223 setMediaStorageNotification(
224 com.android.internal.R.string.ext_media_unmountable_notification_title,
225 com.android.internal.R.string.ext_media_unmountable_notification_message,
226 com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi);
227 updateUsbMassStorageNotification(mUmsAvailable);
San Mehatb1043402010-02-05 08:26:50 -0800228 } else if (newState.equals(Environment.MEDIA_REMOVED)) {
229 /*
230 * Storage has been removed. Show nomedia media notification,
231 * and disable UMS notification regardless of connection state.
232 */
233 setMediaStorageNotification(
234 com.android.internal.R.string.ext_media_nomedia_notification_title,
235 com.android.internal.R.string.ext_media_nomedia_notification_message,
236 com.android.internal.R.drawable.stat_notify_sdcard_usb,
237 true, false, null);
238 updateUsbMassStorageNotification(false);
239 } else if (newState.equals(Environment.MEDIA_BAD_REMOVAL)) {
240 /*
241 * Storage has been removed unsafely. Show bad removal media notification,
242 * and disable UMS notification regardless of connection state.
243 */
244 setMediaStorageNotification(
245 com.android.internal.R.string.ext_media_badremoval_notification_title,
246 com.android.internal.R.string.ext_media_badremoval_notification_message,
247 com.android.internal.R.drawable.stat_sys_warning,
248 true, true, null);
249 updateUsbMassStorageNotification(false);
250 } else {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800251 Slog.w(TAG, String.format("Ignoring unknown state {%s}", newState));
San Mehat64e6a452010-02-04 20:53:48 -0800252 }
253 }
254
255 /**
256 * Update the state of the USB mass storage notification
257 */
258 void updateUsbMassStorageNotification(boolean available) {
259
260 if (available) {
261 Intent intent = new Intent();
Joe Onoratofe4f3ae2010-06-04 11:25:26 -0700262 intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class);
San Mehat64e6a452010-02-04 20:53:48 -0800263 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Daniel Sandlerc07907e2010-02-22 15:08:41 -0500264
San Mehat64e6a452010-02-04 20:53:48 -0800265 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
266 setUsbStorageNotification(
267 com.android.internal.R.string.usb_storage_notification_title,
268 com.android.internal.R.string.usb_storage_notification_message,
269 com.android.internal.R.drawable.stat_sys_data_usb,
270 false, true, pi);
271 } else {
272 setUsbStorageNotification(0, 0, 0, false, false, null);
273 }
274 }
275
276 /**
277 * Sets the USB storage notification.
278 */
San Mehat4154c072010-02-09 18:37:54 -0800279 private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon,
280 boolean sound, boolean visible, PendingIntent pi) {
San Mehat64e6a452010-02-04 20:53:48 -0800281
282 if (!visible && mUsbStorageNotification == null) {
283 return;
284 }
285
286 NotificationManager notificationManager = (NotificationManager) mContext
287 .getSystemService(Context.NOTIFICATION_SERVICE);
288
289 if (notificationManager == null) {
290 return;
291 }
292
293 if (visible) {
294 Resources r = Resources.getSystem();
295 CharSequence title = r.getText(titleId);
296 CharSequence message = r.getText(messageId);
297
298 if (mUsbStorageNotification == null) {
299 mUsbStorageNotification = new Notification();
300 mUsbStorageNotification.icon = icon;
301 mUsbStorageNotification.when = 0;
302 }
303
304 if (sound) {
305 mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
306 } else {
307 mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
308 }
309
310 mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
311
312 mUsbStorageNotification.tickerText = title;
313 if (pi == null) {
314 Intent intent = new Intent();
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700315 pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
316 UserHandle.CURRENT);
San Mehat64e6a452010-02-04 20:53:48 -0800317 }
318
319 mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700320 final boolean adbOn = 1 == Settings.Global.getInt(
Daniel Sandlerf7d2b4a2010-07-08 13:07:41 -0400321 mContext.getContentResolver(),
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700322 Settings.Global.ADB_ENABLED,
Daniel Sandlerf7d2b4a2010-07-08 13:07:41 -0400323 0);
324
325 if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) {
326 // Pop up a full-screen alert to coach the user through enabling UMS. The average
327 // user has attached the device to USB either to charge the phone (in which case
328 // this is harmless) or transfer files, and in the latter case this alert saves
329 // several steps (as well as subtly indicates that you shouldn't mix UMS with other
330 // activities on the device).
331 //
332 // If ADB is enabled, however, we suppress this dialog (under the assumption that a
333 // developer (a) knows how to enable UMS, and (b) is probably using USB to install
334 // builds or use adb commands.
335 mUsbStorageNotification.fullScreenIntent = pi;
336 }
San Mehat64e6a452010-02-04 20:53:48 -0800337 }
338
339 final int notificationId = mUsbStorageNotification.icon;
340 if (visible) {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700341 notificationManager.notifyAsUser(null, notificationId, mUsbStorageNotification,
342 UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800343 } else {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700344 notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800345 }
346 }
347
348 private synchronized boolean getMediaStorageNotificationDismissable() {
349 if ((mMediaStorageNotification != null) &&
350 ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
351 Notification.FLAG_AUTO_CANCEL))
352 return true;
353
354 return false;
355 }
356
357 /**
358 * Sets the media storage notification.
359 */
360 private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
361 boolean dismissable, PendingIntent pi) {
362
363 if (!visible && mMediaStorageNotification == null) {
364 return;
365 }
366
367 NotificationManager notificationManager = (NotificationManager) mContext
368 .getSystemService(Context.NOTIFICATION_SERVICE);
369
370 if (notificationManager == null) {
371 return;
372 }
373
374 if (mMediaStorageNotification != null && visible) {
375 /*
376 * Dismiss the previous notification - we're about to
377 * re-use it.
378 */
379 final int notificationId = mMediaStorageNotification.icon;
380 notificationManager.cancel(notificationId);
381 }
382
383 if (visible) {
384 Resources r = Resources.getSystem();
385 CharSequence title = r.getText(titleId);
386 CharSequence message = r.getText(messageId);
387
388 if (mMediaStorageNotification == null) {
389 mMediaStorageNotification = new Notification();
390 mMediaStorageNotification.when = 0;
391 }
392
393 mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
394
395 if (dismissable) {
396 mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
397 } else {
398 mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
399 }
400
401 mMediaStorageNotification.tickerText = title;
402 if (pi == null) {
403 Intent intent = new Intent();
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700404 pi = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
405 UserHandle.CURRENT);
San Mehat64e6a452010-02-04 20:53:48 -0800406 }
407
408 mMediaStorageNotification.icon = icon;
409 mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
410 }
411
412 final int notificationId = mMediaStorageNotification.icon;
413 if (visible) {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700414 notificationManager.notifyAsUser(null, notificationId,
415 mMediaStorageNotification, UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800416 } else {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700417 notificationManager.cancelAsUser(null, notificationId, UserHandle.ALL);
San Mehat64e6a452010-02-04 20:53:48 -0800418 }
419 }
420}