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 android.app.Notification;
20dfdc8b6da3cc074c97d53a597855aeaa3f039a68Todd Kennedyimport android.app.Notification.Builder;
21899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.app.NotificationManager;
22899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.app.PendingIntent;
23c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedyimport android.content.ContentResolver;
246e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport android.content.ContentUris;
25899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.content.Context;
26d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blankimport android.content.Intent;
27c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedyimport android.database.ContentObserver;
2883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedyimport android.database.Cursor;
2972f6a2432ed3ca183e2311ffaa2f7fa87f3df915Rohan Shahimport android.graphics.Bitmap;
30899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.net.Uri;
31c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedyimport android.os.Handler;
3283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedyimport android.os.Looper;
3383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedyimport android.os.Process;
34b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedyimport android.provider.Settings;
35b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedyimport android.support.v4.app.NotificationCompat;
36899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.text.TextUtils;
37a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantlerimport android.text.format.DateUtils;
38899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
396e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.email.activity.setup.AccountSecurity;
408c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantlerimport com.android.email.activity.setup.HeadlessAccountSettingsLoader;
41f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport com.android.email.provider.EmailProvider;
42840408c41cfc95ffa491aaf11f1c3f4075eae9c9Tony Mantlerimport com.android.email.service.EmailServiceUtils;
436e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.Account;
446e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.EmailContent;
456e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment;
466e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
476e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.Mailbox;
48f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport com.android.emailcommon.utility.EmailAsyncTask;
49b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedyimport com.android.mail.preferences.FolderPreferences;
50f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport com.android.mail.providers.Folder;
51f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport com.android.mail.providers.UIProvider;
525dcb447c04ccfc6bf3201ad127dcb1e9721f429aAndrew Sappersteinimport com.android.mail.utils.Clock;
53b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedyimport com.android.mail.utils.LogTag;
54b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedyimport com.android.mail.utils.LogUtils;
55b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedyimport com.android.mail.utils.NotificationUtils;
566e418aa41a17136be0dddb816d843428a0a1e722Marc Blank
57c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedyimport java.util.HashMap;
58e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedyimport java.util.HashSet;
59b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedyimport java.util.Map;
60b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedyimport java.util.Set;
61c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy
62899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki/**
63899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * Class that manages notifications.
64899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki */
65bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrookpublic class EmailNotificationController implements NotificationController {
66b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private static final String LOG_TAG = LogTag.getLogTag();
67f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
68308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3;
69308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_PASSWORD_EXPIRING = 4;
70308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_PASSWORD_EXPIRED = 5;
71d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
72f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private static final int NOTIFICATION_ID_BASE_MASK = 0xF0000000;
73d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000;
74f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private static final int NOTIFICATION_ID_BASE_SECURITY_NEEDED = 0x30000000;
75f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private static final int NOTIFICATION_ID_BASE_SECURITY_CHANGED = 0x40000000;
76899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
77e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    private static NotificationThread sNotificationThread;
78e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    private static Handler sNotificationHandler;
79bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    private static EmailNotificationController sInstance;
80899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private final Context mContext;
81899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private final NotificationManager mNotificationManager;
8274e094834c6efd3260c717090a89232a892c411bMakoto Onuki    private final Clock mClock;
83f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    /** Maps account id to its observer */
84b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private final Map<Long, ContentObserver> mNotificationMap =
85b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            new HashMap<Long, ContentObserver>();
86e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    private ContentObserver mAccountObserver;
87899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
88899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /** Constructor */
89bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    protected EmailNotificationController(Context context, Clock clock) {
90899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        mContext = context.getApplicationContext();
91e714bb9d153cfe13a7f0932e7d67ea08fa5a1d98Marc Blank        EmailContent.init(context);
92899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        mNotificationManager = (NotificationManager) context.getSystemService(
93899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                Context.NOTIFICATION_SERVICE);
9474e094834c6efd3260c717090a89232a892c411bMakoto Onuki        mClock = clock;
95899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
96899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
97899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /** Singleton access */
98bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    public static synchronized EmailNotificationController getInstance(Context context) {
99899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        if (sInstance == null) {
100bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook            sInstance = new EmailNotificationController(context, Clock.INSTANCE);
101899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
102899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        return sInstance;
103899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
104899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
105899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /**
106d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank     * Return whether or not a notification, based on the passed-in id, needs to be "ongoing"
107d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank     * @param notificationId the notification id to check
108d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank     * @return whether or not the notification must be "ongoing"
109d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank     */
110b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private static boolean needsOngoingNotification(int notificationId) {
111d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank        // "Security needed" must be ongoing so that the user doesn't close it; otherwise, sync will
112d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank        // be prevented until a reboot.  Consider also doing this for password expired.
113f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        return (notificationId & NOTIFICATION_ID_BASE_MASK) == NOTIFICATION_ID_BASE_SECURITY_NEEDED;
114d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank    }
115d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank
116d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank    /**
117b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy     * Returns a {@link android.support.v4.app.NotificationCompat.Builder} for an event with the
118b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy     * given account. The account contains specific rules on ring tone usage and these will be used
119b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy     * to modify the notification behaviour.
1201ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     *
121f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * @param accountId The id of the account this notification is being built for.
122958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param ticker Text displayed when the notification is first shown. May be {@code null}.
123958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param title The first line of text. May NOT be {@code null}.
124958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param contentText The second line of text. May NOT be {@code null}.
125958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param intent The intent to start if the user clicks on the notification.
126b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy     * @param number A number to display using {@link Builder#setNumber(int)}. May be {@code null}.
127c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy     * @param enableAudio If {@code false}, do not play any sound. Otherwise, play sound according
128c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy     *        to the settings for the given account.
129958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @return A {@link Notification} that can be sent to the notification service.
1301ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
131b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private NotificationCompat.Builder createBaseAccountNotificationBuilder(long accountId,
132523d3073f6d81e1d1e8b3ad249cc82e7e3496e01Rohan Shah            String ticker, CharSequence title, String contentText, Intent intent,
133d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank            Integer number, boolean enableAudio, boolean ongoing) {
1341ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // Pending Intent
135c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        PendingIntent pending = null;
136c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        if (intent != null) {
137958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            pending = PendingIntent.getActivity(
138958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                    mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
139c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        }
1401ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
141958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        // NOTE: the ticker is not shown for notifications in the Holo UX
142b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
143958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setContentTitle(title)
144958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setContentText(contentText)
145958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setContentIntent(pending)
146958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setNumber(number == null ? 0 : number)
14772f6a2432ed3ca183e2311ffaa2f7fa87f3df915Rohan Shah                .setSmallIcon(R.drawable.ic_notification_mail_24dp)
148958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setWhen(mClock.getTime())
149d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank                .setTicker(ticker)
150d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank                .setOngoing(ongoing);
151c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy
152c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        if (enableAudio) {
153f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            Account account = Account.restoreAccountWithId(mContext, accountId);
154c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy            setupSoundAndVibration(builder, account);
155c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        }
1561ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
157f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        return builder;
158958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy    }
1591ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
160958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy    /**
161958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Generic notifier for any account.  Uses notification rules from account.
162958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     *
163f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * @param accountId The account id this notification is being built for.
164958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param ticker Text displayed when the notification is first shown. May be {@code null}.
165958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param title The first line of text. May NOT be {@code null}.
166958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param contentText The second line of text. May NOT be {@code null}.
167958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param intent The intent to start if the user clicks on the notification.
168958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param notificationId The ID of the notification to register with the service.
169958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     */
170f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private void showNotification(long accountId, String ticker, String title,
171958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            String contentText, Intent intent, int notificationId) {
172b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        final NotificationCompat.Builder builder = createBaseAccountNotificationBuilder(accountId,
173523d3073f6d81e1d1e8b3ad249cc82e7e3496e01Rohan Shah                ticker, title, contentText, intent, null, true,
174f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                needsOngoingNotification(notificationId));
175b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        mNotificationManager.notify(notificationId, builder.build());
1761ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
1771ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1781ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
17983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * Tells the notification controller if it should be watching for changes to the message table.
18083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * This is the main life cycle method for message notifications. When we stop observing
18183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * database changes, we save the state [e.g. message ID and count] of the most recent
18283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * notification shown to the user. And, when we start observing database changes, we restore
18383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * the saved state.
184899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     */
185bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
186b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    public void watchForMessages() {
1875701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        ensureHandlerExists();
18883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        // Run this on the message notification handler
189e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        sNotificationHandler.post(new Runnable() {
19083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            @Override
19183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            public void run() {
19283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                ContentResolver resolver = mContext.getContentResolver();
19383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
19483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                // otherwise, start new observers for all notified accounts
1955701e0a555a5c263862156c1291aa13b06850425Todd Kennedy                registerMessageNotification(Account.ACCOUNT_ID_COMBINED_VIEW);
196e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                // If we're already observing account changes, don't do anything else
197e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                if (mAccountObserver == null) {
198b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                    LogUtils.i(LOG_TAG, "Observing account changes for notifications");
199e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    mAccountObserver = new AccountContentObserver(sNotificationHandler, mContext);
200e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    resolver.registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver);
20183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                }
20283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
20383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        });
204899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
205899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
206899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /**
2075701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     * Ensures the notification handler exists and is ready to handle requests.
2085701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     */
209a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
210a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    /**
211a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler     * TODO: Notifications jump around too much because we get too many content updates.
212a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler     * We should try to make the provider generate fewer updates instead.
213a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler     */
214a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
215a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static final int NOTIFICATION_DELAYED_MESSAGE = 0;
216a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static final long NOTIFICATION_DELAY = 15 * DateUtils.SECOND_IN_MILLIS;
217a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    // True if we're coalescing notification updates
218a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static boolean sNotificationDelayedMessagePending;
219a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    // True if accounts have changed and we need to refresh everything
220a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static boolean sRefreshAllNeeded;
221a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    // Set of accounts we need to regenerate notifications for
222a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static final HashSet<Long> sRefreshAccountSet = new HashSet<Long>();
223a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    // These should all be accessed on-thread, but just in case...
224a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static final Object sNotificationDelayedMessageLock = new Object();
225a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
2265701e0a555a5c263862156c1291aa13b06850425Todd Kennedy    private static synchronized void ensureHandlerExists() {
2275701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        if (sNotificationThread == null) {
2285701e0a555a5c263862156c1291aa13b06850425Todd Kennedy            sNotificationThread = new NotificationThread();
229a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            sNotificationHandler = new Handler(sNotificationThread.getLooper(),
230a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    new Handler.Callback() {
231a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        @Override
232a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        public boolean handleMessage(final android.os.Message message) {
233a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            /**
234a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                             * To reduce spamming the notifications, we quiesce updates for a few
235a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                             * seconds to batch them up, then handle them here.
236a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                             */
237a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            LogUtils.d(LOG_TAG, "Delayed notification processing");
238a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            synchronized (sNotificationDelayedMessageLock) {
239a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                sNotificationDelayedMessagePending = false;
240a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                final Context context = (Context)message.obj;
241a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                if (sRefreshAllNeeded) {
242a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                    sRefreshAllNeeded = false;
243a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                    refreshAllNotificationsInternal(context);
244a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                }
245a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                for (final Long accountId : sRefreshAccountSet) {
246a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                    refreshNotificationsForAccountInternal(context, accountId);
247a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                }
248a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                sRefreshAccountSet.clear();
249a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            }
250a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            return true;
251a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        }
252a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    });
2535701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        }
2545701e0a555a5c263862156c1291aa13b06850425Todd Kennedy    }
2555701e0a555a5c263862156c1291aa13b06850425Todd Kennedy
2565701e0a555a5c263862156c1291aa13b06850425Todd Kennedy    /**
257f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Registers an observer for changes to mailboxes in the given account.
25883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * NOTE: This must be called on the notification handler thread.
25983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * @param accountId The ID of the account to register the observer for. May be
2605701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     *                  {@link Account#ACCOUNT_ID_COMBINED_VIEW} to register observers for all
2615701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     *                  accounts that allow for user notification.
262899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     */
263b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private void registerMessageNotification(final long accountId) {
26483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        ContentResolver resolver = mContext.getContentResolver();
2655701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
26683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            Cursor c = resolver.query(
26783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    Account.CONTENT_URI, EmailContent.ID_PROJECTION,
268b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                    null, null, null);
26983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            try {
27083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                while (c.moveToNext()) {
27183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
27283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    registerMessageNotification(id);
27383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                }
27483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            } finally {
27583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                c.close();
27683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
27783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        } else {
278aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            ContentObserver obs = mNotificationMap.get(accountId);
279aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            if (obs != null) return;  // we're already observing; nothing to do
280b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            LogUtils.i(LOG_TAG, "Registering for notifications for account " + accountId);
28183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            ContentObserver observer = new MessageContentObserver(
282f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    sNotificationHandler, mContext, accountId);
28383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            resolver.registerContentObserver(Message.NOTIFIER_URI, true, observer);
284aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            mNotificationMap.put(accountId, observer);
285e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // Now, ping the observer for any initial notifications
286aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            observer.onChange(true);
287c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        }
28883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    }
28983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
29083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    /**
29183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * Unregisters the observer for the given account. If the specified account does not have
292e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     * a registered observer, no action is performed. This will not clear any existing notification
293e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     * for the specified account. Use {@link NotificationManager#cancel(int)}.
29483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * NOTE: This must be called on the notification handler thread.
2955701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     * @param accountId The ID of the account to unregister from. To unregister all accounts that
2965701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     *                  have observers, specify an ID of {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
29783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     */
298b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private void unregisterMessageNotification(final long accountId) {
29983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        ContentResolver resolver = mContext.getContentResolver();
3005701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
301b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            LogUtils.i(LOG_TAG, "Unregistering notifications for all accounts");
30283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            // cancel all existing message observers
303aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            for (ContentObserver observer : mNotificationMap.values()) {
30483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                resolver.unregisterContentObserver(observer);
30583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
30683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            mNotificationMap.clear();
307c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        } else {
308b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            LogUtils.i(LOG_TAG, "Unregistering notifications for account " + accountId);
309aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            ContentObserver observer = mNotificationMap.remove(accountId);
310aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            if (observer != null) {
31183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                resolver.unregisterContentObserver(observer);
31283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
31383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        }
31483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    }
31583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
316f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public static final String EXTRA_ACCOUNT = "account";
317f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public static final String EXTRA_CONVERSATION = "conversationUri";
318f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public static final String EXTRA_FOLDER = "folder";
319f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
320b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    /** Sets up the notification's sound and vibration based upon account details. */
321b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private void setupSoundAndVibration(
322b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            NotificationCompat.Builder builder, Account account) {
323b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        String ringtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
324b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        boolean vibrate = false;
325f986f44ecac2b65fa50dcd8543945ff85d3b2245Mindy Pereira
326b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        // Use the Inbox notification preferences
327b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        final Cursor accountCursor = mContext.getContentResolver().query(EmailProvider.uiUri(
328b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                "uiaccount", account.mId), UIProvider.ACCOUNTS_PROJECTION, null, null, null);
329986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook
330b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        com.android.mail.providers.Account uiAccount = null;
331986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook        try {
332b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            if (accountCursor.moveToFirst()) {
3337e75afadb152659e3a237c62e4d95cefb60e228dRay Chen                uiAccount = com.android.mail.providers.Account.builder().buildFrom(accountCursor);
334986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook            }
335986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook        } finally {
336b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            accountCursor.close();
337986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook        }
338986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook
339b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        if (uiAccount != null) {
340b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            final Cursor folderCursor =
341b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                    mContext.getContentResolver().query(uiAccount.settings.defaultInbox,
342b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                            UIProvider.FOLDERS_PROJECTION, null, null, null);
343899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
344b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            if (folderCursor == null) {
34595bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                // This can happen when the notification is for the security policy notification
34695bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                // that happens before the account is setup
34795bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                LogUtils.w(LOG_TAG, "Null folder cursor for mailbox %s",
348b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                        uiAccount.settings.defaultInbox);
34995bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook            } else {
35095bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                Folder folder = null;
35195bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                try {
35295bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    if (folderCursor.moveToFirst()) {
35395bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                        folder = new Folder(folderCursor);
35495bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    }
35595bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                } finally {
35695bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    folderCursor.close();
357f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                }
358f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
35995bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                if (folder != null) {
36095bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    final FolderPreferences folderPreferences = new FolderPreferences(
36132614f3746f0fa8a09370672abaca68fa795fc33Tony Mantler                            mContext, uiAccount.getEmailAddress(), folder, true /* inbox */);
362899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
36395bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    ringtoneUri = folderPreferences.getNotificationRingtoneUri();
36495bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    vibrate = folderPreferences.isNotificationVibrateEnabled();
36595bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                } else {
36695bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    LogUtils.e(LOG_TAG,
36795bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                            "Null folder for mailbox %s", uiAccount.settings.defaultInbox);
36895bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                }
369b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            }
37074e094834c6efd3260c717090a89232a892c411bMakoto Onuki        } else {
371b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            LogUtils.e(LOG_TAG, "Null uiAccount for account id %d", account.mId);
37274e094834c6efd3260c717090a89232a892c411bMakoto Onuki        }
373899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
374958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        int defaults = Notification.DEFAULT_LIGHTS;
375f9997921cbea7feb13bbbd897e2f179494e5400eScott Kennedy        if (vibrate) {
376958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            defaults |= Notification.DEFAULT_VIBRATE;
377899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
378899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
379722b7bc29d3f5be029d21f5587b7bc675051073bScott Kennedy        builder.setSound(TextUtils.isEmpty(ringtoneUri) ? null : Uri.parse(ringtoneUri))
380958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            .setDefaults(defaults);
381899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
382d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
383d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    /**
384958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Show (or update) a notification that the given attachment could not be forwarded. This
385958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * is a very unusual case, and perhaps we shouldn't even send a notification. For now,
386958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * it's helpful for debugging.
387958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     *
388c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
389d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank     */
390bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
3918c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler    public void showDownloadForwardFailedNotificationSynchronous(Attachment attachment) {
3928c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Message message = Message.restoreMessageWithId(mContext, attachment.mMessageKey);
393f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (message == null) return;
3948c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
395f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(mailbox.mAccountKey,
396d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank                mContext.getString(R.string.forward_download_failed_ticker),
397c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                mContext.getString(R.string.forward_download_failed_title),
398c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                attachment.mFileName,
399c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                null,
400c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                NOTIFICATION_ID_ATTACHMENT_WARNING);
401d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
402d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
403d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    /**
404958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Returns a notification ID for login failed notifications for the given account account.
405d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank     */
406b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private static int getLoginFailedNotificationId(long accountId) {
407d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int)accountId;
408d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
409d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
410c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler    /**
411958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Show (or update) a notification that there was a login failure for the given account.
412958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     *
413c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
414c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     */
415bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
4168c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler    public void showLoginFailedNotificationSynchronous(long accountId, boolean incoming) {
417d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        final Account account = Account.restoreAccountWithId(mContext, accountId);
418d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        if (account == null) return;
4198c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Mailbox mailbox = Mailbox.restoreMailboxOfType(mContext, accountId,
420f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                Mailbox.TYPE_INBOX);
421f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (mailbox == null) return;
4228c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler
4238c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Intent settingsIntent;
4248c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        if (incoming) {
4258c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler            settingsIntent = new Intent(Intent.ACTION_VIEW,
426bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                    EmailProvider.getIncomingSettingsUri(accountId));
4278c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        } else {
4288c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler            settingsIntent = new Intent(Intent.ACTION_VIEW,
4298c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler                    HeadlessAccountSettingsLoader.getOutgoingSettingsUri(accountId));
4308c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        }
431f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(mailbox.mAccountKey,
432d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank                mContext.getString(R.string.login_failed_ticker, account.mDisplayName),
433c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                mContext.getString(R.string.login_failed_title),
434c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                account.getDisplayName(),
4358c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler                settingsIntent,
4368c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler                getLoginFailedNotificationId(accountId));
437d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
438d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
439958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy    /**
440958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Cancels the login failed notification for the given account.
441958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     */
442bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
443d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    public void cancelLoginFailedNotification(long accountId) {
444d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        mNotificationManager.cancel(getLoginFailedNotificationId(accountId));
445d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
446308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
447308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
448958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Show (or update) a notification that the user's password is expiring. The given account
449958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * is used to update the display text, but, all accounts share the same notification ID.
450308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     *
451958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
452308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
453bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
4548c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler    public void showPasswordExpiringNotificationSynchronous(long accountId) {
4558c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Account account = Account.restoreAccountWithId(mContext, accountId);
456308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        if (account == null) return;
457958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy
4588c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext,
459308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                accountId, false);
4608c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String accountName = account.getDisplayName();
4618c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String ticker =
462958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            mContext.getString(R.string.password_expire_warning_ticker_fmt, accountName);
4638c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String title = mContext.getString(R.string.password_expire_warning_content_title);
464f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(accountId, ticker, title, accountName, intent,
465308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                NOTIFICATION_ID_PASSWORD_EXPIRING);
466308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
467308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
468308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
469958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Show (or update) a notification that the user's password has expired. The given account
470958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * is used to update the display text, but, all accounts share the same notification ID.
471308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     *
472958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
473308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
474bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
4758c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler    public void showPasswordExpiredNotificationSynchronous(long accountId) {
4768c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Account account = Account.restoreAccountWithId(mContext, accountId);
477308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        if (account == null) return;
478958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy
4798c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext,
480308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                accountId, true);
4818c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String accountName = account.getDisplayName();
4828c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String ticker = mContext.getString(R.string.password_expired_ticker);
4838c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String title = mContext.getString(R.string.password_expired_content_title);
484f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(accountId, ticker, title, accountName, intent,
485958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                NOTIFICATION_ID_PASSWORD_EXPIRED);
486308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
487308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
488308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
489958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Cancels any password expire notifications [both expired & expiring].
490308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
491bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
492308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void cancelPasswordExpirationNotifications() {
49383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRING);
49483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRED);
495308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
496308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
497308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
498f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Show (or update) a security needed notification. If tapped, the user is taken to a
499f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * dialog asking whether he wants to update his settings.
500308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
501bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
502308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void showSecurityNeededNotification(Account account) {
503308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, account.mId, true);
504958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        String accountName = account.getDisplayName();
505958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        String ticker =
506f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            mContext.getString(R.string.security_needed_ticker_fmt, accountName);
507f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        String title = mContext.getString(R.string.security_notification_content_update_title);
508f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(account.mId, ticker, title, accountName, intent,
509f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                (int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
510f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    }
511f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
512f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    /**
513f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Show (or update) a security changed notification. If tapped, the user is taken to the
514f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * account settings screen where he can view the list of enforced policies
515f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     */
516bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
517f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public void showSecurityChangedNotification(Account account) {
5188c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Intent intent = new Intent(Intent.ACTION_VIEW,
519bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                EmailProvider.getIncomingSettingsUri(account.getId()));
5208c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String accountName = account.getDisplayName();
5218c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String ticker =
522f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            mContext.getString(R.string.security_changed_ticker_fmt, accountName);
5238c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String title =
5248c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler                mContext.getString(R.string.security_notification_content_change_title);
525f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(account.mId, ticker, title, accountName, intent,
526f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                (int)(NOTIFICATION_ID_BASE_SECURITY_CHANGED + account.mId));
527308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
528308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
529308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
530f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Show (or update) a security unsupported notification. If tapped, the user is taken to the
531f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * account settings screen where he can view the list of unsupported policies
532f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     */
533bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
534f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public void showSecurityUnsupportedNotification(Account account) {
5358c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final Intent intent = new Intent(Intent.ACTION_VIEW,
536bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                EmailProvider.getIncomingSettingsUri(account.getId()));
5378c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String accountName = account.getDisplayName();
5388c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String ticker =
539f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            mContext.getString(R.string.security_unsupported_ticker_fmt, accountName);
5408c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler        final String title =
5418c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler                mContext.getString(R.string.security_notification_content_unsupported_title);
542f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(account.mId, ticker, title, accountName, intent,
543f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                (int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
544f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank   }
545f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
546f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    /**
547f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Cancels all security needed notifications.
548308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
549bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
550308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void cancelSecurityNeededNotification() {
551f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        EmailAsyncTask.runAsyncParallel(new Runnable() {
552f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            @Override
553f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            public void run() {
554f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
555f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        Account.ID_PROJECTION, null, null, null);
556f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                try {
557f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    while (c.moveToNext()) {
558f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        long id = c.getLong(Account.ID_PROJECTION_COLUMN);
559f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        mNotificationManager.cancel(
560f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                               (int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + id));
561f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    }
562f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                }
563f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                finally {
564f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    c.close();
565f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                }
566f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            }});
567308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
568c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy
569c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy    /**
570d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy     * Cancels all notifications for the specified account id. This includes new mail notifications,
571d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy     * as well as special login/security notifications.
572d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy     */
573bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
574bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    public void cancelNotifications(final Context context, final Account account) {
575ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler        final EmailServiceUtils.EmailServiceInfo serviceInfo
576ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler                = EmailServiceUtils.getServiceInfoForAccount(context, account.mId);
577ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler        if (serviceInfo == null) {
578ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler            LogUtils.d(LOG_TAG, "Can't cancel notification for missing account %d", account.mId);
579ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler            return;
580ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler        }
581840408c41cfc95ffa491aaf11f1c3f4075eae9c9Tony Mantler        final android.accounts.Account notifAccount
582ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler                = account.getAccountManagerAccount(serviceInfo.accountType);
583ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler
584840408c41cfc95ffa491aaf11f1c3f4075eae9c9Tony Mantler        NotificationUtils.clearAccountNotifications(context, notifAccount);
585d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy
586d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy        final NotificationManager notificationManager = getInstance(context).mNotificationManager;
587d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy
588d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy        notificationManager.cancel((int) (NOTIFICATION_ID_BASE_LOGIN_WARNING + account.mId));
589d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy        notificationManager.cancel((int) (NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
590d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy        notificationManager.cancel((int) (NOTIFICATION_ID_BASE_SECURITY_CHANGED + account.mId));
591d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy    }
592d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy
593a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static void refreshNotificationsForAccount(final Context context,
594a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            final long accountId) {
595a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        synchronized (sNotificationDelayedMessageLock) {
596a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            if (sNotificationDelayedMessagePending) {
597a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sRefreshAccountSet.add(accountId);
598a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            } else {
599a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                ensureHandlerExists();
600a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sNotificationHandler.sendMessageDelayed(
601a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        android.os.Message.obtain(sNotificationHandler,
602a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                NOTIFICATION_DELAYED_MESSAGE, context), NOTIFICATION_DELAY);
603a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sNotificationDelayedMessagePending = true;
604a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                refreshNotificationsForAccountInternal(context, accountId);
605a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            }
606a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
607a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    }
608a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
609a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static void refreshNotificationsForAccountInternal(final Context context,
610a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            final long accountId) {
6118196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        final Uri accountUri = EmailProvider.uiUri("uiaccount", accountId);
6128196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
613a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        final ContentResolver contentResolver = context.getContentResolver();
614a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
6158196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        final Cursor mailboxCursor = contentResolver.query(
6168196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                ContentUris.withAppendedId(EmailContent.MAILBOX_NOTIFICATION_URI, accountId),
6178196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                null, null, null, null);
6188196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        try {
6198196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            while (mailboxCursor.moveToNext()) {
6208196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                final long mailboxId =
6218196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                        mailboxCursor.getLong(EmailContent.NOTIFICATION_MAILBOX_ID_COLUMN);
6228196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                if (mailboxId == 0) continue;
6238196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6248196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                final int unseenCount = mailboxCursor.getInt(
6258196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                        EmailContent.NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN);
6268196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6278196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                final int unreadCount;
6288196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                // If nothing is unseen, clear the notification
6298196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                if (unseenCount == 0) {
6308196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                    unreadCount = 0;
6318196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                } else {
6328196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                    unreadCount = mailboxCursor.getInt(
6338196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                            EmailContent.NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN);
6348196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                }
6358196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6368196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                final Uri folderUri = EmailProvider.uiUri("uifolder", mailboxId);
6378196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6388196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6398196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                LogUtils.d(LOG_TAG, "Changes to account " + accountId + ", folder: "
6408196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                        + mailboxId + ", unreadCount: " + unreadCount + ", unseenCount: "
6418196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                        + unseenCount);
6428196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6438196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                final Intent intent = new Intent(UIProvider.ACTION_UPDATE_NOTIFICATION);
6448196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                intent.setPackage(context.getPackageName());
6458196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                intent.setType(EmailProvider.EMAIL_APP_MIME_TYPE);
6468196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6478196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_ACCOUNT, accountUri);
6488196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_FOLDER, folderUri);
6498196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNREAD_COUNT,
6508196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                        unreadCount);
6518196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNSEEN_COUNT,
6528196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                        unseenCount);
6538196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6548196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                context.sendOrderedBroadcast(intent, null);
6558196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            }
6568196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        } finally {
6578196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            mailboxCursor.close();
6588196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        }
6598196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler    }
6608196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
661bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    @Override
662bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook    public void handleUpdateNotificationIntent(Context context, Intent intent) {
6638196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        final Uri accountUri =
6648196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                intent.getParcelableExtra(UIProvider.UpdateNotificationExtras.EXTRA_ACCOUNT);
6658196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        final Uri folderUri =
6668196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                intent.getParcelableExtra(UIProvider.UpdateNotificationExtras.EXTRA_FOLDER);
6678196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        final int unreadCount = intent.getIntExtra(
6688196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNREAD_COUNT, 0);
6698196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        final int unseenCount = intent.getIntExtra(
6708196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNSEEN_COUNT, 0);
6718196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6728196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        final ContentResolver contentResolver = context.getContentResolver();
6738196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
6748196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        final Cursor accountCursor = contentResolver.query(accountUri,
6758196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                UIProvider.ACCOUNTS_PROJECTION,  null, null, null);
676a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
677a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        if (accountCursor == null) {
6788196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            LogUtils.e(LOG_TAG, "Null account cursor for account " + accountUri);
679a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            return;
680a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
681a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
682a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        com.android.mail.providers.Account account = null;
683a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        try {
684a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            if (accountCursor.moveToFirst()) {
6857e75afadb152659e3a237c62e4d95cefb60e228dRay Chen                account = com.android.mail.providers.Account.builder().buildFrom(accountCursor);
686a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            }
687a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        } finally {
688a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            accountCursor.close();
689a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
690a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
691a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        if (account == null) {
6928196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            LogUtils.d(LOG_TAG, "Tried to create a notification for a missing account "
6938196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                    + accountUri);
694a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            return;
695a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
696a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
6978196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        final Cursor folderCursor = contentResolver.query(folderUri, UIProvider.FOLDERS_PROJECTION,
6988196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                null, null, null);
699a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
7008196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        if (folderCursor == null) {
7018196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            LogUtils.e(LOG_TAG, "Null folder cursor for account " + accountUri + ", mailbox "
7028196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                    + folderUri);
7038196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            return;
7048196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        }
705a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
7068196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        Folder folder = null;
7078196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        try {
7088196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            if (folderCursor.moveToFirst()) {
7098196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                folder = new Folder(folderCursor);
7108196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            } else {
7118196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                LogUtils.e(LOG_TAG, "Empty folder cursor for account " + accountUri + ", mailbox "
7128196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                        + folderUri);
7138196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler                return;
714a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            }
715a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        } finally {
7168196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler            folderCursor.close();
717a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
7188196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler
7198196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        // TODO: we don't always want getAttention to be true, but we don't necessarily have a
7208196f82ce207f0c7a1088996d0f3017a16c3637eTony Mantler        // good heuristic for when it should or shouldn't be.
7216c31d220a8607ef33c9e81e5eb1eabd985915841Andrew Sapperstein        NotificationUtils.sendSetNewEmailIndicatorIntent(context, unreadCount, unseenCount,
7226c31d220a8607ef33c9e81e5eb1eabd985915841Andrew Sapperstein                account, folder, true /* getAttention */);
723a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    }
724a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
725a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static void refreshAllNotifications(final Context context) {
726a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        synchronized (sNotificationDelayedMessageLock) {
727a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            if (sNotificationDelayedMessagePending) {
728a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sRefreshAllNeeded = true;
729a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            } else {
730a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                ensureHandlerExists();
731a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sNotificationHandler.sendMessageDelayed(
732a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        android.os.Message.obtain(sNotificationHandler,
733a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                NOTIFICATION_DELAYED_MESSAGE, context), NOTIFICATION_DELAY);
734a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sNotificationDelayedMessagePending = true;
735a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                refreshAllNotificationsInternal(context);
736a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            }
737a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
738a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    }
739a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
740a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static void refreshAllNotificationsInternal(final Context context) {
7416c31d220a8607ef33c9e81e5eb1eabd985915841Andrew Sapperstein        NotificationUtils.resendNotifications(
7427afc42646432f6b0a09a9c1e50806b908fdf9acdRohan Shah                context, false, null, null, null /* ContactFetcher */);
743a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    }
744a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
745d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy    /**
746c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy     * Observer invoked whenever a message we're notifying the user about changes.
747c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy     */
748c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy    private static class MessageContentObserver extends ContentObserver {
749c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        private final Context mContext;
75083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        private final long mAccountId;
751c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy
75283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        public MessageContentObserver(
753b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                final Handler handler, final Context context, final long accountId) {
75483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            super(handler);
755c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy            mContext = context;
756c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy            mAccountId = accountId;
757c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        }
758c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy
759c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        @Override
760b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public void onChange(final boolean selfChange) {
761a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            refreshNotificationsForAccount(mContext, mAccountId);
762f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        }
763f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    }
764f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
765e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    /**
766e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     * Observer invoked whenever an account is modified. This could mean the user changed the
767e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     * notification settings.
768e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     */
769e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    private static class AccountContentObserver extends ContentObserver {
770e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        private final Context mContext;
771b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public AccountContentObserver(final Handler handler, final Context context) {
772e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            super(handler);
773e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            mContext = context;
774e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        }
775e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy
776e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        @Override
777b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public void onChange(final boolean selfChange) {
778e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            final ContentResolver resolver = mContext.getContentResolver();
779f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            final Cursor c = resolver.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
780b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                null, null, null);
781b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            final Set<Long> newAccountList = new HashSet<Long>();
782b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            final Set<Long> removedAccountList = new HashSet<Long>();
783e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            if (c == null) {
784e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                // Suspender time ... theoretically, this will never happen
785b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                LogUtils.wtf(LOG_TAG, "#onChange(); NULL response for account id query");
786e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                return;
787e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
788e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            try {
789e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                while (c.moveToNext()) {
790e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    long accountId = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
791e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    newAccountList.add(accountId);
792e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                }
793e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            } finally {
7941b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy                c.close();
795e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
796e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // NOTE: Looping over three lists is not necessarily the most efficient. However, the
797e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // account lists are going to be very small, so, this will not be necessarily bad.
798e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // Cycle through existing notification list and adjust as necessary
799b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            for (final long accountId : sInstance.mNotificationMap.keySet()) {
800e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                if (!newAccountList.remove(accountId)) {
801e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    // account id not in the current set of notifiable accounts
802e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    removedAccountList.add(accountId);
803e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                }
804e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
805e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // A new account was added to the notification list
806b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            for (final long accountId : newAccountList) {
807e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                sInstance.registerMessageNotification(accountId);
808e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
809e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // An account was removed from the notification list
810b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            for (final long accountId : removedAccountList) {
811e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                sInstance.unregisterMessageNotification(accountId);
812e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
813b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy
814a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            refreshAllNotifications(mContext);
815e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        }
816e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    }
817e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy
81883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    /**
81983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * Thread to handle all notification actions through its own {@link Looper}.
82083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     */
82183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    private static class NotificationThread implements Runnable {
82283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        /** Lock to ensure proper initialization */
82383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        private final Object mLock = new Object();
82483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        /** The {@link Looper} that handles messages for this thread */
82583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        private Looper mLooper;
82683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
827b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public NotificationThread() {
82883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            new Thread(null, this, "EmailNotification").start();
82983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            synchronized (mLock) {
83083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                while (mLooper == null) {
83183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    try {
83283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                        mLock.wait();
83383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    } catch (InterruptedException ex) {
834f086deac70a5ee893ebd14be9d0502132e0fa988Tony Mantler                        // Loop around and wait again
83583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    }
83683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                }
83783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
83883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        }
83983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
84083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        @Override
84183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        public void run() {
84283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            synchronized (mLock) {
84383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                Looper.prepare();
84483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                mLooper = Looper.myLooper();
84583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                mLock.notifyAll();
84683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
84783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
84883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            Looper.loop();
84983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        }
850b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy
851b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public Looper getLooper() {
85283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            return mLooper;
85383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        }
854c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy    }
855899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki}
856