Switch to tablet style notification

- Now we show separate notification for each account
- New notification has sender photo, sender name, and subject
  of the latest email
- Added the NotificationController class, which is intended to manage
  all notifications besides "new message" eventually.

The framework doesn't seem to be 100% ready, and it's not clear how to
add the 3rd line in the expanded notification at this point.  Need to
revisit it later to verify UI details.

Change-Id: I40193ee372cb6b2b7245c1588890f238b2469699
This commit is contained in:
Makoto Onuki 2010-09-26 16:16:21 -07:00
parent cd2e98d000
commit 899c5b8661
13 changed files with 395 additions and 159 deletions

View File

@ -163,6 +163,41 @@
<item quantity="other">in <xliff:g id="number_accounts" example="10">%d</xliff:g> accounts</item>
</plurals>
<!-- "new message" notification message title.
"sender_name" will be replaced with the sender name of the latest email,
and "num_more_mails" will be replaced with the number of other messages that are received
together.
[CHAR LIMIT=none] -->
<plurals name="notification_sender_name_multi_messages">
<item quantity="one"><xliff:g id="sender_name">%1$s</xliff:g>
+ <xliff:g id="num_more_mails">%2$d</xliff:g> more</item>
<item quantity="other"><xliff:g id="sender_name">%1$s</xliff:g>
+ <xliff:g id="num_more_mails">%2$d</xliff:g> more</item>
</plurals>
<!-- "new message" notification body for the number of new messages,
used when only one account is set up.
"num_new_message" will be replaced with the number of new messages. [CHAR LIMIT=none] -->
<plurals name="notification_num_new_messages_single_account">
<item quantity="one"><xliff:g id="num_new_message">%1$d</xliff:g></item> new
<item quantity="other"><xliff:g id="num_new_message">%1$d</xliff:g></item> new
</plurals>
<!-- "new message" notification body for the number of new messages,
used when multiple accounts are set up.
"num_new_message" will be replaced with the number of new messages, and "account_name"
will be replaced with the receiving account name.
"separator" should be between these two elements.
[CHAR LIMIT=none] -->
<plurals name="notification_num_new_messages_multi_account">
<item quantity="one"><xliff:g id="num_new_message">%1$d</xliff:g
> new<xliff:g id="separator">&#8195;</xliff:g>
<xliff:g id="account_name">%2$s</xliff:g></item>
<item quantity="other"><xliff:g id="num_new_message">%1$d</xliff:g
> new<xliff:g id="separator">&#8195;</xliff:g>
<xliff:g id="account_name">%2$s</xliff:g></item>
</plurals>
<!-- The next set of strings are used server-side and must not be localized. -->
<!-- Do Not Translate. This is the name of the "inbox" folder, on the server. -->
<string name="mailbox_name_server_inbox" translatable="false">Inbox</string>

View File

