New style for the new message notification

Bug 3176956

Change-Id: Id7418fb8bb942230266815167f0a2e79062a9116
This commit is contained in:
Makoto Onuki 2010-12-03 14:44:47 -08:00
parent 69cd842c07
commit 74e094834c
5 changed files with 335 additions and 78 deletions

View File

@ -33,9 +33,15 @@
<!-- Do Not Translate. Unused string. -->
<string name="message_view_show_pictures_instructions" translatable="false"></string>
<!-- Do Not Translate. Unused string. -->
<string name="provider_note_yahoo"></string>
<string name="provider_note_yahoo" translatable="false"></string>
<!-- Do Not Translate. Unused string. -->
<string name="provider_note_yahoo_uk"></string>
<string name="provider_note_yahoo_uk" translatable="false"></string>
<!-- Do Not Translate. Unused string. -->
<plurals name="notification_sender_name_multi_messages" translatable="false" />
<!-- Do Not Translate. Unused string. -->
<plurals name="notification_num_new_messages_single_account" translatable="false" />
<!-- Do Not Translate. Unused string. -->
<plurals name="notification_num_new_messages_multi_account" translatable="false" />
<!-- Messages that are not currently used, but will probably reused in the near future -->
<!-- STOPSHIP Remove them if they're not used after all -->
@ -180,48 +186,15 @@
<!-- Case of plural number of accounts with unread messages. -->
<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> new</item>
<item quantity="other"><xliff:g id="num_new_message">%1$d</xliff:g> new</item>
</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>
<!-- String to indicate which account received a new message. [CHAR LIMIT=none] -->
<string name="notification_to_account">to <xliff:g id="receiver_name"
example="Main">%1$s</xliff:g></string>
<!-- The number of accounts configured. [CHAR LIMIT=16] -->
<plurals name="number_of_accounts">
<item quantity="one"><xliff:g id="num_accounts" example="1">%1$d</xliff:g> account</item>
<item quantity="other"><xliff:g id="num_accounts" example="2">%1$d</xliff:g> accounts</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

@ -72,4 +72,8 @@
<item name="android:layout_height">1dip</item>
<item name="android:background">@color/divider_color</item>
</style>
<style name="notification_secondary_text">
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
</resources>

View File

