2010-09-26 23:16:21 +00:00
|
|
|
/*
|
|
|
|
* 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 android.app.Notification;
|
2011-05-11 20:40:52 +00:00
|
|
|
import android.app.Notification.Builder;
|
2010-09-26 23:16:21 +00:00
|
|
|
import android.app.NotificationManager;
|
|
|
|
import android.app.PendingIntent;
|
2011-05-03 21:42:26 +00:00
|
|
|
import android.content.ContentResolver;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import android.content.ContentUris;
|
2011-07-20 01:19:59 +00:00
|
|
|
import android.content.ContentValues;
|
2010-09-26 23:16:21 +00:00
|
|
|
import android.content.Context;
|
2010-10-18 20:14:20 +00:00
|
|
|
import android.content.Intent;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.content.res.Resources;
|
2011-05-03 21:42:26 +00:00
|
|
|
import android.database.ContentObserver;
|
2011-05-10 18:36:57 +00:00
|
|
|
import android.database.Cursor;
|
2010-09-26 23:16:21 +00:00
|
|
|
import android.graphics.Bitmap;
|
2010-12-03 22:44:47 +00:00
|
|
|
import android.graphics.BitmapFactory;
|
2010-09-26 23:16:21 +00:00
|
|
|
import android.media.AudioManager;
|
|
|
|
import android.net.Uri;
|
2011-05-03 21:42:26 +00:00
|
|
|
import android.os.Handler;
|
2011-05-10 18:36:57 +00:00
|
|
|
import android.os.Looper;
|
|
|
|
import android.os.Process;
|
2010-12-03 22:44:47 +00:00
|
|
|
import android.text.SpannableString;
|
2010-09-26 23:16:21 +00:00
|
|
|
import android.text.TextUtils;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.text.style.TextAppearanceSpan;
|
2011-05-10 18:36:57 +00:00
|
|
|
import android.util.Log;
|
2010-09-26 23:16:21 +00:00
|
|
|
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import com.android.email.activity.ContactStatusLoader;
|
|
|
|
import com.android.email.activity.setup.AccountSecurity;
|
|
|
|
import com.android.email.activity.setup.AccountSettings;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.email.provider.EmailProvider;
|
|
|
|
import com.android.email.service.EmailBroadcastProcessorService;
|
|
|
|
import com.android.email2.ui.MailActivityEmail;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import com.android.emailcommon.Logging;
|
|
|
|
import com.android.emailcommon.mail.Address;
|
|
|
|
import com.android.emailcommon.provider.Account;
|
|
|
|
import com.android.emailcommon.provider.EmailContent;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.Attachment;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.Message;
|
|
|
|
import com.android.emailcommon.provider.Mailbox;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.emailcommon.utility.EmailAsyncTask;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import com.android.emailcommon.utility.Utility;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.mail.providers.Conversation;
|
|
|
|
import com.android.mail.providers.Folder;
|
|
|
|
import com.android.mail.providers.UIProvider;
|
|
|
|
import com.android.mail.utils.Utils;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
|
|
|
2011-05-03 21:42:26 +00:00
|
|
|
import java.util.HashMap;
|
2011-05-11 22:29:24 +00:00
|
|
|
import java.util.HashSet;
|
2011-05-03 21:42:26 +00:00
|
|
|
|
2010-09-26 23:16:21 +00:00
|
|
|
/**
|
|
|
|
* Class that manages notifications.
|
|
|
|
*/
|
|
|
|
public class NotificationController {
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final String TAG = "NotificationController";
|
|
|
|
|
2011-04-28 18:09:41 +00:00
|
|
|
/** Reserved for {@link com.android.exchange.CalendarSyncEnabler} */
|
|
|
|
@SuppressWarnings("unused")
|
|
|
|
private static final int NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED = 2;
|
2011-03-22 00:08:16 +00:00
|
|
|
private static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3;
|
|
|
|
private static final int NOTIFICATION_ID_PASSWORD_EXPIRING = 4;
|
|
|
|
private static final int NOTIFICATION_ID_PASSWORD_EXPIRED = 5;
|
2010-10-18 20:14:20 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final int NOTIFICATION_ID_BASE_MASK = 0xF0000000;
|
2010-10-18 20:14:20 +00:00
|
|
|
private static final int NOTIFICATION_ID_BASE_NEW_MESSAGES = 0x10000000;
|
|
|
|
private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000;
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final int NOTIFICATION_ID_BASE_SECURITY_NEEDED = 0x30000000;
|
|
|
|
private static final int NOTIFICATION_ID_BASE_SECURITY_CHANGED = 0x40000000;
|
2010-09-26 23:16:21 +00:00
|
|
|
|
2011-05-10 18:36:57 +00:00
|
|
|
/** Selection to retrieve accounts that should we notify user for changes */
|
|
|
|
private final static String NOTIFIED_ACCOUNT_SELECTION =
|
|
|
|
Account.FLAGS + "&" + Account.FLAGS_NOTIFY_NEW_MAIL + " != 0";
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final String NEW_MAIL_MAILBOX_ID = "com.android.email.new_mail.mailboxId";
|
|
|
|
private static final String NEW_MAIL_MESSAGE_ID = "com.android.email.new_mail.messageId";
|
|
|
|
private static final String NEW_MAIL_MESSAGE_COUNT = "com.android.email.new_mail.messageCount";
|
|
|
|
private static final String NEW_MAIL_UNREAD_COUNT = "com.android.email.new_mail.unreadCount";
|
|
|
|
|
2011-05-11 22:29:24 +00:00
|
|
|
private static NotificationThread sNotificationThread;
|
|
|
|
private static Handler sNotificationHandler;
|
2010-09-26 23:16:21 +00:00
|
|
|
private static NotificationController sInstance;
|
|
|
|
private final Context mContext;
|
|
|
|
private final NotificationManager mNotificationManager;
|
|
|
|
private final AudioManager mAudioManager;
|
2010-12-15 23:26:30 +00:00
|
|
|
private final Bitmap mGenericSenderIcon;
|
2011-08-18 00:10:06 +00:00
|
|
|
private final Bitmap mGenericMultipleSenderIcon;
|
2010-12-03 22:44:47 +00:00
|
|
|
private final Clock mClock;
|
2012-06-28 17:40:46 +00:00
|
|
|
/** Maps account id to its observer */
|
2011-07-20 01:19:59 +00:00
|
|
|
private final HashMap<Long, ContentObserver> mNotificationMap;
|
2011-05-11 22:29:24 +00:00
|
|
|
private ContentObserver mAccountObserver;
|
2010-09-26 23:16:21 +00:00
|
|
|
|
2011-07-22 18:41:51 +00:00
|
|
|
/**
|
|
|
|
* Timestamp indicating when the last message notification sound was played.
|
|
|
|
* Used for throttling.
|
|
|
|
*/
|
|
|
|
private long mLastMessageNotifyTime;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Minimum interval between notification sounds.
|
|
|
|
* Since a long sync (either on account setup or after a long period of being offline) can cause
|
|
|
|
* several notifications consecutively, it can be pretty overwhelming to get a barrage of
|
|
|
|
* notification sounds. Throttle them using this value.
|
|
|
|
*/
|
2011-07-22 20:29:48 +00:00
|
|
|
private static final long MIN_SOUND_INTERVAL_MS = 15 * 1000; // 15 seconds
|
2011-07-22 18:41:51 +00:00
|
|
|
|
2010-09-26 23:16:21 +00:00
|
|
|
/** Constructor */
|
2011-04-28 18:09:41 +00:00
|
|
|
@VisibleForTesting
|
|
|
|
NotificationController(Context context, Clock clock) {
|
2010-09-26 23:16:21 +00:00
|
|
|
mContext = context.getApplicationContext();
|
2012-08-23 05:25:42 +00:00
|
|
|
EmailContent.init(context);
|
2010-09-26 23:16:21 +00:00
|
|
|
mNotificationManager = (NotificationManager) context.getSystemService(
|
|
|
|
Context.NOTIFICATION_SERVICE);
|
|
|
|
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
2010-12-15 23:26:30 +00:00
|
|
|
mGenericSenderIcon = BitmapFactory.decodeResource(mContext.getResources(),
|
|
|
|
R.drawable.ic_contact_picture);
|
2011-08-18 00:10:06 +00:00
|
|
|
mGenericMultipleSenderIcon = BitmapFactory.decodeResource(mContext.getResources(),
|
|
|
|
R.drawable.ic_notification_multiple_mail_holo_dark);
|
2010-12-03 22:44:47 +00:00
|
|
|
mClock = clock;
|
2011-07-20 01:19:59 +00:00
|
|
|
mNotificationMap = new HashMap<Long, ContentObserver>();
|
2010-09-26 23:16:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Singleton access */
|
|
|
|
public static synchronized NotificationController getInstance(Context context) {
|
|
|
|
if (sInstance == null) {
|
2010-12-03 22:44:47 +00:00
|
|
|
sInstance = new NotificationController(context, Clock.INSTANCE);
|
2010-09-26 23:16:21 +00:00
|
|
|
}
|
|
|
|
return sInstance;
|
|
|
|
}
|
|
|
|
|
2011-07-28 20:55:10 +00:00
|
|
|
/**
|
|
|
|
* Return whether or not a notification, based on the passed-in id, needs to be "ongoing"
|
|
|
|
* @param notificationId the notification id to check
|
|
|
|
* @return whether or not the notification must be "ongoing"
|
|
|
|
*/
|
|
|
|
private boolean needsOngoingNotification(int notificationId) {
|
|
|
|
// "Security needed" must be ongoing so that the user doesn't close it; otherwise, sync will
|
|
|
|
// be prevented until a reboot. Consider also doing this for password expired.
|
2012-06-28 17:40:46 +00:00
|
|
|
return (notificationId & NOTIFICATION_ID_BASE_MASK) == NOTIFICATION_ID_BASE_SECURITY_NEEDED;
|
2011-07-28 20:55:10 +00:00
|
|
|
}
|
|
|
|
|
2010-12-01 20:58:36 +00:00
|
|
|
/**
|
2012-06-28 17:40:46 +00:00
|
|
|
* Returns a {@link Notification.Builder}} for an event with the given account. The account
|
|
|
|
* contains specific rules on ring tone usage and these will be used to modify the notification
|
2011-04-28 18:09:41 +00:00
|
|
|
* behaviour.
|
2010-12-01 20:58:36 +00:00
|
|
|
*
|
2012-06-28 17:40:46 +00:00
|
|
|
* @param accountId The id of the account this notification is being built for.
|
2011-04-28 18:09:41 +00:00
|
|
|
* @param ticker Text displayed when the notification is first shown. May be {@code null}.
|
|
|
|
* @param title The first line of text. May NOT be {@code null}.
|
|
|
|
* @param contentText The second line of text. May NOT be {@code null}.
|
|
|
|
* @param intent The intent to start if the user clicks on the notification.
|
|
|
|
* @param largeIcon A large icon. May be {@code null}
|
2011-05-11 20:40:52 +00:00
|
|
|
* @param number A number to display using {@link Builder#setNumber(int)}. May
|
2011-04-28 18:09:41 +00:00
|
|
|
* be {@code null}.
|
2011-05-03 21:42:26 +00:00
|
|
|
* @param enableAudio If {@code false}, do not play any sound. Otherwise, play sound according
|
|
|
|
* to the settings for the given account.
|
2011-04-28 18:09:41 +00:00
|
|
|
* @return A {@link Notification} that can be sent to the notification service.
|
2010-12-01 20:58:36 +00:00
|
|
|
*/
|
2012-06-28 17:40:46 +00:00
|
|
|
private Notification.Builder createBaseAccountNotificationBuilder(long accountId, String ticker,
|
2011-04-28 18:09:41 +00:00
|
|
|
CharSequence title, String contentText, Intent intent, Bitmap largeIcon,
|
2011-07-28 20:55:10 +00:00
|
|
|
Integer number, boolean enableAudio, boolean ongoing) {
|
2010-12-01 20:58:36 +00:00
|
|
|
// Pending Intent
|
2011-02-17 00:38:18 +00:00
|
|
|
PendingIntent pending = null;
|
|
|
|
if (intent != null) {
|
2011-04-28 18:09:41 +00:00
|
|
|
pending = PendingIntent.getActivity(
|
|
|
|
mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
2011-02-17 00:38:18 +00:00
|
|
|
}
|
2010-12-01 20:58:36 +00:00
|
|
|
|
2011-04-28 18:09:41 +00:00
|
|
|
// NOTE: the ticker is not shown for notifications in the Holo UX
|
2012-06-28 17:40:46 +00:00
|
|
|
final Notification.Builder builder = new Notification.Builder(mContext)
|
2011-04-28 18:09:41 +00:00
|
|
|
.setContentTitle(title)
|
|
|
|
.setContentText(contentText)
|
|
|
|
.setContentIntent(pending)
|
|
|
|
.setLargeIcon(largeIcon)
|
|
|
|
.setNumber(number == null ? 0 : number)
|
|
|
|
.setSmallIcon(R.drawable.stat_notify_email_generic)
|
|
|
|
.setWhen(mClock.getTime())
|
2011-07-28 20:55:10 +00:00
|
|
|
.setTicker(ticker)
|
|
|
|
.setOngoing(ongoing);
|
2011-05-03 21:42:26 +00:00
|
|
|
|
|
|
|
if (enableAudio) {
|
2012-06-28 17:40:46 +00:00
|
|
|
Account account = Account.restoreAccountWithId(mContext, accountId);
|
2011-05-03 21:42:26 +00:00
|
|
|
setupSoundAndVibration(builder, account);
|
|
|
|
}
|
2010-12-01 20:58:36 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
return builder;
|
2011-04-28 18:09:41 +00:00
|
|
|
}
|
2010-12-01 20:58:36 +00:00
|
|
|
|
2011-04-28 18:09:41 +00:00
|
|
|
/**
|
|
|
|
* Generic notifier for any account. Uses notification rules from account.
|
|
|
|
*
|
2012-06-28 17:40:46 +00:00
|
|
|
* @param accountId The account id this notification is being built for.
|
2011-04-28 18:09:41 +00:00
|
|
|
* @param ticker Text displayed when the notification is first shown. May be {@code null}.
|
|
|
|
* @param title The first line of text. May NOT be {@code null}.
|
|
|
|
* @param contentText The second line of text. May NOT be {@code null}.
|
|
|
|
* @param intent The intent to start if the user clicks on the notification.
|
|
|
|
* @param notificationId The ID of the notification to register with the service.
|
|
|
|
*/
|
2012-06-28 17:40:46 +00:00
|
|
|
private void showNotification(long accountId, String ticker, String title,
|
2011-04-28 18:09:41 +00:00
|
|
|
String contentText, Intent intent, int notificationId) {
|
2012-06-28 17:40:46 +00:00
|
|
|
final Notification.Builder builder = createBaseAccountNotificationBuilder(accountId, ticker,
|
|
|
|
title, contentText, intent, null, null, true,
|
|
|
|
needsOngoingNotification(notificationId));
|
|
|
|
mNotificationManager.notify(notificationId, builder.getNotification());
|
2010-12-01 20:58:36 +00:00
|
|
|
}
|
|
|
|
|
2010-09-26 23:16:21 +00:00
|
|
|
/**
|
2011-04-28 18:09:41 +00:00
|
|
|
* Returns a notification ID for new message notifications for the given account.
|
2010-09-26 23:16:21 +00:00
|
|
|
*/
|
2012-06-28 17:40:46 +00:00
|
|
|
private int getNewMessageNotificationId(long mailboxId) {
|
2011-04-28 18:09:41 +00:00
|
|
|
// We assume accountId will always be less than 0x0FFFFFFF; is there a better way?
|
2012-06-28 17:40:46 +00:00
|
|
|
return (int) (NOTIFICATION_ID_BASE_NEW_MESSAGES + mailboxId);
|
2010-09-26 23:16:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-05-10 18:36:57 +00:00
|
|
|
* Tells the notification controller if it should be watching for changes to the message table.
|
|
|
|
* This is the main life cycle method for message notifications. When we stop observing
|
|
|
|
* database changes, we save the state [e.g. message ID and count] of the most recent
|
|
|
|
* notification shown to the user. And, when we start observing database changes, we restore
|
|
|
|
* the saved state.
|
|
|
|
* @param watch If {@code true}, we register observers for all accounts whose settings have
|
2011-05-12 17:13:45 +00:00
|
|
|
* notifications enabled. Otherwise, all observers are unregistered.
|
2010-09-26 23:16:21 +00:00
|
|
|
*/
|
2011-05-10 18:36:57 +00:00
|
|
|
public void watchForMessages(final boolean watch) {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
|
|
|
Log.d(Logging.LOG_TAG, "Notifications being toggled: " + watch);
|
2011-07-21 18:02:51 +00:00
|
|
|
}
|
2011-05-10 18:36:57 +00:00
|
|
|
// Don't create the thread if we're only going to stop watching
|
2011-05-12 17:13:45 +00:00
|
|
|
if (!watch && sNotificationThread == null) return;
|
2011-05-10 18:36:57 +00:00
|
|
|
|
2011-05-12 17:13:45 +00:00
|
|
|
ensureHandlerExists();
|
2011-05-10 18:36:57 +00:00
|
|
|
// Run this on the message notification handler
|
2011-05-11 22:29:24 +00:00
|
|
|
sNotificationHandler.post(new Runnable() {
|
2011-05-10 18:36:57 +00:00
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
|
|
|
if (!watch) {
|
2011-05-12 17:13:45 +00:00
|
|
|
unregisterMessageNotification(Account.ACCOUNT_ID_COMBINED_VIEW);
|
2011-05-11 22:29:24 +00:00
|
|
|
if (mAccountObserver != null) {
|
|
|
|
resolver.unregisterContentObserver(mAccountObserver);
|
|
|
|
mAccountObserver = null;
|
|
|
|
}
|
2011-05-10 18:36:57 +00:00
|
|
|
|
|
|
|
// tear down the event loop
|
2011-05-11 22:29:24 +00:00
|
|
|
sNotificationThread.quit();
|
|
|
|
sNotificationThread = null;
|
2011-05-10 18:36:57 +00:00
|
|
|
return;
|
2011-05-03 21:42:26 +00:00
|
|
|
}
|
2011-05-10 18:36:57 +00:00
|
|
|
|
|
|
|
// otherwise, start new observers for all notified accounts
|
2011-05-12 17:13:45 +00:00
|
|
|
registerMessageNotification(Account.ACCOUNT_ID_COMBINED_VIEW);
|
2011-05-11 22:29:24 +00:00
|
|
|
// If we're already observing account changes, don't do anything else
|
|
|
|
if (mAccountObserver == null) {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
2011-07-21 18:02:51 +00:00
|
|
|
Log.i(Logging.LOG_TAG, "Observing account changes for notifications");
|
|
|
|
}
|
2011-05-11 22:29:24 +00:00
|
|
|
mAccountObserver = new AccountContentObserver(sNotificationHandler, mContext);
|
|
|
|
resolver.registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver);
|
2011-05-10 18:36:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2010-09-26 23:16:21 +00:00
|
|
|
}
|
|
|
|
|
2011-05-12 17:13:45 +00:00
|
|
|
/**
|
|
|
|
* Ensures the notification handler exists and is ready to handle requests.
|
|
|
|
*/
|
|
|
|
private static synchronized void ensureHandlerExists() {
|
|
|
|
if (sNotificationThread == null) {
|
|
|
|
sNotificationThread = new NotificationThread();
|
|
|
|
sNotificationHandler = new Handler(sNotificationThread.getLooper());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-26 23:16:21 +00:00
|
|
|
/**
|
2012-06-28 17:40:46 +00:00
|
|
|
* Registers an observer for changes to mailboxes in the given account.
|
2011-05-10 18:36:57 +00:00
|
|
|
* NOTE: This must be called on the notification handler thread.
|
|
|
|
* @param accountId The ID of the account to register the observer for. May be
|
2011-05-12 17:13:45 +00:00
|
|
|
* {@link Account#ACCOUNT_ID_COMBINED_VIEW} to register observers for all
|
|
|
|
* accounts that allow for user notification.
|
2010-09-26 23:16:21 +00:00
|
|
|
*/
|
2011-05-10 18:36:57 +00:00
|
|
|
private void registerMessageNotification(long accountId) {
|
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
2011-05-12 17:13:45 +00:00
|
|
|
if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
|
2011-05-10 18:36:57 +00:00
|
|
|
Cursor c = resolver.query(
|
|
|
|
Account.CONTENT_URI, EmailContent.ID_PROJECTION,
|
|
|
|
NOTIFIED_ACCOUNT_SELECTION, null, null);
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
|
|
|
|
registerMessageNotification(id);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
} else {
|
2011-07-20 01:19:59 +00:00
|
|
|
ContentObserver obs = mNotificationMap.get(accountId);
|
|
|
|
if (obs != null) return; // we're already observing; nothing to do
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
2011-07-21 18:02:51 +00:00
|
|
|
Log.i(Logging.LOG_TAG, "Registering for notifications for account " + accountId);
|
|
|
|
}
|
2011-05-10 18:36:57 +00:00
|
|
|
ContentObserver observer = new MessageContentObserver(
|
2012-06-28 17:40:46 +00:00
|
|
|
sNotificationHandler, mContext, accountId);
|
2011-05-10 18:36:57 +00:00
|
|
|
resolver.registerContentObserver(Message.NOTIFIER_URI, true, observer);
|
2011-07-20 01:19:59 +00:00
|
|
|
mNotificationMap.put(accountId, observer);
|
2011-05-11 22:29:24 +00:00
|
|
|
// Now, ping the observer for any initial notifications
|
2011-07-20 01:19:59 +00:00
|
|
|
observer.onChange(true);
|
2011-05-03 21:42:26 +00:00
|
|
|
}
|
2011-05-10 18:36:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregisters the observer for the given account. If the specified account does not have
|
2011-05-11 22:29:24 +00:00
|
|
|
* a registered observer, no action is performed. This will not clear any existing notification
|
|
|
|
* for the specified account. Use {@link NotificationManager#cancel(int)}.
|
2011-05-10 18:36:57 +00:00
|
|
|
* NOTE: This must be called on the notification handler thread.
|
2011-05-12 17:13:45 +00:00
|
|
|
* @param accountId The ID of the account to unregister from. To unregister all accounts that
|
|
|
|
* have observers, specify an ID of {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
|
2011-05-10 18:36:57 +00:00
|
|
|
*/
|
|
|
|
private void unregisterMessageNotification(long accountId) {
|
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
2011-05-12 17:13:45 +00:00
|
|
|
if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
2011-07-21 18:02:51 +00:00
|
|
|
Log.i(Logging.LOG_TAG, "Unregistering notifications for all accounts");
|
|
|
|
}
|
2011-05-10 18:36:57 +00:00
|
|
|
// cancel all existing message observers
|
2011-07-20 01:19:59 +00:00
|
|
|
for (ContentObserver observer : mNotificationMap.values()) {
|
2011-05-10 18:36:57 +00:00
|
|
|
resolver.unregisterContentObserver(observer);
|
|
|
|
}
|
|
|
|
mNotificationMap.clear();
|
2011-05-03 21:42:26 +00:00
|
|
|
} else {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
2011-07-21 18:02:51 +00:00
|
|
|
Log.i(Logging.LOG_TAG, "Unregistering notifications for account " + accountId);
|
|
|
|
}
|
2011-07-20 01:19:59 +00:00
|
|
|
ContentObserver observer = mNotificationMap.remove(accountId);
|
|
|
|
if (observer != null) {
|
2011-05-10 18:36:57 +00:00
|
|
|
resolver.unregisterContentObserver(observer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-26 23:16:21 +00:00
|
|
|
/**
|
2011-04-28 18:09:41 +00:00
|
|
|
* Returns a picture of the sender of the given message. If no picture is available, returns
|
|
|
|
* {@code null}.
|
2010-09-26 23:16:21 +00:00
|
|
|
*
|
2011-04-28 18:09:41 +00:00
|
|
|
* NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
|
2010-09-26 23:16:21 +00:00
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
Bitmap photo = ContactStatusLoader.getContactInfo(mContext, email).mPhoto;
|
|
|
|
|
|
|
|
if (photo != null) {
|
|
|
|
final Resources res = mContext.getResources();
|
|
|
|
final int idealIconHeight =
|
|
|
|
res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
|
|
|
|
final int idealIconWidth =
|
|
|
|
res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
|
|
|
|
|
|
|
|
if (photo.getHeight() < idealIconHeight) {
|
|
|
|
// We should scale this image to fit the intended size
|
|
|
|
photo = Bitmap.createScaledBitmap(
|
|
|
|
photo, idealIconWidth, idealIconHeight, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return photo;
|
2010-09-26 23:16:21 +00:00
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
public static final String EXTRA_ACCOUNT = "account";
|
|
|
|
public static final String EXTRA_CONVERSATION = "conversationUri";
|
|
|
|
public static final String EXTRA_FOLDER = "folder";
|
|
|
|
|
|
|
|
private Intent createViewConversationIntent(Conversation conversation, Folder folder,
|
|
|
|
com.android.mail.providers.Account account) {
|
|
|
|
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
2012-07-16 21:35:44 +00:00
|
|
|
intent.putExtra(EXTRA_ACCOUNT, account.serialize());
|
|
|
|
if (folder != null) {
|
|
|
|
intent.setDataAndType(folder.uri, account.mimeType);
|
2012-07-30 20:12:07 +00:00
|
|
|
intent.putExtra(EXTRA_FOLDER, Folder.toString(folder));
|
2012-07-16 21:35:44 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
intent.putExtra(EXTRA_CONVERSATION, conversation);
|
|
|
|
return intent;
|
|
|
|
}
|
2012-07-30 20:12:07 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private Cursor getUiCursor(Uri uri, String[] projection) {
|
|
|
|
Cursor c = mContext.getContentResolver().query(uri, projection, null, null, null);
|
|
|
|
if (c == null) return null;
|
|
|
|
if (c.moveToFirst()) {
|
|
|
|
return c;
|
|
|
|
} else {
|
|
|
|
c.close();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2012-07-30 20:12:07 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private Intent createViewConversationIntent(Message message) {
|
|
|
|
Cursor c = getUiCursor(EmailProvider.uiUri("uiaccount", message.mAccountKey),
|
|
|
|
UIProvider.ACCOUNTS_PROJECTION);
|
|
|
|
if (c == null) {
|
|
|
|
Log.w(TAG, "Can't find account for message " + message.mId);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
com.android.mail.providers.Account acct = new com.android.mail.providers.Account(c);
|
|
|
|
c.close();
|
|
|
|
c = getUiCursor(EmailProvider.uiUri("uifolder", message.mMailboxKey),
|
|
|
|
UIProvider.FOLDERS_PROJECTION);
|
|
|
|
if (c == null) {
|
|
|
|
Log.w(TAG, "Can't find folder for message " + message.mId + ", folder " +
|
|
|
|
message.mMailboxKey);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
Folder folder = new Folder(c);
|
|
|
|
c.close();
|
|
|
|
c = getUiCursor(EmailProvider.uiUri("uiconversation", message.mId),
|
|
|
|
UIProvider.CONVERSATION_PROJECTION);
|
|
|
|
if (c == null) {
|
|
|
|
Log.w(TAG, "Can't find conversation for message " + message.mId);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
Conversation conv = new Conversation(c);
|
|
|
|
c.close();
|
|
|
|
return createViewConversationIntent(conv, folder, acct);
|
|
|
|
}
|
|
|
|
|
2010-09-26 23:16:21 +00:00
|
|
|
/**
|
2011-04-28 18:09:41 +00:00
|
|
|
* Returns a "new message" notification for the given account.
|
2010-09-26 23:16:21 +00:00
|
|
|
*
|
2011-04-28 18:09:41 +00:00
|
|
|
* NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
|
2010-09-26 23:16:21 +00:00
|
|
|
*/
|
2011-04-28 18:09:41 +00:00
|
|
|
@VisibleForTesting
|
2012-06-28 17:40:46 +00:00
|
|
|
Notification createNewMessageNotification(long mailboxId, long newMessageId,
|
2011-07-22 18:41:51 +00:00
|
|
|
int unseenMessageCount, int unreadCount) {
|
2012-06-28 17:40:46 +00:00
|
|
|
final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
|
|
|
if (mailbox == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
final Account account = Account.restoreAccountWithId(mContext, mailbox.mAccountKey);
|
2010-09-26 23:16:21 +00:00
|
|
|
if (account == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
// Get the latest message
|
2012-06-28 17:40:46 +00:00
|
|
|
final Message message = Message.restoreMessageWithId(mContext, newMessageId);
|
2010-09-26 23:16:21 +00:00
|
|
|
if (message == null) {
|
|
|
|
return null; // no message found???
|
|
|
|
}
|
|
|
|
|
2011-03-08 18:34:18 +00:00
|
|
|
String senderName = Address.toFriendly(Address.unpack(message.mFrom));
|
|
|
|
if (senderName == null) {
|
|
|
|
senderName = ""; // Happens when a message has no from.
|
|
|
|
}
|
2011-06-24 00:48:25 +00:00
|
|
|
final boolean multipleUnseen = unseenMessageCount > 1;
|
2011-08-18 00:10:06 +00:00
|
|
|
final Bitmap senderPhoto = multipleUnseen
|
|
|
|
? mGenericMultipleSenderIcon
|
|
|
|
: getSenderPhoto(message);
|
2011-06-24 00:48:25 +00:00
|
|
|
final SpannableString title = getNewMessageTitle(senderName, unseenMessageCount);
|
|
|
|
// TODO: add in display name on the second line for the text, once framework supports
|
|
|
|
// multiline texts.
|
2012-06-28 17:40:46 +00:00
|
|
|
// Show account name if an inbox; otherwise mailbox name
|
2011-06-24 00:48:25 +00:00
|
|
|
final String text = multipleUnseen
|
2012-06-28 17:40:46 +00:00
|
|
|
? ((mailbox.mType == Mailbox.TYPE_INBOX) ? account.mDisplayName :
|
|
|
|
mailbox.mDisplayName)
|
2011-06-24 00:48:25 +00:00
|
|
|
: message.mSubject;
|
2011-04-28 18:09:41 +00:00
|
|
|
final Bitmap largeIcon = senderPhoto != null ? senderPhoto : mGenericSenderIcon;
|
2011-06-24 00:48:25 +00:00
|
|
|
final Integer number = unreadCount > 1 ? unreadCount : null;
|
2012-06-28 17:40:46 +00:00
|
|
|
Intent intent = createViewConversationIntent(message);
|
|
|
|
if (intent == null) {
|
|
|
|
return null;
|
2011-05-13 15:41:48 +00:00
|
|
|
}
|
2012-04-27 01:26:57 +00:00
|
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
|
|
|
|
Intent.FLAG_ACTIVITY_TASK_ON_HOME);
|
2011-07-22 18:41:51 +00:00
|
|
|
long now = mClock.getTime();
|
2011-07-22 20:29:48 +00:00
|
|
|
boolean enableAudio = (now - mLastMessageNotifyTime) > MIN_SOUND_INTERVAL_MS;
|
2012-06-28 17:40:46 +00:00
|
|
|
final Notification.Builder builder = createBaseAccountNotificationBuilder(
|
|
|
|
mailbox.mAccountKey, title.toString(), title, text,
|
2011-07-28 20:55:10 +00:00
|
|
|
intent, largeIcon, number, enableAudio, false);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (Utils.isRunningJellybeanOrLater()) {
|
|
|
|
// For a new-style notification
|
|
|
|
if (multipleUnseen) {
|
|
|
|
final Cursor messageCursor =
|
|
|
|
mContext.getContentResolver().query(ContentUris.withAppendedId(
|
|
|
|
EmailContent.MAILBOX_NOTIFICATION_URI, mailbox.mAccountKey),
|
|
|
|
EmailContent.NOTIFICATION_PROJECTION, null, null, null);
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (messageCursor != null && messageCursor.getCount() > 0) {
|
|
|
|
final int maxNumDigestItems = mContext.getResources().getInteger(
|
|
|
|
R.integer.max_num_notification_digest_items);
|
|
|
|
// The body of the notification is the account name, or the label name.
|
|
|
|
builder.setSubText(text);
|
|
|
|
|
|
|
|
Notification.InboxStyle digest = new Notification.InboxStyle(builder);
|
|
|
|
|
|
|
|
digest.setBigContentTitle(title);
|
|
|
|
|
|
|
|
int numDigestItems = 0;
|
|
|
|
// We can assume that the current position of the cursor is on the
|
|
|
|
// newest message
|
|
|
|
messageCursor.moveToFirst();
|
|
|
|
do {
|
|
|
|
final long messageId =
|
|
|
|
messageCursor.getLong(EmailContent.ID_PROJECTION_COLUMN);
|
|
|
|
|
|
|
|
// Get the latest message
|
|
|
|
final Message digestMessage =
|
|
|
|
Message.restoreMessageWithId(mContext, messageId);
|
|
|
|
if (digestMessage != null) {
|
|
|
|
final CharSequence digestLine =
|
|
|
|
getSingleMessageInboxLine(mContext, digestMessage);
|
|
|
|
digest.addLine(digestLine);
|
|
|
|
numDigestItems++;
|
|
|
|
}
|
|
|
|
} while (numDigestItems <= maxNumDigestItems && messageCursor.moveToNext());
|
|
|
|
|
|
|
|
// We want to clear the content text in this case. The content text would
|
|
|
|
// have been set in createBaseAccountNotificationBuilder, but since the
|
|
|
|
// same string was set in as the subtext, we don't want to show a
|
|
|
|
// duplicate string.
|
|
|
|
builder.setContentText(null);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (messageCursor != null) {
|
|
|
|
messageCursor.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// The notification content will be the subject of the conversation.
|
|
|
|
builder.setContentText(getSingleMessageLittleText(mContext, message.mSubject));
|
|
|
|
|
|
|
|
// The notification subtext will be the subject of the conversation for inbox
|
|
|
|
// notifications, or will based on the the label name for user label notifications.
|
|
|
|
builder.setSubText(account.mDisplayName);
|
|
|
|
|
|
|
|
final Notification.BigTextStyle bigText = new Notification.BigTextStyle(builder);
|
|
|
|
bigText.bigText(getSingleMessageBigText(mContext, message));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-22 18:41:51 +00:00
|
|
|
mLastMessageNotifyTime = now;
|
2012-06-28 17:40:46 +00:00
|
|
|
return builder.getNotification();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the bigtext for a notification for a single new conversation
|
|
|
|
* @param context
|
|
|
|
* @param message New message that triggered the notification.
|
|
|
|
* @return a {@link CharSequence} suitable for use in {@link Notification.BigTextStyle}
|
|
|
|
*/
|
|
|
|
private static CharSequence getSingleMessageInboxLine(Context context, Message message) {
|
|
|
|
final String subject = message.mSubject;
|
|
|
|
final String snippet = message.mSnippet;
|
|
|
|
final String senders = Address.toFriendly(Address.unpack(message.mFrom));
|
|
|
|
|
|
|
|
final String subjectSnippet = !TextUtils.isEmpty(subject) ? subject : snippet;
|
|
|
|
|
|
|
|
final TextAppearanceSpan notificationPrimarySpan =
|
|
|
|
new TextAppearanceSpan(context, R.style.NotificationPrimaryText);
|
|
|
|
|
|
|
|
if (TextUtils.isEmpty(senders)) {
|
|
|
|
// If the senders are empty, just use the subject/snippet.
|
|
|
|
return subjectSnippet;
|
|
|
|
}
|
|
|
|
else if (TextUtils.isEmpty(subjectSnippet)) {
|
|
|
|
// If the subject/snippet is empty, just use the senders.
|
|
|
|
final SpannableString spannableString = new SpannableString(senders);
|
|
|
|
spannableString.setSpan(notificationPrimarySpan, 0, senders.length(), 0);
|
|
|
|
|
|
|
|
return spannableString;
|
|
|
|
} else {
|
|
|
|
final String formatString = context.getResources().getString(
|
|
|
|
R.string.multiple_new_message_notification_item);
|
|
|
|
final TextAppearanceSpan notificationSecondarySpan =
|
|
|
|
new TextAppearanceSpan(context, R.style.NotificationSecondaryText);
|
|
|
|
|
|
|
|
final String instantiatedString = String.format(formatString, senders, subjectSnippet);
|
|
|
|
|
|
|
|
final SpannableString spannableString = new SpannableString(instantiatedString);
|
|
|
|
|
|
|
|
final boolean isOrderReversed = formatString.indexOf("%2$s") <
|
|
|
|
formatString.indexOf("%1$s");
|
|
|
|
final int primaryOffset =
|
|
|
|
(isOrderReversed ? instantiatedString.lastIndexOf(senders) :
|
|
|
|
instantiatedString.indexOf(senders));
|
|
|
|
final int secondaryOffset =
|
|
|
|
(isOrderReversed ? instantiatedString.lastIndexOf(subjectSnippet) :
|
|
|
|
instantiatedString.indexOf(subjectSnippet));
|
|
|
|
spannableString.setSpan(notificationPrimarySpan,
|
|
|
|
primaryOffset, primaryOffset + senders.length(), 0);
|
|
|
|
spannableString.setSpan(notificationSecondarySpan,
|
|
|
|
secondaryOffset, secondaryOffset + subjectSnippet.length(), 0);
|
|
|
|
return spannableString;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the bigtext for a notification for a single new conversation
|
|
|
|
* @param context
|
|
|
|
* @param subject Subject of the new message that triggered the notification
|
|
|
|
* @return a {@link CharSequence} suitable for use in {@link Notification.ContentText}
|
|
|
|
*/
|
|
|
|
private static CharSequence getSingleMessageLittleText(Context context, String subject) {
|
|
|
|
if (subject == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan(
|
|
|
|
context, R.style.NotificationPrimaryText);
|
|
|
|
|
|
|
|
final SpannableString spannableString = new SpannableString(subject);
|
|
|
|
spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0);
|
|
|
|
|
|
|
|
return spannableString;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the bigtext for a notification for a single new conversation
|
|
|
|
* @param context
|
|
|
|
* @param message New message that triggered the notification
|
|
|
|
* @return a {@link CharSequence} suitable for use in {@link Notification.BigTextStyle}
|
|
|
|
*/
|
|
|
|
private static CharSequence getSingleMessageBigText(Context context, Message message) {
|
|
|
|
final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan(
|
|
|
|
context, R.style.NotificationPrimaryText);
|
|
|
|
|
|
|
|
final String subject = message.mSubject;
|
|
|
|
final String snippet = message.mSnippet;
|
|
|
|
|
|
|
|
if (TextUtils.isEmpty(subject)) {
|
|
|
|
// If the subject is empty, just use the snippet.
|
|
|
|
return snippet;
|
|
|
|
}
|
|
|
|
else if (TextUtils.isEmpty(snippet)) {
|
|
|
|
// If the snippet is empty, just use the subject.
|
|
|
|
final SpannableString spannableString = new SpannableString(subject);
|
|
|
|
spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0);
|
|
|
|
|
|
|
|
return spannableString;
|
|
|
|
} else {
|
|
|
|
final String notificationBigTextFormat = context.getResources().getString(
|
|
|
|
R.string.single_new_message_notification_big_text);
|
|
|
|
|
|
|
|
// Localizers may change the order of the parameters, look at how the format
|
|
|
|
// string is structured.
|
|
|
|
final boolean isSubjectFirst = notificationBigTextFormat.indexOf("%2$s") >
|
|
|
|
notificationBigTextFormat.indexOf("%1$s");
|
|
|
|
final String bigText = String.format(notificationBigTextFormat, subject, snippet);
|
|
|
|
final SpannableString spannableString = new SpannableString(bigText);
|
|
|
|
|
|
|
|
final int subjectOffset =
|
|
|
|
(isSubjectFirst ? bigText.indexOf(subject) : bigText.lastIndexOf(subject));
|
|
|
|
spannableString.setSpan(notificationSubjectSpan,
|
|
|
|
subjectOffset, subjectOffset + subject.length(), 0);
|
|
|
|
|
|
|
|
return spannableString;
|
|
|
|
}
|
2010-09-26 23:16:21 +00:00
|
|
|
}
|
|
|
|
|
2010-12-03 22:44:47 +00:00
|
|
|
/**
|
2011-06-24 00:48:25 +00:00
|
|
|
* Creates a notification title for a new message. If there is only a single message,
|
|
|
|
* show the sender name. Otherwise, show "X new messages".
|
2010-12-03 22:44:47 +00:00
|
|
|
*/
|
2011-04-28 18:09:41 +00:00
|
|
|
@VisibleForTesting
|
2011-06-24 00:48:25 +00:00
|
|
|
SpannableString getNewMessageTitle(String sender, int unseenCount) {
|
|
|
|
String title;
|
|
|
|
if (unseenCount > 1) {
|
|
|
|
title = String.format(
|
|
|
|
mContext.getString(R.string.notification_multiple_new_messages_fmt),
|
|
|
|
unseenCount);
|
2010-12-03 22:44:47 +00:00
|
|
|
} else {
|
2011-06-24 00:48:25 +00:00
|
|
|
title = sender;
|
2010-12-03 22:44:47 +00:00
|
|
|
}
|
2011-06-24 00:48:25 +00:00
|
|
|
return new SpannableString(title);
|
2010-12-03 22:44:47 +00:00
|
|
|
}
|
|
|
|
|
2011-04-28 18:09:41 +00:00
|
|
|
/** Returns the system's current ringer mode */
|
|
|
|
@VisibleForTesting
|
|
|
|
int getRingerMode() {
|
2010-12-03 22:44:47 +00:00
|
|
|
return mAudioManager.getRingerMode();
|
|
|
|
}
|
|
|
|
|
2011-04-28 18:09:41 +00:00
|
|
|
/** Sets up the notification's sound and vibration based upon account details. */
|
|
|
|
@VisibleForTesting
|
|
|
|
void setupSoundAndVibration(Notification.Builder builder, Account account) {
|
2010-09-26 23:16:21 +00:00
|
|
|
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;
|
2011-04-28 18:09:41 +00:00
|
|
|
final boolean isRingerSilent = getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
|
2010-09-26 23:16:21 +00:00
|
|
|
|
2011-04-28 18:09:41 +00:00
|
|
|
int defaults = Notification.DEFAULT_LIGHTS;
|
|
|
|
if (vibrate || (vibrateWhenSilent && isRingerSilent)) {
|
|
|
|
defaults |= Notification.DEFAULT_VIBRATE;
|
2010-09-26 23:16:21 +00:00
|
|
|
}
|
|
|
|
|
2011-04-28 18:09:41 +00:00
|
|
|
builder.setSound((ringtoneUri == null) ? null : Uri.parse(ringtoneUri))
|
|
|
|
.setDefaults(defaults);
|
2010-09-26 23:16:21 +00:00
|
|
|
}
|
2010-10-18 20:14:20 +00:00
|
|
|
|
|
|
|
/**
|
2011-04-28 18:09:41 +00:00
|
|
|
* Show (or update) a notification that the given attachment could not be forwarded. This
|
|
|
|
* is a very unusual case, and perhaps we shouldn't even send a notification. For now,
|
|
|
|
* it's helpful for debugging.
|
|
|
|
*
|
2011-02-17 00:38:18 +00:00
|
|
|
* NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
|
2010-10-18 20:14:20 +00:00
|
|
|
*/
|
2011-02-17 00:38:18 +00:00
|
|
|
public void showDownloadForwardFailedNotification(Attachment attachment) {
|
2012-06-28 17:40:46 +00:00
|
|
|
Message message = Message.restoreMessageWithId(mContext, attachment.mMessageKey);
|
|
|
|
if (message == null) return;
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
|
|
|
|
showNotification(mailbox.mAccountKey,
|
2010-10-18 20:14:20 +00:00
|
|
|
mContext.getString(R.string.forward_download_failed_ticker),
|
2011-02-17 00:38:18 +00:00
|
|
|
mContext.getString(R.string.forward_download_failed_title),
|
|
|
|
attachment.mFileName,
|
|
|
|
null,
|
|
|
|
NOTIFICATION_ID_ATTACHMENT_WARNING);
|
2010-10-18 20:14:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-04-28 18:09:41 +00:00
|
|
|
* Returns a notification ID for login failed notifications for the given account account.
|
2010-10-18 20:14:20 +00:00
|
|
|
*/
|
|
|
|
private int getLoginFailedNotificationId(long accountId) {
|
|
|
|
return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int)accountId;
|
|
|
|
}
|
|
|
|
|
2011-02-17 00:38:18 +00:00
|
|
|
/**
|
2011-04-28 18:09:41 +00:00
|
|
|
* Show (or update) a notification that there was a login failure for the given account.
|
|
|
|
*
|
2011-02-17 00:38:18 +00:00
|
|
|
* NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
|
|
|
|
*/
|
2010-10-18 20:14:20 +00:00
|
|
|
public void showLoginFailedNotification(long accountId) {
|
2012-07-27 20:36:41 +00:00
|
|
|
showLoginFailedNotification(accountId, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void showLoginFailedNotification(long accountId, String reason) {
|
2010-10-18 20:14:20 +00:00
|
|
|
final Account account = Account.restoreAccountWithId(mContext, accountId);
|
|
|
|
if (account == null) return;
|
2012-06-28 17:40:46 +00:00
|
|
|
final Mailbox mailbox = Mailbox.restoreMailboxOfType(mContext, account.mId,
|
|
|
|
Mailbox.TYPE_INBOX);
|
|
|
|
if (mailbox == null) return;
|
|
|
|
showNotification(mailbox.mAccountKey,
|
2010-10-18 20:14:20 +00:00
|
|
|
mContext.getString(R.string.login_failed_ticker, account.mDisplayName),
|
2011-02-17 00:38:18 +00:00
|
|
|
mContext.getString(R.string.login_failed_title),
|
|
|
|
account.getDisplayName(),
|
2011-05-13 00:27:56 +00:00
|
|
|
AccountSettings.createAccountSettingsIntent(mContext, accountId,
|
2012-07-27 20:36:41 +00:00
|
|
|
account.mDisplayName, reason),
|
2011-02-17 00:38:18 +00:00
|
|
|
getLoginFailedNotificationId(accountId));
|
2010-10-18 20:14:20 +00:00
|
|
|
}
|
|
|
|
|
2011-04-28 18:09:41 +00:00
|
|
|
/**
|
|
|
|
* Cancels the login failed notification for the given account.
|
|
|
|
*/
|
2010-10-18 20:14:20 +00:00
|
|
|
public void cancelLoginFailedNotification(long accountId) {
|
|
|
|
mNotificationManager.cancel(getLoginFailedNotificationId(accountId));
|
|
|
|
}
|
2011-03-22 00:08:16 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* Cancels the new message notification for a given mailbox
|
|
|
|
*/
|
|
|
|
public void cancelNewMessageNotification(long mailboxId) {
|
|
|
|
mNotificationManager.cancel(getNewMessageNotificationId(mailboxId));
|
|
|
|
}
|
|
|
|
|
2011-03-22 00:08:16 +00:00
|
|
|
/**
|
2011-04-28 18:09:41 +00:00
|
|
|
* Show (or update) a notification that the user's password is expiring. The given account
|
|
|
|
* is used to update the display text, but, all accounts share the same notification ID.
|
2011-03-22 00:08:16 +00:00
|
|
|
*
|
2011-04-28 18:09:41 +00:00
|
|
|
* NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
|
2011-03-22 00:08:16 +00:00
|
|
|
*/
|
|
|
|
public void showPasswordExpiringNotification(long accountId) {
|
|
|
|
Account account = Account.restoreAccountWithId(mContext, accountId);
|
|
|
|
if (account == null) return;
|
2011-04-28 18:09:41 +00:00
|
|
|
|
2011-03-22 00:08:16 +00:00
|
|
|
Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext,
|
|
|
|
accountId, false);
|
2011-04-28 18:09:41 +00:00
|
|
|
String accountName = account.getDisplayName();
|
|
|
|
String ticker =
|
|
|
|
mContext.getString(R.string.password_expire_warning_ticker_fmt, accountName);
|
|
|
|
String title = mContext.getString(R.string.password_expire_warning_content_title);
|
2012-06-28 17:40:46 +00:00
|
|
|
showNotification(accountId, ticker, title, accountName, intent,
|
2011-03-22 00:08:16 +00:00
|
|
|
NOTIFICATION_ID_PASSWORD_EXPIRING);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-04-28 18:09:41 +00:00
|
|
|
* Show (or update) a notification that the user's password has expired. The given account
|
|
|
|
* is used to update the display text, but, all accounts share the same notification ID.
|
2011-03-22 00:08:16 +00:00
|
|
|
*
|
2011-04-28 18:09:41 +00:00
|
|
|
* NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
|
2011-03-22 00:08:16 +00:00
|
|
|
*/
|
|
|
|
public void showPasswordExpiredNotification(long accountId) {
|
|
|
|
Account account = Account.restoreAccountWithId(mContext, accountId);
|
|
|
|
if (account == null) return;
|
2011-04-28 18:09:41 +00:00
|
|
|
|
2011-03-22 00:08:16 +00:00
|
|
|
Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext,
|
|
|
|
accountId, true);
|
2011-04-28 18:09:41 +00:00
|
|
|
String accountName = account.getDisplayName();
|
2011-03-22 00:08:16 +00:00
|
|
|
String ticker = mContext.getString(R.string.password_expired_ticker);
|
2011-04-28 18:09:41 +00:00
|
|
|
String title = mContext.getString(R.string.password_expired_content_title);
|
2012-06-28 17:40:46 +00:00
|
|
|
showNotification(accountId, ticker, title, accountName, intent,
|
2011-04-28 18:09:41 +00:00
|
|
|
NOTIFICATION_ID_PASSWORD_EXPIRED);
|
2011-03-22 00:08:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-04-28 18:09:41 +00:00
|
|
|
* Cancels any password expire notifications [both expired & expiring].
|
2011-03-22 00:08:16 +00:00
|
|
|
*/
|
|
|
|
public void cancelPasswordExpirationNotifications() {
|
2011-05-10 18:36:57 +00:00
|
|
|
mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRING);
|
|
|
|
mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRED);
|
2011-03-22 00:08:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-06-28 17:40:46 +00:00
|
|
|
* Show (or update) a security needed notification. If tapped, the user is taken to a
|
|
|
|
* dialog asking whether he wants to update his settings.
|
2011-03-22 00:08:16 +00:00
|
|
|
*/
|
|
|
|
public void showSecurityNeededNotification(Account account) {
|
|
|
|
Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, account.mId, true);
|
2011-04-28 18:09:41 +00:00
|
|
|
String accountName = account.getDisplayName();
|
|
|
|
String ticker =
|
2012-06-28 17:40:46 +00:00
|
|
|
mContext.getString(R.string.security_needed_ticker_fmt, accountName);
|
|
|
|
String title = mContext.getString(R.string.security_notification_content_update_title);
|
|
|
|
showNotification(account.mId, ticker, title, accountName, intent,
|
|
|
|
(int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show (or update) a security changed notification. If tapped, the user is taken to the
|
|
|
|
* account settings screen where he can view the list of enforced policies
|
|
|
|
*/
|
|
|
|
public void showSecurityChangedNotification(Account account) {
|
2012-07-27 20:36:41 +00:00
|
|
|
Intent intent =
|
|
|
|
AccountSettings.createAccountSettingsIntent(mContext, account.mId, null, null);
|
2012-06-28 17:40:46 +00:00
|
|
|
String accountName = account.getDisplayName();
|
|
|
|
String ticker =
|
|
|
|
mContext.getString(R.string.security_changed_ticker_fmt, accountName);
|
|
|
|
String title = mContext.getString(R.string.security_notification_content_change_title);
|
|
|
|
showNotification(account.mId, ticker, title, accountName, intent,
|
|
|
|
(int)(NOTIFICATION_ID_BASE_SECURITY_CHANGED + account.mId));
|
2011-03-22 00:08:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-06-28 17:40:46 +00:00
|
|
|
* Show (or update) a security unsupported notification. If tapped, the user is taken to the
|
|
|
|
* account settings screen where he can view the list of unsupported policies
|
|
|
|
*/
|
|
|
|
public void showSecurityUnsupportedNotification(Account account) {
|
2012-07-27 20:36:41 +00:00
|
|
|
Intent intent =
|
|
|
|
AccountSettings.createAccountSettingsIntent(mContext, account.mId, null, null);
|
2012-06-28 17:40:46 +00:00
|
|
|
String accountName = account.getDisplayName();
|
|
|
|
String ticker =
|
|
|
|
mContext.getString(R.string.security_unsupported_ticker_fmt, accountName);
|
|
|
|
String title = mContext.getString(R.string.security_notification_content_unsupported_title);
|
|
|
|
showNotification(account.mId, ticker, title, accountName, intent,
|
|
|
|
(int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cancels all security needed notifications.
|
2011-03-22 00:08:16 +00:00
|
|
|
*/
|
|
|
|
public void cancelSecurityNeededNotification() {
|
2012-06-28 17:40:46 +00:00
|
|
|
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
|
|
|
|
Account.ID_PROJECTION, null, null, null);
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
long id = c.getLong(Account.ID_PROJECTION_COLUMN);
|
|
|
|
mNotificationManager.cancel(
|
|
|
|
(int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + id));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
}});
|
2011-03-22 00:08:16 +00:00
|
|
|
}
|
2011-05-03 21:42:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Observer invoked whenever a message we're notifying the user about changes.
|
|
|
|
*/
|
|
|
|
private static class MessageContentObserver extends ContentObserver {
|
|
|
|
private final Context mContext;
|
2011-05-10 18:36:57 +00:00
|
|
|
private final long mAccountId;
|
2011-05-03 21:42:26 +00:00
|
|
|
|
2011-05-10 18:36:57 +00:00
|
|
|
public MessageContentObserver(
|
2012-06-28 17:40:46 +00:00
|
|
|
Handler handler, Context context, long accountId) {
|
2011-05-10 18:36:57 +00:00
|
|
|
super(handler);
|
2011-05-03 21:42:26 +00:00
|
|
|
mContext = context;
|
|
|
|
mAccountId = accountId;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onChange(boolean selfChange) {
|
2011-07-20 01:19:59 +00:00
|
|
|
ContentObserver observer = sInstance.mNotificationMap.get(mAccountId);
|
|
|
|
Account account = Account.restoreAccountWithId(mContext, mAccountId);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (observer == null || account == null) {
|
2011-07-21 18:02:51 +00:00
|
|
|
Log.w(Logging.LOG_TAG, "Couldn't find account for changed message notification");
|
|
|
|
return;
|
|
|
|
}
|
2011-05-03 21:42:26 +00:00
|
|
|
|
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
2012-06-28 17:40:46 +00:00
|
|
|
Cursor c = resolver.query(ContentUris.withAppendedId(
|
|
|
|
EmailContent.MAILBOX_NOTIFICATION_URI, mAccountId),
|
|
|
|
EmailContent.NOTIFICATION_PROJECTION, null, null, null);
|
2011-05-03 21:42:26 +00:00
|
|
|
try {
|
2012-06-28 17:40:46 +00:00
|
|
|
while (c.moveToNext()) {
|
|
|
|
long mailboxId = c.getLong(EmailContent.NOTIFICATION_MAILBOX_ID_COLUMN);
|
|
|
|
if (mailboxId == 0) continue;
|
|
|
|
int messageCount =
|
|
|
|
c.getInt(EmailContent.NOTIFICATION_MAILBOX_MESSAGE_COUNT_COLUMN);
|
|
|
|
int unreadCount =
|
|
|
|
c.getInt(EmailContent.NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN);
|
|
|
|
|
|
|
|
Mailbox m = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
|
|
|
long newMessageId = Utility.getFirstRowLong(mContext,
|
|
|
|
ContentUris.withAppendedId(
|
|
|
|
EmailContent.MAILBOX_MOST_RECENT_MESSAGE_URI, mailboxId),
|
|
|
|
Message.ID_COLUMN_PROJECTION, null, null, null,
|
|
|
|
Message.ID_MAILBOX_COLUMN_ID, -1L);
|
|
|
|
Log.d(Logging.LOG_TAG, "Changes to " + account.mDisplayName + "/" +
|
|
|
|
m.mDisplayName + ", count: " + messageCount + ", lastNotified: " +
|
|
|
|
m.mLastNotifiedMessageKey + ", mostRecent: " + newMessageId);
|
|
|
|
// Broadcast intent here
|
|
|
|
Intent i = new Intent(EmailBroadcastProcessorService.ACTION_NOTIFY_NEW_MAIL);
|
|
|
|
// Required by UIProvider
|
|
|
|
i.setType(EmailProvider.EMAIL_APP_MIME_TYPE);
|
|
|
|
i.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_FOLDER,
|
|
|
|
Uri.parse(EmailProvider.uiUriString("uifolder", mailboxId)));
|
|
|
|
i.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_ACCOUNT,
|
|
|
|
Uri.parse(EmailProvider.uiUriString("uiaccount", m.mAccountKey)));
|
|
|
|
i.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNREAD_COUNT,
|
|
|
|
unreadCount);
|
|
|
|
// Required by our notification controller
|
|
|
|
i.putExtra(NEW_MAIL_MAILBOX_ID, mailboxId);
|
|
|
|
i.putExtra(NEW_MAIL_MESSAGE_ID, newMessageId);
|
|
|
|
i.putExtra(NEW_MAIL_MESSAGE_COUNT, messageCount);
|
|
|
|
i.putExtra(NEW_MAIL_UNREAD_COUNT, unreadCount);
|
|
|
|
mContext.sendOrderedBroadcast(i, null);
|
2011-05-10 18:36:57 +00:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
2011-05-03 21:42:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
public static void notifyNewMail(Context context, Intent i) {
|
|
|
|
Log.d(Logging.LOG_TAG, "Sending notification to system...");
|
|
|
|
NotificationController nc = NotificationController.getInstance(context);
|
|
|
|
ContentResolver resolver = context.getContentResolver();
|
|
|
|
long mailboxId = i.getLongExtra(NEW_MAIL_MAILBOX_ID, -1);
|
|
|
|
long newMessageId = i.getLongExtra(NEW_MAIL_MESSAGE_ID, -1);
|
|
|
|
int messageCount = i.getIntExtra(NEW_MAIL_MESSAGE_COUNT, 0);
|
|
|
|
int unreadCount = i.getIntExtra(NEW_MAIL_UNREAD_COUNT, 0);
|
|
|
|
Notification n = nc.createNewMessageNotification(mailboxId, newMessageId,
|
|
|
|
messageCount, unreadCount);
|
|
|
|
if (n != null) {
|
|
|
|
// Make the notification visible
|
|
|
|
nc.mNotificationManager.notify(nc.getNewMessageNotificationId(mailboxId), n);
|
|
|
|
}
|
|
|
|
// Save away the new values
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY, newMessageId);
|
|
|
|
cv.put(MailboxColumns.LAST_NOTIFIED_MESSAGE_COUNT, messageCount);
|
|
|
|
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), cv,
|
|
|
|
null, null);
|
|
|
|
}
|
|
|
|
|
2011-05-11 22:29:24 +00:00
|
|
|
/**
|
|
|
|
* Observer invoked whenever an account is modified. This could mean the user changed the
|
|
|
|
* notification settings.
|
|
|
|
*/
|
|
|
|
private static class AccountContentObserver extends ContentObserver {
|
|
|
|
private final Context mContext;
|
|
|
|
public AccountContentObserver(Handler handler, Context context) {
|
|
|
|
super(handler);
|
|
|
|
mContext = context;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onChange(boolean selfChange) {
|
|
|
|
final ContentResolver resolver = mContext.getContentResolver();
|
2012-06-28 17:40:46 +00:00
|
|
|
final Cursor c = resolver.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
|
2011-05-11 22:29:24 +00:00
|
|
|
NOTIFIED_ACCOUNT_SELECTION, null, null);
|
|
|
|
final HashSet<Long> newAccountList = new HashSet<Long>();
|
|
|
|
final HashSet<Long> removedAccountList = new HashSet<Long>();
|
|
|
|
if (c == null) {
|
|
|
|
// Suspender time ... theoretically, this will never happen
|
|
|
|
Log.wtf(Logging.LOG_TAG, "#onChange(); NULL response for account id query");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
long accountId = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
|
|
|
|
newAccountList.add(accountId);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (c != null) {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// NOTE: Looping over three lists is not necessarily the most efficient. However, the
|
|
|
|
// account lists are going to be very small, so, this will not be necessarily bad.
|
|
|
|
// Cycle through existing notification list and adjust as necessary
|
|
|
|
for (long accountId : sInstance.mNotificationMap.keySet()) {
|
|
|
|
if (!newAccountList.remove(accountId)) {
|
|
|
|
// account id not in the current set of notifiable accounts
|
|
|
|
removedAccountList.add(accountId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// A new account was added to the notification list
|
|
|
|
for (long accountId : newAccountList) {
|
|
|
|
sInstance.registerMessageNotification(accountId);
|
|
|
|
}
|
|
|
|
// An account was removed from the notification list
|
|
|
|
for (long accountId : removedAccountList) {
|
|
|
|
sInstance.unregisterMessageNotification(accountId);
|
|
|
|
int notificationId = sInstance.getNewMessageNotificationId(accountId);
|
|
|
|
sInstance.mNotificationManager.cancel(notificationId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-10 18:36:57 +00:00
|
|
|
/**
|
|
|
|
* Thread to handle all notification actions through its own {@link Looper}.
|
|
|
|
*/
|
|
|
|
private static class NotificationThread implements Runnable {
|
|
|
|
/** Lock to ensure proper initialization */
|
|
|
|
private final Object mLock = new Object();
|
|
|
|
/** The {@link Looper} that handles messages for this thread */
|
|
|
|
private Looper mLooper;
|
|
|
|
|
|
|
|
NotificationThread() {
|
|
|
|
new Thread(null, this, "EmailNotification").start();
|
|
|
|
synchronized (mLock) {
|
|
|
|
while (mLooper == null) {
|
|
|
|
try {
|
|
|
|
mLock.wait();
|
|
|
|
} catch (InterruptedException ex) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
synchronized (mLock) {
|
|
|
|
Looper.prepare();
|
|
|
|
mLooper = Looper.myLooper();
|
|
|
|
mLock.notifyAll();
|
|
|
|
}
|
|
|
|
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
|
|
|
Looper.loop();
|
|
|
|
}
|
|
|
|
void quit() {
|
|
|
|
mLooper.quit();
|
|
|
|
}
|
|
|
|
Looper getLooper() {
|
|
|
|
return mLooper;
|
|
|
|
}
|
2011-05-03 21:42:26 +00:00
|
|
|
}
|
2010-09-26 23:16:21 +00:00
|
|
|
}
|