replicant-packages_apps_Email/src/com/android/email/NotificationController.java

349 lines
14 KiB
Java

/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email;
import com.android.email.activity.ContactStatusLoader;
import com.android.email.activity.Welcome;
import com.android.email.activity.setup.AccountSettingsXL;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Attachment;
import com.android.email.provider.EmailContent.Message;
import com.android.emailcommon.mail.Address;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.net.Uri;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
/**
* Class that manages notifications.
*
* TODO Gather all notification related code here
*/
public class NotificationController {
public static final int NOTIFICATION_ID_SECURITY_NEEDED = 1;
public static final int NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED = 2;
public static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3;
public static final int NOTIFICATION_ID_PASSWORD_EXPIRING = 4;
public static final int NOTIFICATION_ID_PASSWORD_EXPIRED = 5;
private static final int NOTIFICATION_ID_BASE_NEW_MESSAGES = 0x10000000;
private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000;
private static NotificationController sInstance;
private final Context mContext;
private final NotificationManager mNotificationManager;
private final AudioManager mAudioManager;
private final Bitmap mGenericSenderIcon;
private final Clock mClock;
/** Constructor */
/* package */ NotificationController(Context context, Clock clock) {
mContext = context.getApplicationContext();
mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mGenericSenderIcon = BitmapFactory.decodeResource(mContext.getResources(),
R.drawable.ic_contact_picture);
mClock = clock;
}
/** Singleton access */
public static synchronized NotificationController getInstance(Context context) {
if (sInstance == null) {
sInstance = new NotificationController(context, Clock.INSTANCE);
}
return sInstance;
}
/**
* Generic notifier for any account. Uses notification rules from account.
*
* @param account The account for which the notification is posted
* @param ticker String for ticker
* @param contentTitle String for notification content title
* @param contentText String for notification content text
* @param intent The intent to launch from the notification
* @param notificationId The notification id
*/
public void postAccountNotification(Account account, String ticker, String contentTitle,
String contentText, Intent intent, int notificationId) {
// Pending Intent
PendingIntent pending =
PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Ringtone & Vibration
String ringtoneString = account.getRingtone();
Uri ringTone = (ringtoneString == null) ? null : Uri.parse(ringtoneString);
boolean vibrate = 0 != (account.mFlags & Account.FLAGS_VIBRATE_ALWAYS);
boolean vibrateWhenSilent = 0 != (account.mFlags & Account.FLAGS_VIBRATE_WHEN_SILENT);
// Use the account's notification rules for sound & vibrate (but always notify)
boolean nowSilent =
mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
int defaults = Notification.DEFAULT_LIGHTS;
if (vibrate || (vibrateWhenSilent && nowSilent)) {
defaults |= Notification.DEFAULT_VIBRATE;
}
// Notification
Notification.Builder nb = new Notification.Builder(mContext);
nb.setSmallIcon(R.drawable.stat_notify_email_generic);
nb.setTicker(ticker);
nb.setContentTitle(contentTitle);
nb.setContentText(contentText);
nb.setContentIntent(pending);
nb.setSound(ringTone);
nb.setDefaults(defaults);
Notification notification = nb.getNotification();
mNotificationManager.notify(notificationId, notification);
}
/**
* Generic notification canceler.
* @param notificationId The notification id
*/
public void cancelNotification(int notificationId) {
mNotificationManager.cancel(notificationId);
}
/**
* @return the "new message" notification ID for an account. It just assumes
* accountID won't be too huge. Any other smarter/cleaner way?
*/
private int getNewMessageNotificationId(long accountId) {
return (int) (NOTIFICATION_ID_BASE_NEW_MESSAGES + accountId);
}
/**
* Dismiss new message notification
*
* @param accountId ID of the target account, or -1 for all accounts.
*/
public void cancelNewMessageNotification(long accountId) {
if (accountId == -1) {
new Utility.ForEachAccount(mContext) {
@Override
protected void performAction(long accountId) {
cancelNewMessageNotification(accountId);
}
}.execute();
} else {
mNotificationManager.cancel(getNewMessageNotificationId(accountId));
}
}
/**
* Show (or update) the "new message" notification.
*/
public void showNewMessageNotification(final long accountId, final int unseenMessageCount,
final int justFetchedCount) {
Utility.runAsync(new Runnable() {
@Override
public void run() {
Notification n = createNewMessageNotification(accountId, unseenMessageCount);
if (n == null) {
return;
}
mNotificationManager.notify(getNewMessageNotificationId(accountId), n);
}
});
}
/**
* @return The sender's photo, if available, or null.
*
* Don't call it on the UI thread.
*/
private Bitmap getSenderPhoto(Message message) {
Address sender = Address.unpackFirst(message.mFrom);
if (sender == null) {
return null;
}
String email = sender.getAddress();
if (TextUtils.isEmpty(email)) {
return null;
}
return ContactStatusLoader.load(mContext, email).mPhoto;
}
/**
* Create a notification
*
* Don't call it on the UI thread.
*/
/* package */ Notification createNewMessageNotification(long accountId,
int unseenMessageCount) {
final Account account = Account.restoreAccountWithId(mContext, accountId);
if (account == null) {
return null;
}
// Get the latest message
final Message message = Message.getLatestIncomingMessage(mContext, accountId);
if (message == null) {
return null; // no message found???
}
final String senderName = Address.toFriendly(Address.unpack(message.mFrom));
final String subject = message.mSubject;
final Bitmap senderPhoto = getSenderPhoto(message);
// Intent to open inbox
PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0,
Welcome.createOpenAccountInboxIntent(mContext, accountId),
PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.stat_notify_email_generic)
.setWhen(mClock.getTime())
.setLargeIcon(senderPhoto != null ? senderPhoto : mGenericSenderIcon)
.setContentTitle(getNotificationTitle(senderName, account.mDisplayName))
.setContentText(subject)
.setContentIntent(contentIntent);
if (unseenMessageCount > 1) {
builder.setNumber(unseenMessageCount);
}
Notification notification = builder.getNotification();
setupNotificationSoundAndVibrationFromAccount(notification, account);
return notification;
}
/**
* Creates the notification title.
*
* If only 1 account, just show the sender name.
* If 2+ accounts, make it "SENDER_NAME to RECEIVER_NAME", and gray out the "to RECEIVER_NAME"
* part.
*/
/* package */ SpannableString getNotificationTitle(String sender, String receiverDisplayName) {
final int numAccounts = EmailContent.count(mContext, Account.CONTENT_URI);
if (numAccounts == 1) {
return new SpannableString(sender);
} else {
// "to [account name]"
String toAcccount = mContext.getResources().getString(R.string.notification_to_account,
receiverDisplayName);
// "[Sender] to [account name]"
SpannableString senderToAccount = new SpannableString(sender + " " + toAcccount);
// "[Sender] to [account name]"
// ^^^^^^^^^^^^^^^^^ <- Make this part gray
TextAppearanceSpan secondarySpan = new TextAppearanceSpan(
mContext, R.style.notification_secondary_text);
senderToAccount.setSpan(secondarySpan, sender.length() + 1, senderToAccount.length(),
0);
return senderToAccount;
}
}
// Overridden for testing (AudioManager can't be mocked out.)
/* package */ int getRingerMode() {
return mAudioManager.getRingerMode();
}
/* package */ boolean isRingerModeSilent() {
return getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
}
/* package */ void setupNotificationSoundAndVibrationFromAccount(Notification notification,
Account account) {
final int flags = account.mFlags;
final String ringtoneUri = account.mRingtoneUri;
final boolean vibrate = (flags & Account.FLAGS_VIBRATE_ALWAYS) != 0;
final boolean vibrateWhenSilent = (flags & Account.FLAGS_VIBRATE_WHEN_SILENT) != 0;
notification.sound = (ringtoneUri == null) ? null : Uri.parse(ringtoneUri);
if (vibrate || (vibrateWhenSilent && isRingerModeSilent())) {
notification.defaults |= Notification.DEFAULT_VIBRATE;
}
// This code is identical to that used by Gmail and GTalk for notifications
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
notification.defaults |= Notification.DEFAULT_LIGHTS;
}
/**
* Generic warning notification
*/
public void showWarningNotification(int id, String tickerText, String notificationText,
Intent intent) {
PendingIntent pendingIntent = null;
if (intent != null) {
pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
Builder b = new Builder(mContext);
b.setSmallIcon(android.R.drawable.stat_notify_error)
.setTicker(tickerText)
.setWhen(mClock.getTime())
.setContentTitle(tickerText)
.setContentText(notificationText)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
Notification n = b.getNotification();
mNotificationManager.notify(id, n);
}
/**
* Alert the user that an attachment couldn't be forwarded. This is a very unusual case, and
* perhaps we shouldn't even send a notification. For now, it's helpful for debugging.
*/
public void showDownloadForwardFailedNotification(Attachment att) {
showWarningNotification(NOTIFICATION_ID_ATTACHMENT_WARNING,
mContext.getString(R.string.forward_download_failed_ticker),
mContext.getString(R.string.forward_download_failed_notification,
att.mFileName), null);
}
/**
* Alert the user that login failed for the specified account
*/
private int getLoginFailedNotificationId(long accountId) {
return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int)accountId;
}
// NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
public void showLoginFailedNotification(long accountId) {
final Account account = Account.restoreAccountWithId(mContext, accountId);
if (account == null) return;
showWarningNotification(getLoginFailedNotificationId(accountId),
mContext.getString(R.string.login_failed_ticker, account.mDisplayName),
mContext.getString(R.string.login_failed_notification),
AccountSettingsXL.createAccountSettingsIntent(mContext, accountId));
}
public void cancelLoginFailedNotification(long accountId) {
mNotificationManager.cancel(getLoginFailedNotificationId(accountId));
}
}