@ -31,9 +31,12 @@ 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.
@ -54,19 +57,23 @@ public class NotificationController {
private final Context mContext;
private final NotificationManager mNotificationManager;
private final AudioManager mAudioManager;
private final Bitmap mAppIcon;
private final Clock mClock;
/** Constructor */
private NotificationController(Context context) {
/* package */ NotificationController(Context context, Clock clock) {
mContext = context.getApplicationContext();
mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mAppIcon = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.icon);
mClock = clock;
}
/** Singleton access */
public static synchronized NotificationController getInstance(Context context) {
if (sInstance == null) {
sInstance = new NotificationController(context);
sInstance = new NotificationController(context, Clock.INSTANCE);
}
return sInstance;
}
@ -159,8 +166,7 @@ public class NotificationController {
Utility.runAsync(new Runnable() {
@Override
public void run() {
Notification n = createNewMessageNotification(accountId, unseenMessageCount,
justFetchedCount);
Notification n = createNewMessageNotification(accountId, unseenMessageCount);
if (n == null) {
return;
}
@ -190,11 +196,9 @@ public class NotificationController {
* 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) {
/* package */ Notification createNewMessageNotification(long accountId,
int unseenMessageCount) {
final Account account = Account.restoreAccountWithId(mContext, accountId);
if (account == null) {
return null;
@ -214,35 +218,16 @@ public class NotificationController {
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.Builder builder = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.stat_notify_email_generic)
.setWhen(System.currentTimeMillis())
.setTicker(mContext.getString(R.string.notification_new_title))
.setLargeIcon(senderPhoto)
.setContentTitle(notificationTitle)
.setContentText(subject + "\n" + numNewMessages)
.setWhen(mClock.getTime())
.setLargeIcon(senderPhoto != null ? senderPhoto : mAppIcon)
.setContentTitle(getNotificationTitle(senderName, account.mDisplayName))
.setContentText(subject)
.setContentIntent(contentIntent);
if (unseenMessageCount > 1) {
builder.setNumber(unseenMessageCount);
}
Notification notification = builder.getNotification();
@ -250,11 +235,44 @@ public class NotificationController {
return notification;
}
private boolean isRingerModeSilent() {
return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
/**
* 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;
}
}
private void setupNotificationSoundAndVibrationFromAccount(Notification notification,
// 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;
@ -283,7 +301,7 @@ public class NotificationController {
PendingIntent.FLAG_UPDATE_CURRENT);
}
Notification n = new Notification(android.R.drawable.stat_notify_error, tickerText,
System.currentTimeMillis());
mClock.getTime());
n.setLatestEventInfo(mContext, tickerText, notificationText, pendingIntent);
n.flags = Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify(id, n);

View File

@ -25,6 +25,7 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.database.Cursor;
import android.net.Uri;
import android.test.IsolatedContext;
@ -175,14 +176,30 @@ public final class DBTestHelper {
/** {@link IsolatedContext} + getApplicationContext() */
private static class MyIsolatedContext extends IsolatedContext {
public MyIsolatedContext(ContentResolver resolver, Context targetContext) {
private final Context mRealContext;
public MyIsolatedContext(ContentResolver resolver, Context targetContext,
Context realContext) {
super(resolver, targetContext);
mRealContext = realContext;
}
@Override
public Context getApplicationContext() {
return this;
}
// Following methods are not supported by the mock context.
// Redirect to the actual context.
@Override
public String getPackageName() {
return mRealContext.getPackageName();
}
@Override
public Theme getTheme() {
return mRealContext.getTheme();
}
}
// Based on ProviderTestCase2.setUp().
@ -193,7 +210,8 @@ public final class DBTestHelper {
new MockContext2(context), // The context that most methods are delegated to
context, // The context that file methods are delegated to
filenamePrefix);
final Context providerContext = new MyIsolatedContext(resolver, targetContextWrapper);
final Context providerContext = new MyIsolatedContext(resolver, targetContextWrapper,
context);
providerContext.getContentResolver();
// register EmailProvider and AttachmentProvider.

View File

@ -0,0 +1,244 @@
/*
* 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.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.Message;
import com.android.email.provider.ProviderTestUtils;
import android.app.Notification;
import android.content.Context;
import android.media.AudioManager;
import android.net.Uri;
import android.test.AndroidTestCase;
/**
* Test for {@link NotificationController}.
*
* TODO Add tests for all methods.
*/
public class NotificationControllerTest extends AndroidTestCase {
private Context mProviderContext;
private NotificationController mTarget;
private final MockClock mMockClock = new MockClock();
private int mRingerMode;
/**
* Subclass {@link NotificationController} to override un-mockable operations.
*/
private class NotificationControllerForTest extends NotificationController {
NotificationControllerForTest(Context context) {
super(context, mMockClock);
}
@Override
int getRingerMode() {
return mRingerMode;
}
}
@Override
protected void setUp() throws Exception {
super.setUp();
mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(mContext);
mTarget = new NotificationControllerForTest(mProviderContext);
}
public void testSetupNotificationSoundAndVibrationFromAccount() {
final Notification n = new Notification();
final Context c = mProviderContext;
final Account a1 = ProviderTestUtils.setupAccount("a1", true, c);
// === Ringer mode change ===
mRingerMode = AudioManager.RINGER_MODE_NORMAL;
// VIBRATE_ALWAYS, with a ringer tone
a1.mFlags = Account.FLAGS_VIBRATE_ALWAYS;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertEquals(Uri.parse(a1.mRingtoneUri), n.sound);
assertTrue((n.defaults & Notification.DEFAULT_VIBRATE) != 0);
assertTrue((n.flags & Notification.FLAG_SHOW_LIGHTS) != 0); // always set
assertTrue((n.defaults & Notification.DEFAULT_LIGHTS) != 0); // always set
// FLAGS_VIBRATE_WHEN_SILENT, with a ringer tone
a1.mFlags = Account.FLAGS_VIBRATE_WHEN_SILENT;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertEquals(Uri.parse(a1.mRingtoneUri), n.sound);
assertFalse((n.defaults & Notification.DEFAULT_VIBRATE) != 0); // no vibe
assertTrue((n.flags & Notification.FLAG_SHOW_LIGHTS) != 0); // always set
assertTrue((n.defaults & Notification.DEFAULT_LIGHTS) != 0); // always set
// No VIBRATE flags, with a ringer tone
a1.mFlags = 0;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertEquals(Uri.parse(a1.mRingtoneUri), n.sound);
assertFalse((n.defaults & Notification.DEFAULT_VIBRATE) != 0); // no vibe
assertTrue((n.flags & Notification.FLAG_SHOW_LIGHTS) != 0); // always set
assertTrue((n.defaults & Notification.DEFAULT_LIGHTS) != 0); // always set
// === Ringer mode change ===
mRingerMode = AudioManager.RINGER_MODE_VIBRATE;
// VIBRATE_ALWAYS, with a ringer tone
a1.mFlags = Account.FLAGS_VIBRATE_ALWAYS;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertEquals(Uri.parse(a1.mRingtoneUri), n.sound);
assertTrue((n.defaults & Notification.DEFAULT_VIBRATE) != 0);
assertTrue((n.flags & Notification.FLAG_SHOW_LIGHTS) != 0); // always set
assertTrue((n.defaults & Notification.DEFAULT_LIGHTS) != 0); // always set
// FLAGS_VIBRATE_WHEN_SILENT, with a ringer tone
a1.mFlags = Account.FLAGS_VIBRATE_WHEN_SILENT;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertEquals(Uri.parse(a1.mRingtoneUri), n.sound);
assertTrue((n.defaults & Notification.DEFAULT_VIBRATE) != 0);
assertTrue((n.flags & Notification.FLAG_SHOW_LIGHTS) != 0); // always set
assertTrue((n.defaults & Notification.DEFAULT_LIGHTS) != 0); // always set
// No VIBRATE flags, with a ringer tone
a1.mFlags = 0;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertEquals(Uri.parse(a1.mRingtoneUri), n.sound);
assertFalse((n.defaults & Notification.DEFAULT_VIBRATE) != 0); // no vibe
assertTrue((n.flags & Notification.FLAG_SHOW_LIGHTS) != 0); // always set
assertTrue((n.defaults & Notification.DEFAULT_LIGHTS) != 0); // always set
// === Ringer mode change ===
mRingerMode = AudioManager.RINGER_MODE_SILENT;
// VIBRATE_ALWAYS, with a ringer tone
a1.mFlags = Account.FLAGS_VIBRATE_ALWAYS;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertEquals(Uri.parse(a1.mRingtoneUri), n.sound);
assertTrue((n.defaults & Notification.DEFAULT_VIBRATE) != 0);
assertTrue((n.flags & Notification.FLAG_SHOW_LIGHTS) != 0); // always set
assertTrue((n.defaults & Notification.DEFAULT_LIGHTS) != 0); // always set
// FLAGS_VIBRATE_WHEN_SILENT, with a ringer tone
a1.mFlags = Account.FLAGS_VIBRATE_WHEN_SILENT;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertEquals(Uri.parse(a1.mRingtoneUri), n.sound);
assertTrue((n.defaults & Notification.DEFAULT_VIBRATE) != 0);
assertTrue((n.flags & Notification.FLAG_SHOW_LIGHTS) != 0); // always set
assertTrue((n.defaults & Notification.DEFAULT_LIGHTS) != 0); // always set
// No VIBRATE flags, with a ringer tone
a1.mFlags = 0;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertEquals(Uri.parse(a1.mRingtoneUri), n.sound);
assertFalse((n.defaults & Notification.DEFAULT_VIBRATE) != 0); // no vibe
assertTrue((n.flags & Notification.FLAG_SHOW_LIGHTS) != 0); // always set
assertTrue((n.defaults & Notification.DEFAULT_LIGHTS) != 0); // always set
// No ringer tone
a1.mRingtoneUri = null;
n.defaults = 0;
n.flags = 0;
mTarget.setupNotificationSoundAndVibrationFromAccount(n, a1);
assertNull(n.sound);
}
public void testCreateNewMessageNotification() {
final Context c = mProviderContext;
Notification n;
// Case 1: 1 account, 1 unseen message
Account a1 = ProviderTestUtils.setupAccount("a1", true, c);
Mailbox b1 = ProviderTestUtils.setupMailbox("inbox", a1.mId, true, c, Mailbox.TYPE_INBOX);
Message m1 = ProviderTestUtils.setupMessage("message", a1.mId, b1.mId, true, true, c);
n = mTarget.createNewMessageNotification(a1.mId, 1);
assertEquals(R.drawable.stat_notify_email_generic, n.icon);
assertEquals(mMockClock.mTime, n.when);
assertNotNull(n.largeIcon);
assertEquals(0, n.number);
// TODO Check content -- how?
// Case 2: 1 account, 2 unseen message
n = mTarget.createNewMessageNotification(a1.mId, 2);
assertEquals(R.drawable.stat_notify_email_generic, n.icon);
assertEquals(mMockClock.mTime, n.when);
assertNotNull(n.largeIcon);
assertEquals(2, n.number);
// TODO Check content -- how?
// TODO Add 2 account test, if we find a way to check content
}
public void testGetNotificationTitle() {
final Context c = mProviderContext;
// Case 1: 1 account
Account a1 = ProviderTestUtils.setupAccount("a1", true, c);
// Just check the content. Ignore the spans.
String title = mTarget.getNotificationTitle("*sender*", "*receiver*").toString();
assertEquals("*sender*", title);
// Case 1: 2 account
Account a2 = ProviderTestUtils.setupAccount("a1", true, c);
// Just check the content. Ignore the spans.
title = mTarget.getNotificationTitle("*sender*", "*receiver*").toString();
assertEquals("*sender* to *receiver*", title);
}
}