EmailNotificationController.java revision 308ce9284793b597797994dfb1fb25155cbe0b20
1899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki/*
2899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * Copyright (C) 2010 The Android Open Source Project
3899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki *
4899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * Licensed under the Apache License, Version 2.0 (the "License");
5899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * you may not use this file except in compliance with the License.
6899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * You may obtain a copy of the License at
7899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki *
8899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki *      http://www.apache.org/licenses/LICENSE-2.0
9899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki *
10899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * Unless required by applicable law or agreed to in writing, software
11899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * distributed under the License is distributed on an "AS IS" BASIS,
12899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * See the License for the specific language governing permissions and
14899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * limitations under the License.
15899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki */
16899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
17899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukipackage com.android.email;
18899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
19899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport com.android.email.activity.ContactStatusLoader;
20899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport com.android.email.activity.Welcome;
21308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onukiimport com.android.email.activity.setup.AccountSecurity;
22d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blankimport com.android.email.activity.setup.AccountSettingsXL;
232193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.Address;
24a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent;
25a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Account;
26a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment;
27a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
2831d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.utility.Utility;
29899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
30899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.app.Notification;
31899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.app.NotificationManager;
32899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.app.PendingIntent;
33899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.content.Context;
34d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blankimport android.content.Intent;
35899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.graphics.Bitmap;
3674e094834c6efd3260c717090a89232a892c411bMakoto Onukiimport android.graphics.BitmapFactory;
37899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.media.AudioManager;
38899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.net.Uri;
3974e094834c6efd3260c717090a89232a892c411bMakoto Onukiimport android.text.SpannableString;
40899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.text.TextUtils;
4174e094834c6efd3260c717090a89232a892c411bMakoto Onukiimport android.text.style.TextAppearanceSpan;
42899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
43dd6e6b850462fd8617a73dd40b5e7d32f0aaf5edMakoto Onukiimport java.util.concurrent.atomic.AtomicInteger;
44dd6e6b850462fd8617a73dd40b5e7d32f0aaf5edMakoto Onuki
45899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki/**
46899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * Class that manages notifications.
47899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki */
48899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukipublic class NotificationController {
49308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_SECURITY_NEEDED = 1;
50308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
51308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    // ID reserved for CalendarSyncEnabler
52308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int PLACEHOLDER_NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED = 2;
53308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3;
54308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_PASSWORD_EXPIRING = 4;
55308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_PASSWORD_EXPIRED = 5;
56d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
57d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    private static final int NOTIFICATION_ID_BASE_NEW_MESSAGES = 0x10000000;
58d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000;
59899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
60899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private static NotificationController sInstance;
61899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private final Context mContext;
62899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private final NotificationManager mNotificationManager;
63899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private final AudioManager mAudioManager;
64c1c3b6f21ecddbb237195caedc36af442ebf00e8Andy Stadler    private final Bitmap mGenericSenderIcon;
6574e094834c6efd3260c717090a89232a892c411bMakoto Onuki    private final Clock mClock;
66899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
67899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /** Constructor */
6874e094834c6efd3260c717090a89232a892c411bMakoto Onuki    /* package */ NotificationController(Context context, Clock clock) {
69899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        mContext = context.getApplicationContext();
70899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        mNotificationManager = (NotificationManager) context.getSystemService(
71899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                Context.NOTIFICATION_SERVICE);
72899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
73c1c3b6f21ecddbb237195caedc36af442ebf00e8Andy Stadler        mGenericSenderIcon = BitmapFactory.decodeResource(mContext.getResources(),
74c1c3b6f21ecddbb237195caedc36af442ebf00e8Andy Stadler                R.drawable.ic_contact_picture);
7574e094834c6efd3260c717090a89232a892c411bMakoto Onuki        mClock = clock;
76899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
77899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
78899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /** Singleton access */
79899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    public static synchronized NotificationController getInstance(Context context) {
80899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        if (sInstance == null) {
8174e094834c6efd3260c717090a89232a892c411bMakoto Onuki            sInstance = new NotificationController(context, Clock.INSTANCE);
82899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
83899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        return sInstance;
84899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
85899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
86899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /**
871ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * Generic notifier for any account.  Uses notification rules from account.
88c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     * NOTE:  Ticker is not shown in Holo XL notifications.
891ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     *
901ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param account The account for which the notification is posted
911ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param ticker String for ticker
921ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param contentTitle String for notification content title
931ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param contentText String for notification content text
941ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param intent The intent to launch from the notification
951ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param notificationId The notification id
961ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
97308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private void showAccountNotification(Account account, String ticker, String contentTitle,
981ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            String contentText, Intent intent, int notificationId) {
991ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1001ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // Pending Intent
101c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        PendingIntent pending = null;
102c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        if (intent != null) {
103c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler            pending =
104c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
105c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        }
1061ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1071ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // Ringtone & Vibration
1081ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        String ringtoneString = account.getRingtone();
1091ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        Uri ringTone = (ringtoneString == null) ? null : Uri.parse(ringtoneString);
1101ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        boolean vibrate = 0 != (account.mFlags & Account.FLAGS_VIBRATE_ALWAYS);
1111ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        boolean vibrateWhenSilent = 0 != (account.mFlags & Account.FLAGS_VIBRATE_WHEN_SILENT);
1121ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1131ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // Use the account's notification rules for sound & vibrate (but always notify)
1141ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        boolean nowSilent =
1151ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
1161ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1171ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        int defaults = Notification.DEFAULT_LIGHTS;
1181ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        if (vibrate || (vibrateWhenSilent && nowSilent)) {
1191ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            defaults |= Notification.DEFAULT_VIBRATE;
1201ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
1211ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1221ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // Notification
1231ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        Notification.Builder nb = new Notification.Builder(mContext);
1241ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        nb.setSmallIcon(R.drawable.stat_notify_email_generic);
1251ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        nb.setTicker(ticker);
1261ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        nb.setContentTitle(contentTitle);
1271ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        nb.setContentText(contentText);
1281ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        nb.setContentIntent(pending);
1291ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        nb.setSound(ringTone);
1301ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        nb.setDefaults(defaults);
1311ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        Notification notification = nb.getNotification();
1321ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1331ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        mNotificationManager.notify(notificationId, notification);
1341ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
1351ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1361ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
1371ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * Generic notification canceler.
1381ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param notificationId The notification id
1391ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
140308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private void cancelNotification(int notificationId) {
1411ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        mNotificationManager.cancel(notificationId);
1421ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
1431ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1441ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
145899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     * @return the "new message" notification ID for an account. It just assumes
146899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     *         accountID won't be too huge. Any other smarter/cleaner way?
147899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     */
148899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private int getNewMessageNotificationId(long accountId) {
149d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        return (int) (NOTIFICATION_ID_BASE_NEW_MESSAGES + accountId);
150899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
151899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
152899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /**
153899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     * Dismiss new message notification
154899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     *
155899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     * @param accountId ID of the target account, or -1 for all accounts.
156899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     */
157899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    public void cancelNewMessageNotification(long accountId) {
158899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        if (accountId == -1) {
159899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            new Utility.ForEachAccount(mContext) {
160899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                @Override
161899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                protected void performAction(long accountId) {
162899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                    cancelNewMessageNotification(accountId);
163899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                }
164899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            }.execute();
165899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        } else {
166899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            mNotificationManager.cancel(getNewMessageNotificationId(accountId));
167899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
168899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
169899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
170899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /**
171899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     * Show (or update) the "new message" notification.
172899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     */
173899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    public void showNewMessageNotification(final long accountId, final int unseenMessageCount,
174899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            final int justFetchedCount) {
175899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        Utility.runAsync(new Runnable() {
176899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            @Override
177899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            public void run() {
17874e094834c6efd3260c717090a89232a892c411bMakoto Onuki                Notification n = createNewMessageNotification(accountId, unseenMessageCount);
179899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                if (n == null) {
180899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                    return;
181899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                }
182899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                mNotificationManager.notify(getNewMessageNotificationId(accountId), n);
183899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            }
184899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        });
185899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
186899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
187899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /**
188899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     * @return The sender's photo, if available, or null.
189899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     *
190899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     * Don't call it on the UI thread.
191899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     */
192899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private Bitmap getSenderPhoto(Message message) {
193899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        Address sender = Address.unpackFirst(message.mFrom);
194899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        if (sender == null) {
195899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            return null;
196899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
197899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        String email = sender.getAddress();
198899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        if (TextUtils.isEmpty(email)) {
199899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            return null;
200899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
201899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        return ContactStatusLoader.load(mContext, email).mPhoto;
202899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
203899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
204dd6e6b850462fd8617a73dd40b5e7d32f0aaf5edMakoto Onuki    private static final AtomicInteger sSequenceNumber = new AtomicInteger();
205dd6e6b850462fd8617a73dd40b5e7d32f0aaf5edMakoto Onuki
206dd6e6b850462fd8617a73dd40b5e7d32f0aaf5edMakoto Onuki    /**
207899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     * Create a notification
208899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     *
209899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     * Don't call it on the UI thread.
210899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     */
21174e094834c6efd3260c717090a89232a892c411bMakoto Onuki    /* package */ Notification createNewMessageNotification(long accountId,
21274e094834c6efd3260c717090a89232a892c411bMakoto Onuki            int unseenMessageCount) {
213899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        final Account account = Account.restoreAccountWithId(mContext, accountId);
214899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        if (account == null) {
215899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            return null;
216899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
217899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        // Get the latest message
2186f3d167cfa948d20153c2172c34cbede5ec83a1dMakoto Onuki        final Message message = Message.getLatestIncomingMessage(mContext, accountId);
219899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        if (message == null) {
220899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            return null; // no message found???
221899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
222899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
22343c455eb266125a8e09a98e38d7639a865f52d42Makoto Onuki        String senderName = Address.toFriendly(Address.unpack(message.mFrom));
22443c455eb266125a8e09a98e38d7639a865f52d42Makoto Onuki        if (senderName == null) {
22543c455eb266125a8e09a98e38d7639a865f52d42Makoto Onuki            senderName = ""; // Happens when a message has no from.
22643c455eb266125a8e09a98e38d7639a865f52d42Makoto Onuki        }
227899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        final String subject = message.mSubject;
228899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        final Bitmap senderPhoto = getSenderPhoto(message);
229899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
230899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        // Intent to open inbox
231899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0,
232308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                Welcome.createOpenAccountInboxIntent(mContext, accountId),
233dd6e6b850462fd8617a73dd40b5e7d32f0aaf5edMakoto Onuki                0);
234899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
2352e578032d09b376a48146932146f88394f731077Joe Onorato        Notification.Builder builder = new Notification.Builder(mContext)
2362e578032d09b376a48146932146f88394f731077Joe Onorato                .setSmallIcon(R.drawable.stat_notify_email_generic)
23774e094834c6efd3260c717090a89232a892c411bMakoto Onuki                .setWhen(mClock.getTime())
238c1c3b6f21ecddbb237195caedc36af442ebf00e8Andy Stadler                .setLargeIcon(senderPhoto != null ? senderPhoto : mGenericSenderIcon)
23974e094834c6efd3260c717090a89232a892c411bMakoto Onuki                .setContentTitle(getNotificationTitle(senderName, account.mDisplayName))
24074e094834c6efd3260c717090a89232a892c411bMakoto Onuki                .setContentText(subject)
2418d3ef1d01e1d3ae548366fe7961af23ed66f1da5Makoto Onuki                .setContentIntent(contentIntent);
24274e094834c6efd3260c717090a89232a892c411bMakoto Onuki        if (unseenMessageCount > 1) {
24374e094834c6efd3260c717090a89232a892c411bMakoto Onuki            builder.setNumber(unseenMessageCount);
24474e094834c6efd3260c717090a89232a892c411bMakoto Onuki        }
245899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
2462e578032d09b376a48146932146f88394f731077Joe Onorato        Notification notification = builder.getNotification();
247899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
248899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        setupNotificationSoundAndVibrationFromAccount(notification, account);
249899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        return notification;
250899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
251899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
25274e094834c6efd3260c717090a89232a892c411bMakoto Onuki    /**
25374e094834c6efd3260c717090a89232a892c411bMakoto Onuki     * Creates the notification title.
25474e094834c6efd3260c717090a89232a892c411bMakoto Onuki     *
25574e094834c6efd3260c717090a89232a892c411bMakoto Onuki     * If only 1 account, just show the sender name.
25674e094834c6efd3260c717090a89232a892c411bMakoto Onuki     * If 2+ accounts, make it "SENDER_NAME to RECEIVER_NAME", and gray out the "to RECEIVER_NAME"
25774e094834c6efd3260c717090a89232a892c411bMakoto Onuki     * part.
25874e094834c6efd3260c717090a89232a892c411bMakoto Onuki     */
25974e094834c6efd3260c717090a89232a892c411bMakoto Onuki    /* package */ SpannableString getNotificationTitle(String sender, String receiverDisplayName) {
26074e094834c6efd3260c717090a89232a892c411bMakoto Onuki        final int numAccounts = EmailContent.count(mContext, Account.CONTENT_URI);
26174e094834c6efd3260c717090a89232a892c411bMakoto Onuki        if (numAccounts == 1) {
26274e094834c6efd3260c717090a89232a892c411bMakoto Onuki            return new SpannableString(sender);
26374e094834c6efd3260c717090a89232a892c411bMakoto Onuki        } else {
26474e094834c6efd3260c717090a89232a892c411bMakoto Onuki            // "to [account name]"
26574e094834c6efd3260c717090a89232a892c411bMakoto Onuki            String toAcccount = mContext.getResources().getString(R.string.notification_to_account,
26674e094834c6efd3260c717090a89232a892c411bMakoto Onuki                    receiverDisplayName);
26774e094834c6efd3260c717090a89232a892c411bMakoto Onuki            // "[Sender] to [account name]"
26874e094834c6efd3260c717090a89232a892c411bMakoto Onuki            SpannableString senderToAccount = new SpannableString(sender + " " + toAcccount);
26974e094834c6efd3260c717090a89232a892c411bMakoto Onuki
27074e094834c6efd3260c717090a89232a892c411bMakoto Onuki            // "[Sender] to [account name]"
27174e094834c6efd3260c717090a89232a892c411bMakoto Onuki            //           ^^^^^^^^^^^^^^^^^ <- Make this part gray
27274e094834c6efd3260c717090a89232a892c411bMakoto Onuki            TextAppearanceSpan secondarySpan = new TextAppearanceSpan(
27374e094834c6efd3260c717090a89232a892c411bMakoto Onuki                    mContext, R.style.notification_secondary_text);
27474e094834c6efd3260c717090a89232a892c411bMakoto Onuki            senderToAccount.setSpan(secondarySpan, sender.length() + 1, senderToAccount.length(),
27574e094834c6efd3260c717090a89232a892c411bMakoto Onuki                    0);
27674e094834c6efd3260c717090a89232a892c411bMakoto Onuki            return senderToAccount;
27774e094834c6efd3260c717090a89232a892c411bMakoto Onuki        }
27874e094834c6efd3260c717090a89232a892c411bMakoto Onuki    }
27974e094834c6efd3260c717090a89232a892c411bMakoto Onuki
28074e094834c6efd3260c717090a89232a892c411bMakoto Onuki    // Overridden for testing (AudioManager can't be mocked out.)
28174e094834c6efd3260c717090a89232a892c411bMakoto Onuki    /* package */ int getRingerMode() {
28274e094834c6efd3260c717090a89232a892c411bMakoto Onuki        return mAudioManager.getRingerMode();
28374e094834c6efd3260c717090a89232a892c411bMakoto Onuki    }
28474e094834c6efd3260c717090a89232a892c411bMakoto Onuki
28574e094834c6efd3260c717090a89232a892c411bMakoto Onuki    /* package */ boolean isRingerModeSilent() {
28674e094834c6efd3260c717090a89232a892c411bMakoto Onuki        return getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
287899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
288899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
28974e094834c6efd3260c717090a89232a892c411bMakoto Onuki    /* package */ void setupNotificationSoundAndVibrationFromAccount(Notification notification,
290899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            Account account) {
291899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        final int flags = account.mFlags;
292899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        final String ringtoneUri = account.mRingtoneUri;
293899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        final boolean vibrate = (flags & Account.FLAGS_VIBRATE_ALWAYS) != 0;
294899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        final boolean vibrateWhenSilent = (flags & Account.FLAGS_VIBRATE_WHEN_SILENT) != 0;
295899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
296899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        notification.sound = (ringtoneUri == null) ? null : Uri.parse(ringtoneUri);
297899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
298899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        if (vibrate || (vibrateWhenSilent && isRingerModeSilent())) {
299899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki            notification.defaults |= Notification.DEFAULT_VIBRATE;
300899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
301899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
302899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        // This code is identical to that used by Gmail and GTalk for notifications
303899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        notification.flags |= Notification.FLAG_SHOW_LIGHTS;
304899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        notification.defaults |= Notification.DEFAULT_LIGHTS;
305899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
306d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
307d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    /**
308d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank     * Alert the user that an attachment couldn't be forwarded.  This is a very unusual case, and
309d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank     * perhaps we shouldn't even send a notification. For now, it's helpful for debugging.
310c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
311d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank     */
312c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler    public void showDownloadForwardFailedNotification(Attachment attachment) {
313c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        final Account account = Account.restoreAccountWithId(mContext, attachment.mAccountKey);
314c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        if (account == null) return;
315308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        showAccountNotification(account,
316d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank                mContext.getString(R.string.forward_download_failed_ticker),
317c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                mContext.getString(R.string.forward_download_failed_title),
318c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                attachment.mFileName,
319c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                null,
320c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                NOTIFICATION_ID_ATTACHMENT_WARNING);
321d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
322d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
323d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    /**
324d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank     * Alert the user that login failed for the specified account
325d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank     */
326d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    private int getLoginFailedNotificationId(long accountId) {
327d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int)accountId;
328d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
329d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
330c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler    /**
331c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     * Alert the user that login failed on a particular account.
332c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
333c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     */
334d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    public void showLoginFailedNotification(long accountId) {
335d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        final Account account = Account.restoreAccountWithId(mContext, accountId);
336d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        if (account == null) return;
337308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        showAccountNotification(account,
338d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank                mContext.getString(R.string.login_failed_ticker, account.mDisplayName),
339c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                mContext.getString(R.string.login_failed_title),
340c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                account.getDisplayName(),
341f4894131427ec7562fcb05388b9f3aa094e388bcAndy Stadler                AccountSettingsXL.createAccountSettingsIntent(mContext, accountId,
342f4894131427ec7562fcb05388b9f3aa094e388bcAndy Stadler                        account.mDisplayName),
343c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                getLoginFailedNotificationId(accountId));
344d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
345d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
346d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    public void cancelLoginFailedNotification(long accountId) {
347d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        mNotificationManager.cancel(getLoginFailedNotificationId(accountId));
348d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
349308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
350308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
351308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     * Show "password expiring" notification.
352308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     *
353308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     * Note all accounts share the same notification ID.
354308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
355308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void showPasswordExpiringNotification(long accountId) {
356308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Account account = Account.restoreAccountWithId(mContext, accountId);
357308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        if (account == null) return;
358308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext,
359308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                accountId, false);
360308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String ticker = mContext.getString(
361308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                R.string.password_expire_warning_ticker_fmt, account.getDisplayName());
362308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String contentTitle = mContext.getString(
363308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                R.string.password_expire_warning_content_title);
364308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String contentText = account.getDisplayName();
365308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        showAccountNotification(account, ticker, contentTitle, contentText, intent,
366308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                NOTIFICATION_ID_PASSWORD_EXPIRING);
367308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
368308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
369308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
370308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     * Show "password expired" notification.
371308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     *
372308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     * Note all accounts share the same notification ID.
373308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
374308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void showPasswordExpiredNotification(long accountId) {
375308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Account account = Account.restoreAccountWithId(mContext, accountId);
376308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        if (account == null) return;
377308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext,
378308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                accountId, true);
379308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String ticker = mContext.getString(R.string.password_expired_ticker);
380308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String contentTitle = mContext.getString(R.string.password_expired_content_title);
381308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String contentText = account.getDisplayName();
382308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        showAccountNotification(account, ticker, contentTitle,
383308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                contentText, intent, NOTIFICATION_ID_PASSWORD_EXPIRED);
384308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
385308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
386308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
387308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     * Cancel both "password expired/expiring" notifications.
388308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
389308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void cancelPasswordExpirationNotifications() {
390308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        cancelNotification(NOTIFICATION_ID_PASSWORD_EXPIRING);
391308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        cancelNotification(NOTIFICATION_ID_PASSWORD_EXPIRED);
392308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
393308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
394308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
395308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     * Show "security needed" notification.
396308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
397308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void showSecurityNeededNotification(Account account) {
398308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String tickerText = mContext.getString(R.string.security_notification_ticker_fmt,
399308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                account.getDisplayName());
400308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String contentTitle = mContext.getString(R.string.security_notification_content_title);
401308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String contentText = account.getDisplayName();
402308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, account.mId, true);
403308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        showAccountNotification(
404308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                account, tickerText, contentTitle, contentText, intent,
405308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                NOTIFICATION_ID_SECURITY_NEEDED);
406308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
407308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
408308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
409308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     * Cancel "security needed" notification.
410308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
411308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void cancelSecurityNeededNotification() {
412308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        cancelNotification(NOTIFICATION_ID_SECURITY_NEEDED);
413308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
414899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki}
415