@ -0,0 +1,227 @@
/*
* 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.mail.Address;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Message;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.net.Uri;
import android.text.TextUtils;
/**
* 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_WARNING = 3;
private static final int NOTIFICATION_ID_NEW_MESSAGES_BASE = 10;
private static NotificationController sInstance;
private final Context mContext;
private final NotificationManager mNotificationManager;
private final AudioManager mAudioManager;
/** Constructor */
private NotificationController(Context context) {
mContext = context.getApplicationContext();
mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
/** Singleton access */
public static synchronized NotificationController getInstance(Context context) {
if (sInstance == null) {
sInstance = new NotificationController(context);
}
return sInstance;
}
/**
* @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_NEW_MESSAGES_BASE + 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,
justFetchedCount);
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;
}
private Bitmap[] getNotificationBitmaps(Bitmap senderPhoto) {
// TODO Should we cache these objects? (bitmaps and arrays)
// They don't have to be on this process's memory once we post a notification request to
// the system, and decodeResource() seems to be reasonably fast. We don't want them to
// take up memory when not necessary.
Bitmap appIcon = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.icon);
if (senderPhoto == null) {
return new Bitmap[] {appIcon};
} else {
return new Bitmap[] {senderPhoto, appIcon};
}
}
/**
* Create a notification
*
* Don't call it on the UI thread.
*
* TODO Test it when the UI is settled.
*/
private Notification createNewMessageNotification(long accountId, int unseenMessageCount,
int justFetchedCount) {
final Account account = Account.restoreAccountWithId(mContext, accountId);
if (account == null) {
return null;
}
// Get the latest message
final Message message = Message.getLatestMessage(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);
final String notificationTitle;
if (justFetchedCount == 1) {
notificationTitle = senderName;
} else {
notificationTitle = mContext.getResources().getQuantityString(
R.plurals.notification_sender_name_multi_messages, justFetchedCount - 1,
senderName, justFetchedCount - 1);
}
final String content = subject;
final String numNewMessages;
final int numAccounts = EmailContent.count(mContext, Account.CONTENT_URI);
if (numAccounts == 1) {
numNewMessages = mContext.getResources().getQuantityString(
R.plurals.notification_num_new_messages_single_account, unseenMessageCount,
unseenMessageCount, account.mDisplayName);
} else {
numNewMessages = mContext.getResources().getQuantityString(
R.plurals.notification_num_new_messages_multi_account, unseenMessageCount,
unseenMessageCount, account.mDisplayName);
}
Notification notification = new Notification(R.drawable.stat_notify_email_generic,
mContext.getString(R.string.notification_new_title), System.currentTimeMillis());
notification.setLatestEventInfo(mContext, notificationTitle, subject, contentIntent);
notification.tickerTitle = notificationTitle;
// STOPSHIPO numNewMessages should be the 3rd line on expanded notification. But it's not
// clear how to do that yet.
// For now we just append it to subject.
notification.tickerSubtitle = subject + " " + numNewMessages;
notification.tickerIcons = getNotificationBitmaps(senderPhoto);
setupNotificationSoundAndVibrationFromAccount(notification, account);
return notification;
}
private boolean isRingerModeSilent() {
return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
}
private 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;
}
}

View File

