NotificationController.java revision 7126e5ae53082e1f16eb0624208b6ab7edebd392
1/* 2 * Copyright (C) 2010 The Android Open Source Project 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.email; 18 19import com.android.email.activity.ContactStatusLoader; 20import com.android.email.activity.Welcome; 21import com.android.email.activity.setup.AccountSettingsXL; 22import com.android.email.mail.Address; 23import com.android.email.provider.EmailContent; 24import com.android.email.provider.EmailContent.Account; 25import com.android.email.provider.EmailContent.Attachment; 26import com.android.email.provider.EmailContent.Message; 27 28import android.app.Notification; 29import android.app.NotificationManager; 30import android.app.PendingIntent; 31import android.content.Context; 32import android.content.Intent; 33import android.graphics.Bitmap; 34import android.graphics.BitmapFactory; 35import android.media.AudioManager; 36import android.net.Uri; 37import android.text.TextUtils; 38 39/** 40 * Class that manages notifications. 41 * 42 * TODO Gather all notification related code here 43 */ 44public class NotificationController { 45 public static final int NOTIFICATION_ID_SECURITY_NEEDED = 1; 46 public static final int NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED = 2; 47 public static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3; 48 49 private static final int NOTIFICATION_ID_BASE_NEW_MESSAGES = 0x10000000; 50 private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000; 51 52 private static NotificationController sInstance; 53 private final Context mContext; 54 private final NotificationManager mNotificationManager; 55 private final AudioManager mAudioManager; 56 57 /** Constructor */ 58 private NotificationController(Context context) { 59 mContext = context.getApplicationContext(); 60 mNotificationManager = (NotificationManager) context.getSystemService( 61 Context.NOTIFICATION_SERVICE); 62 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 63 } 64 65 /** Singleton access */ 66 public static synchronized NotificationController getInstance(Context context) { 67 if (sInstance == null) { 68 sInstance = new NotificationController(context); 69 } 70 return sInstance; 71 } 72 73 /** 74 * @return the "new message" notification ID for an account. It just assumes 75 * accountID won't be too huge. Any other smarter/cleaner way? 76 */ 77 private int getNewMessageNotificationId(long accountId) { 78 return (int) (NOTIFICATION_ID_BASE_NEW_MESSAGES + accountId); 79 } 80 81 /** 82 * Dismiss new message notification 83 * 84 * @param accountId ID of the target account, or -1 for all accounts. 85 */ 86 public void cancelNewMessageNotification(long accountId) { 87 if (accountId == -1) { 88 new Utility.ForEachAccount(mContext) { 89 @Override 90 protected void performAction(long accountId) { 91 cancelNewMessageNotification(accountId); 92 } 93 }.execute(); 94 } else { 95 mNotificationManager.cancel(getNewMessageNotificationId(accountId)); 96 } 97 } 98 99 /** 100 * Show (or update) the "new message" notification. 101 */ 102 public void showNewMessageNotification(final long accountId, final int unseenMessageCount, 103 final int justFetchedCount) { 104 Utility.runAsync(new Runnable() { 105 @Override 106 public void run() { 107 Notification n = createNewMessageNotification(accountId, unseenMessageCount, 108 justFetchedCount); 109 if (n == null) { 110 return; 111 } 112 mNotificationManager.notify(getNewMessageNotificationId(accountId), n); 113 } 114 }); 115 } 116 117 /** 118 * @return The sender's photo, if available, or null. 119 * 120 * Don't call it on the UI thread. 121 */ 122 private Bitmap getSenderPhoto(Message message) { 123 Address sender = Address.unpackFirst(message.mFrom); 124 if (sender == null) { 125 return null; 126 } 127 String email = sender.getAddress(); 128 if (TextUtils.isEmpty(email)) { 129 return null; 130 } 131 return ContactStatusLoader.load(mContext, email).mPhoto; 132 } 133 134 private Bitmap[] getNotificationBitmaps(Bitmap senderPhoto) { 135 // TODO Should we cache these objects? (bitmaps and arrays) 136 // They don't have to be on this process's memory once we post a notification request to 137 // the system, and decodeResource() seems to be reasonably fast. We don't want them to 138 // take up memory when not necessary. 139 Bitmap appIcon = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.icon); 140 if (senderPhoto == null) { 141 return new Bitmap[] {appIcon}; 142 } else { 143 return new Bitmap[] {senderPhoto, appIcon}; 144 } 145 } 146 147 /** 148 * Create a notification 149 * 150 * Don't call it on the UI thread. 151 * 152 * TODO Test it when the UI is settled. 153 */ 154 private Notification createNewMessageNotification(long accountId, int unseenMessageCount, 155 int justFetchedCount) { 156 final Account account = Account.restoreAccountWithId(mContext, accountId); 157 if (account == null) { 158 return null; 159 } 160 // Get the latest message 161 final Message message = Message.getLatestMessage(mContext, accountId); 162 if (message == null) { 163 return null; // no message found??? 164 } 165 166 final String senderName = Address.toFriendly(Address.unpack(message.mFrom)); 167 final String subject = message.mSubject; 168 final Bitmap senderPhoto = getSenderPhoto(message); 169 170 // Intent to open inbox 171 PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, 172 Welcome.createOpenAccountInboxIntent(mContext, accountId), 173 PendingIntent.FLAG_UPDATE_CURRENT); 174 175 final String notificationTitle; 176 if (justFetchedCount == 1) { 177 notificationTitle = senderName; 178 } else { 179 notificationTitle = mContext.getResources().getQuantityString( 180 R.plurals.notification_sender_name_multi_messages, justFetchedCount - 1, 181 senderName, justFetchedCount - 1); 182 } 183 final String content = subject; 184 final String numNewMessages; 185 final int numAccounts = EmailContent.count(mContext, Account.CONTENT_URI); 186 if (numAccounts == 1) { 187 numNewMessages = mContext.getResources().getQuantityString( 188 R.plurals.notification_num_new_messages_single_account, unseenMessageCount, 189 unseenMessageCount, account.mDisplayName); 190 } else { 191 numNewMessages = mContext.getResources().getQuantityString( 192 R.plurals.notification_num_new_messages_multi_account, unseenMessageCount, 193 unseenMessageCount, account.mDisplayName); 194 } 195 196 Notification notification = new Notification(R.drawable.stat_notify_email_generic, 197 mContext.getString(R.string.notification_new_title), System.currentTimeMillis()); 198 notification.setLatestEventInfo(mContext, notificationTitle, subject, contentIntent); 199 200 notification.tickerTitle = notificationTitle; 201 // STOPSHIPO numNewMessages should be the 3rd line on expanded notification. But it's not 202 // clear how to do that yet. 203 // For now we just append it to subject. 204 notification.tickerSubtitle = subject + " " + numNewMessages; 205 notification.tickerIcons = getNotificationBitmaps(senderPhoto); 206 207 setupNotificationSoundAndVibrationFromAccount(notification, account); 208 return notification; 209 } 210 211 private boolean isRingerModeSilent() { 212 return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; 213 } 214 215 private void setupNotificationSoundAndVibrationFromAccount(Notification notification, 216 Account account) { 217 final int flags = account.mFlags; 218 final String ringtoneUri = account.mRingtoneUri; 219 final boolean vibrate = (flags & Account.FLAGS_VIBRATE_ALWAYS) != 0; 220 final boolean vibrateWhenSilent = (flags & Account.FLAGS_VIBRATE_WHEN_SILENT) != 0; 221 222 notification.sound = (ringtoneUri == null) ? null : Uri.parse(ringtoneUri); 223 224 if (vibrate || (vibrateWhenSilent && isRingerModeSilent())) { 225 notification.defaults |= Notification.DEFAULT_VIBRATE; 226 } 227 228 // This code is identical to that used by Gmail and GTalk for notifications 229 notification.flags |= Notification.FLAG_SHOW_LIGHTS; 230 notification.defaults |= Notification.DEFAULT_LIGHTS; 231 } 232 233 /** 234 * Generic warning notification 235 */ 236 public void showWarningNotification(int id, String tickerText, String notificationText, 237 Intent intent) { 238 PendingIntent pendingIntent = null; 239 if (intent != null) { 240 pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 241 PendingIntent.FLAG_UPDATE_CURRENT); 242 } 243 Notification n = new Notification(android.R.drawable.stat_notify_error, tickerText, 244 System.currentTimeMillis()); 245 n.setLatestEventInfo(mContext, tickerText, notificationText, pendingIntent); 246 n.flags = Notification.FLAG_AUTO_CANCEL; 247 mNotificationManager.notify(id, n); 248 } 249 250 /** 251 * Alert the user that an attachment couldn't be forwarded. This is a very unusual case, and 252 * perhaps we shouldn't even send a notification. For now, it's helpful for debugging. 253 */ 254 public void showDownloadForwardFailedNotification(Attachment att) { 255 showWarningNotification(NOTIFICATION_ID_ATTACHMENT_WARNING, 256 mContext.getString(R.string.forward_download_failed_ticker), 257 mContext.getString(R.string.forward_download_failed_notification, 258 att.mFileName), null); 259 } 260 261 /** 262 * Alert the user that login failed for the specified account 263 */ 264 private int getLoginFailedNotificationId(long accountId) { 265 return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int)accountId; 266 } 267 268 // NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS) 269 public void showLoginFailedNotification(long accountId) { 270 final Account account = Account.restoreAccountWithId(mContext, accountId); 271 if (account == null) return; 272 showWarningNotification(getLoginFailedNotificationId(accountId), 273 mContext.getString(R.string.login_failed_ticker, account.mDisplayName), 274 mContext.getString(R.string.login_failed_notification), 275 AccountSettingsXL.createAccountSettingsIntent(mContext, accountId)); 276 } 277 278 public void cancelLoginFailedNotification(long accountId) { 279 mNotificationManager.cancel(getLoginFailedNotificationId(accountId)); 280 } 281} 282