@ -363,7 +363,8 @@ public class SecurityPolicy {
NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(MailService.NOTIFICATION_ID_SECURITY_NEEDED, notification);
notificationManager.notify(NotificationController.NOTIFICATION_ID_SECURITY_NEEDED,
notification);
}
/**
@ -373,7 +374,7 @@ public class SecurityPolicy {
public void clearNotification(long accountId) {
NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(MailService.NOTIFICATION_ID_SECURITY_NEEDED);
notificationManager.cancel(NotificationController.NOTIFICATION_ID_SECURITY_NEEDED);
}
/**

View File

@ -19,6 +19,7 @@ package com.android.email.activity;
import com.android.email.Controller;
import com.android.email.ControllerResultUiThreadWrapper;
import com.android.email.Email;
import com.android.email.NotificationController;
import com.android.email.R;
import com.android.email.activity.setup.AccountSettingsXL;
import com.android.email.activity.setup.AccountSetupBasics;
@ -26,7 +27,6 @@ import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.service.MailService;
import android.app.Activity;
import android.app.AlertDialog;
@ -37,7 +37,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@ -190,6 +189,7 @@ public class AccountFolderList extends Activity implements AccountFolderListFrag
}
private Dialog createRemoveAccountDialog() {
final Activity activity = this;
return new AlertDialog.Builder(this)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.account_delete_dlg_title)
@ -197,17 +197,18 @@ public class AccountFolderList extends Activity implements AccountFolderListFrag
mSelectedContextAccount.getDisplayName()))
.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
final long accountId = mSelectedContextAccount.mId;
dismissDialog(DIALOG_REMOVE_ACCOUNT);
// Clear notifications, which may become stale here
MailService.cancelNewMessageNotification(AccountFolderList.this);
int numAccounts = EmailContent.count(AccountFolderList.this,
// Dismiss new message notification.
NotificationController.getInstance(activity)
.cancelNewMessageNotification(accountId);
int numAccounts = EmailContent.count(activity,
Account.CONTENT_URI, null, null);
mListFragment.hideDeletingAccount(mSelectedContextAccount.mId);
Controller.getInstance(AccountFolderList.this).deleteAccount(
mSelectedContextAccount.mId);
Controller.getInstance(activity).deleteAccount(accountId);
if (numAccounts == 1) {
AccountSetupBasics.actionNewAccount(AccountFolderList.this);
AccountSetupBasics.actionNewAccount(activity);
finish();
}
}

View File

@ -19,6 +19,7 @@ package com.android.email.activity;
import com.android.email.Controller;
import com.android.email.ControllerResultUiThreadWrapper;
import com.android.email.Email;
import com.android.email.NotificationController;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.activity.setup.AccountSecurity;
@ -29,10 +30,8 @@ import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.AccountColumns;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.MailboxColumns;
import com.android.email.service.MailService;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@ -237,7 +236,8 @@ public class MessageList extends Activity implements OnClickListener,
mController.addResultCallback(mControllerCallback);
// clear notifications here
MailService.cancelNewMessageNotification(this);
NotificationController.getInstance(this).cancelNewMessageNotification(
mListFragment.getAccountId());
// Exit immediately if the accounts list has changed (e.g. externally deleted)
if (Email.getNotifyUiAccountsChanged()) {

View File

@ -18,6 +18,7 @@ package com.android.email.activity;
import com.android.email.Clock;
import com.android.email.Email;
import com.android.email.NotificationController;
import com.android.email.Preferences;
import com.android.email.R;
import com.android.email.RefreshManager;
@ -26,7 +27,6 @@ import com.android.email.activity.setup.AccountSecurity;
import com.android.email.activity.setup.AccountSettingsXL;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.service.MailService;
import android.app.ActionBar;
import android.app.Activity;
@ -174,7 +174,8 @@ public class MessageListXL extends Activity implements
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListXL onResume");
super.onResume();
MailService.cancelNewMessageNotification(this);
NotificationController.getInstance(this).cancelNewMessageNotification(
mFragmentManager.getAccountId());
// On MessageList.onResume, we go back to Welcome if an account has been added/removed.
// We don't need to do that here, because when the activity resumes, the account list loader

View File

@ -18,6 +18,7 @@ package com.android.email.activity.setup;
import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.NotificationController;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.mail.Sender;
@ -25,7 +26,6 @@ import com.android.email.mail.Store;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.AccountColumns;
import com.android.email.service.MailService;
import android.app.Activity;
import android.app.AlertDialog;
@ -558,8 +558,8 @@ public class AccountSettingsXL extends PreferenceActivity implements OnClickList
public void deleteAccount(Account account) {
// Kick off the work to actually delete the account
// Clear notifications, which may become stale here
MailService.cancelNewMessageNotification(this);
// Clear notifications for the account.
NotificationController.getInstance(this).cancelNewMessageNotification(account.mId);
// Delete the account (note, this is async. Would be nice to get a callback.
Controller.getInstance(this).deleteAccount(account.mId);

View File

@ -32,6 +32,7 @@ import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.text.TextUtils;
import java.io.File;
import java.net.URI;
@ -450,6 +451,7 @@ public abstract class EmailContent {
// To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
@SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
public static final Uri CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
public static final Uri SYNCED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
public static final Uri DELETED_CONTENT_URI =
@ -539,6 +541,9 @@ public abstract class EmailContent {
private static final String FAVORITE_COUNT_SELECTION =
MessageColumns.FLAG_FAVORITE + "= 1";
private static final String ACCOUNT_KEY_SELECTION =
MessageColumns.ACCOUNT_KEY + "=?";
// _id field is in AbstractContent
public String mDisplayName;
public long mTimeStamp;
@ -829,6 +834,26 @@ public abstract class EmailContent {
}
return -1;
}
/**
* @return the latest messages on an account.
*/
public static Message getLatestMessage(Context context, Long accountId) {
Cursor c = context.getContentResolver().query(Message.CONTENT_URI_LIMIT_1,
Message.CONTENT_PROJECTION,
ACCOUNT_KEY_SELECTION, new String[] {Long.toString(accountId)},
EmailContent.MessageColumns.TIMESTAMP + " DESC");
try {
if (c.moveToFirst()) {
Message m = new Message();
m.restore(c);
return m;
}
} finally {
c.close();
}
return null; // not found;
}
}
public interface AccountColumns {

View File

@ -16,11 +16,12 @@
package com.android.email.service;
import com.android.email.Controller.ControllerService;
import com.android.email.Email;
import com.android.email.ExchangeUtils.NullEmailService;
import com.android.email.NotificationController;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.Controller.ControllerService;
import com.android.email.ExchangeUtils.NullEmailService;
import com.android.email.activity.Welcome;
import com.android.email.provider.AttachmentProvider;
import com.android.email.provider.EmailContent;
@ -466,7 +467,7 @@ public class AttachmentDownloadService extends Service implements Runnable {
n.flags = Notification.FLAG_AUTO_CANCEL;
NotificationManager nm =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(MailService.NOTIFICATION_ID_WARNING, n);
nm.notify(NotificationController.NOTIFICATION_ID_WARNING, n);
}
/**

View File

@ -19,16 +19,20 @@ package com.android.email.service;
import com.android.email.AccountBackupRestore;
import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.NotificationController;
import com.android.email.R;
import com.android.email.SecurityPolicy;
import com.android.email.Utility;
import com.android.email.activity.ContactStatusLoader;
import com.android.email.activity.Welcome;
import com.android.email.mail.Address;
import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.AccountColumns;
import com.android.email.provider.EmailContent.HostAuth;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.Message;
import com.android.email.provider.EmailProvider;
import android.accounts.AccountManager;
@ -44,11 +48,12 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SyncStatusObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.Uri;
@ -56,6 +61,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
@ -73,11 +79,6 @@ public class MailService extends Service {
private static final String LOG_TAG = "Email-MailService";
private static final int NOTIFICATION_ID_NEW_MESSAGES = 1;
public static final int NOTIFICATION_ID_SECURITY_NEEDED = 2;
public static final int NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED = 3;
public static final int NOTIFICATION_ID_WARNING = 4;
private static final String ACTION_CHECK_MAIL =
"com.android.email.intent.action.MAIL_SERVICE_WAKEUP";
private static final String ACTION_RESCHEDULE =
@ -149,7 +150,7 @@ public class MailService extends Service {
synchronized (mSyncReports) {
for (AccountSyncReport report : mSyncReports.values()) {
if (accountId == -1 || accountId == report.accountId) {
report.numNewMessages = 0;
report.unseenMessageCount = 0;
}
}
}
@ -182,12 +183,6 @@ public class MailService extends Service {
context.startService(i);
}
public static void cancelNewMessageNotification(Context context) {
NotificationManager notificationManager = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(MailService.NOTIFICATION_ID_NEW_MESSAGES);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
@ -282,7 +277,7 @@ public class MailService extends Service {
// As a precaution, clear any outstanding Email notifications
// We could be smarter and only do this when the list of accounts changes,
// but that's an edge condition and this is much safer.
cancelNewMessageNotification(this);
NotificationController.getInstance(this).cancelNewMessageNotification(-1);
// When called externally, we refresh the sync reports table to pick up
// any changes in the account list or account settings
@ -496,22 +491,34 @@ public class MailService extends Service {
long accountId;
long prevSyncTime; // 0 == unknown
long nextSyncTime; // 0 == ASAP -1 == don't sync
int numNewMessages;
/** # of "unseen" messages to show in notification */
int unseenMessageCount;
/**
* # of unseen, the value shown on the last notification. Used to
* calculate "the number of messages that have just been fetched".
*
* TODO It's a sort of cheating. Should we use the "real" number? The only difference
* is the first notification after reboot / process restart.
*/
int lastUnseenMessageCount;
int syncInterval;
boolean notify;
boolean vibrate;
boolean vibrateWhenSilent;
Uri ringtoneUri;
String accountDisplayName;
boolean syncEnabled; // whether auto sync is enabled for this account
/** # of messages that have just been fetched */
int getJustFetchedMessageCount() {
return unseenMessageCount - lastUnseenMessageCount;
}
@Override
public String toString() {
return accountDisplayName + ": id=" + accountId + " prevSync=" + prevSyncTime
+ " nextSync=" + nextSyncTime + " numNew=" + numNewMessages;
return "id=" + accountId
+ " prevSync=" + prevSyncTime + " nextSync=" + nextSyncTime + " numUnseen="
+ unseenMessageCount;
}
}
@ -560,7 +567,6 @@ public class MailService extends Service {
int syncInterval = c.getInt(Account.CONTENT_SYNC_INTERVAL_COLUMN);
int flags = c.getInt(Account.CONTENT_FLAGS_COLUMN);
long id = c.getInt(Account.CONTENT_ID_COLUMN);
String ringtoneString = c.getString(Account.CONTENT_RINGTONE_URI_COLUMN);
// If we're not using MessagingController (EAS at this point), don't schedule syncs
if (!mController.isMessagingController(id)) {
@ -572,16 +578,10 @@ public class MailService extends Service {
report.accountId = c.getLong(Account.CONTENT_ID_COLUMN);
report.prevSyncTime = 0;
report.nextSyncTime = (syncInterval > 0) ? 0 : -1; // 0 == ASAP -1 == no sync
report.numNewMessages = 0;
report.unseenMessageCount = 0;
report.syncInterval = syncInterval;
report.notify = (flags & Account.FLAGS_NOTIFY_NEW_MAIL) != 0;
report.vibrate = (flags & Account.FLAGS_VIBRATE_ALWAYS) != 0;
report.vibrateWhenSilent = (flags & Account.FLAGS_VIBRATE_WHEN_SILENT) != 0;
report.ringtoneUri = (ringtoneString == null) ? null
: Uri.parse(ringtoneString);
report.accountDisplayName = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN);
// See if the account is enabled for sync in AccountManager
Account providerAccount = Account.restoreAccountWithId(this, id);
@ -623,7 +623,7 @@ public class MailService extends Service {
report.nextSyncTime = report.prevSyncTime + (report.syncInterval * 1000 * 60);
}
if (newCount != -1) {
report.numNewMessages = newCount;
report.unseenMessageCount = newCount;
}
if (Config.LOGD && Email.DEBUG) {
Log.d(LOG_TAG, "update account " + report.toString());
@ -709,114 +709,23 @@ public class MailService extends Service {
}
/**
* Prepare notifications for a given new account having received mail
* The notification is organized around the account that has the new mail (e.g. selecting
* the alert preferences) but the notification will include a summary if other
* accounts also have new mail.
* Show "new message" notification for an account. (Notification is shown per account.)
*/
private void notifyNewMessages(long accountId) {
boolean notify = false;
boolean vibrate = false;
boolean vibrateWhenSilent = false;
Uri ringtone = null;
int accountsWithNewMessages = 0;
int numNewMessages = 0;
String reportName = null;
private void notifyNewMessages(final long accountId) {
final int unseenMessageCount;
final int justFetchedCount;
synchronized (mSyncReports) {
for (AccountSyncReport report : mSyncReports.values()) {
if (report.numNewMessages == 0) {
continue;
}
numNewMessages += report.numNewMessages;
accountsWithNewMessages += 1;
if (report.accountId == accountId) {
notify = report.notify;
vibrate = report.vibrate;
vibrateWhenSilent = report.vibrateWhenSilent;
ringtone = report.ringtoneUri;
reportName = report.accountDisplayName;
}
AccountSyncReport report = mSyncReports.get(accountId);
if (report == null || report.unseenMessageCount == 0 || !report.notify) {
return;
}
}
if (!notify) {
return;
unseenMessageCount = report.unseenMessageCount;
justFetchedCount = report.getJustFetchedMessageCount();
report.lastUnseenMessageCount = report.unseenMessageCount;
}
showNewMessageNotification(this, accountId, vibrate, vibrateWhenSilent, ringtone,
accountsWithNewMessages, numNewMessages, reportName);
}
/** Simply runs {@link #showNewMessageNotificationInternal} on a worker thread. */
private static void showNewMessageNotification(final Context context,
final long accountId, final boolean vibrate, final boolean vibrateWhenSilent,
final Uri ringtone, final int accountsWithNewMessages, final int numNewMessages,
final String reportName) {
Utility.runAsync(new Runnable() {
@Override
public void run() {
showNewMessageNotificationInternal(context, accountId, vibrate, vibrateWhenSilent,
ringtone, accountsWithNewMessages, numNewMessages, reportName);
}
});
}
/**
* Show (or update) a notification.
*
* TODO Implement new style notification. (show sender photo, sender name, subject, ...)
*/
private static void showNewMessageNotificationInternal(Context context, long accountId,
boolean vibrate, boolean vibrateWhenSilent, Uri ringtone, int accountsWithNewMessages,
int numNewMessages, String reportName) {
// set up to post a notification
Intent intent;
String reportString;
if (accountsWithNewMessages == 1) {
// Prepare a report for a single account
// "12 unread (gmail)"
reportString = context.getResources().getQuantityString(
R.plurals.notification_new_one_account_fmt, numNewMessages,
numNewMessages, reportName);
intent = Welcome.createOpenAccountInboxIntent(context, accountId);
} else {
// Prepare a report for multiple accounts
// "4 accounts"
reportString = context.getResources().getQuantityString(
R.plurals.notification_new_multi_account_fmt, accountsWithNewMessages,
accountsWithNewMessages);
intent = Welcome.createOpenCombinedInboxIntent(context);
}
// prepare appropriate pending intent, set up notification, and send
PendingIntent pending =
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification(
R.drawable.stat_notify_email_generic,
context.getString(R.string.notification_new_title),
System.currentTimeMillis());
notification.setLatestEventInfo(context,
context.getString(R.string.notification_new_title),
reportString,
pending);
notification.sound = ringtone;
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
boolean nowSilent = audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
// Use same code here as in Gmail and GTalk for vibration
if (vibrate || (vibrateWhenSilent && nowSilent)) {
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;
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID_NEW_MESSAGES, notification);
NotificationController.getInstance(this).showNewMessageNotification(accountId,
unseenMessageCount, justFetchedCount);
}
/**

View File

@ -17,8 +17,8 @@
package com.android.exchange;
import com.android.email.Email;
import com.android.email.NotificationController;
import com.android.email.R;
import com.android.email.service.MailService;
import android.accounts.Account;
import android.accounts.AccountManager;
@ -30,7 +30,6 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Calendar;
import android.provider.ContactsContract;
import android.util.Log;
/**
@ -106,7 +105,7 @@ public class CalendarSyncEnabler {
NotificationManager nm =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(MailService.NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED, n);
nm.notify(NotificationController.NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED, n);
}
/** @return {@link Intent} to launch the Calendar app. */

View File

@ -2210,4 +2210,39 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
assertEquals(0, Account.restoreAccountWithId(c, a4.mId).mNewMessageCount);
assertEquals(0, Account.restoreAccountWithId(c, a5.mId).mNewMessageCount);
}
private static Message createMessageWithTimestamp(Context c, Mailbox b, long timestamp) {
Message m = ProviderTestUtils.setupMessage("1", b.mAccountKey, b.mId, true, false, c, false,
false);
m.mTimeStamp = timestamp;
m.save(c);
return m;
}
public void testMessageGetLatestMessage() {
final Context c = mMockContext;
// Create 2 accounts with a inbox.
Account a1 = ProviderTestUtils.setupAccount("a1", true, c);
Account a2 = ProviderTestUtils.setupAccount("a2", true, c);
Mailbox b1 = ProviderTestUtils.setupMailbox("box1", a1.mId, true, c, Mailbox.TYPE_INBOX);
Mailbox b2 = ProviderTestUtils.setupMailbox("box3", a2.mId, true, c, Mailbox.TYPE_INBOX);
// Create some messages
Message m11 = createMessageWithTimestamp(c, b1, 33);
Message m12 = createMessageWithTimestamp(c, b1, 10);
Message m13 = createMessageWithTimestamp(c, b1, 1000);
Message m21 = createMessageWithTimestamp(c, b2, 99);
Message m22 = createMessageWithTimestamp(c, b2, 1);
Message m23 = createMessageWithTimestamp(c, b2, 2);
// Check!
assertEquals(m13.mId, Message.getLatestMessage(c, a1.mId).mId);
assertEquals(m21.mId, Message.getLatestMessage(c, a2.mId).mId);
// No such account
assertEquals(null, Message.getLatestMessage(c, 9999999L));
}
}

View File

@ -18,7 +18,7 @@ package com.android.exchange;
import com.android.email.AccountTestCase;
import com.android.email.Email;
import com.android.email.service.MailService;
import com.android.email.NotificationController;
import android.accounts.Account;
import android.accounts.AccountManager;
@ -193,7 +193,9 @@ public class CalendarSyncEnablerTest extends AccountTestCase {
enabler.showNotification("a@b.com");
// Remove the notification. Comment it out when you want to know how it looks like.
// TODO If NotificationController supports this notification, we can just mock it out
// and remove this code.
((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE))
.cancel(MailService.NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED);
.cancel(NotificationController.NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED);
}
}