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;
29899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport 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;
406e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.email.activity.setup.AccountSettings;
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 */
65899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukipublic class NotificationController {
66b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private static final String LOG_TAG = LogTag.getLogTag();
67f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
68958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy    /** Reserved for {@link com.android.exchange.CalendarSyncEnabler} */
69958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy    @SuppressWarnings("unused")
70958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy    private static final int NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED = 2;
71308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3;
72308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_PASSWORD_EXPIRING = 4;
73308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    private static final int NOTIFICATION_ID_PASSWORD_EXPIRED = 5;
74d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
75f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private static final int NOTIFICATION_ID_BASE_MASK = 0xF0000000;
76d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000;
77f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private static final int NOTIFICATION_ID_BASE_SECURITY_NEEDED = 0x30000000;
78f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private static final int NOTIFICATION_ID_BASE_SECURITY_CHANGED = 0x40000000;
79899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
80e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    private static NotificationThread sNotificationThread;
81e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    private static Handler sNotificationHandler;
82899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private static NotificationController sInstance;
83899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private final Context mContext;
84899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    private final NotificationManager mNotificationManager;
8574e094834c6efd3260c717090a89232a892c411bMakoto Onuki    private final Clock mClock;
86f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    /** Maps account id to its observer */
87b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private final Map<Long, ContentObserver> mNotificationMap =
88b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            new HashMap<Long, ContentObserver>();
89e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    private ContentObserver mAccountObserver;
90899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
91899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /** Constructor */
92b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private NotificationController(Context context, Clock clock) {
93899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        mContext = context.getApplicationContext();
94e714bb9d153cfe13a7f0932e7d67ea08fa5a1d98Marc Blank        EmailContent.init(context);
95899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        mNotificationManager = (NotificationManager) context.getSystemService(
96899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki                Context.NOTIFICATION_SERVICE);
9774e094834c6efd3260c717090a89232a892c411bMakoto Onuki        mClock = clock;
98899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
99899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
100899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /** Singleton access */
101899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    public static synchronized NotificationController getInstance(Context context) {
102899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        if (sInstance == null) {
10374e094834c6efd3260c717090a89232a892c411bMakoto Onuki            sInstance = new NotificationController(context, Clock.INSTANCE);
104899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
105899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        return sInstance;
106899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
107899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
108899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /**
109d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank     * Return whether or not a notification, based on the passed-in id, needs to be "ongoing"
110d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank     * @param notificationId the notification id to check
111d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank     * @return whether or not the notification must be "ongoing"
112d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank     */
113b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private static boolean needsOngoingNotification(int notificationId) {
114d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank        // "Security needed" must be ongoing so that the user doesn't close it; otherwise, sync will
115d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank        // be prevented until a reboot.  Consider also doing this for password expired.
116f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        return (notificationId & NOTIFICATION_ID_BASE_MASK) == NOTIFICATION_ID_BASE_SECURITY_NEEDED;
117d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank    }
118d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank
119d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank    /**
120b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy     * Returns a {@link android.support.v4.app.NotificationCompat.Builder} for an event with the
121b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy     * given account. The account contains specific rules on ring tone usage and these will be used
122b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy     * to modify the notification behaviour.
1231ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     *
124f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * @param accountId The id of the account this notification is being built for.
125958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param ticker Text displayed when the notification is first shown. May be {@code null}.
126958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param title The first line of text. May NOT be {@code null}.
127958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param contentText The second line of text. May NOT be {@code null}.
128958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param intent The intent to start if the user clicks on the notification.
129958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param largeIcon A large icon. May be {@code null}
130b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy     * @param number A number to display using {@link Builder#setNumber(int)}. May be {@code null}.
131c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy     * @param enableAudio If {@code false}, do not play any sound. Otherwise, play sound according
132c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy     *        to the settings for the given account.
133958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @return A {@link Notification} that can be sent to the notification service.
1341ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
135b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private NotificationCompat.Builder createBaseAccountNotificationBuilder(long accountId,
136b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            String ticker, CharSequence title, String contentText, Intent intent, Bitmap largeIcon,
137d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank            Integer number, boolean enableAudio, boolean ongoing) {
1381ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // Pending Intent
139c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        PendingIntent pending = null;
140c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        if (intent != null) {
141958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            pending = PendingIntent.getActivity(
142958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                    mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
143c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler        }
1441ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
145958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        // NOTE: the ticker is not shown for notifications in the Holo UX
146b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
147958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setContentTitle(title)
148958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setContentText(contentText)
149958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setContentIntent(pending)
150958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setLargeIcon(largeIcon)
151958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setNumber(number == null ? 0 : number)
152b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                .setSmallIcon(R.drawable.stat_notify_email)
153958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                .setWhen(mClock.getTime())
154d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank                .setTicker(ticker)
155d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank                .setOngoing(ongoing);
156c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy
157c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        if (enableAudio) {
158f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            Account account = Account.restoreAccountWithId(mContext, accountId);
159c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy            setupSoundAndVibration(builder, account);
160c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        }
1611ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
162f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        return builder;
163958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy    }
1641ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
165958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy    /**
166958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Generic notifier for any account.  Uses notification rules from account.
167958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     *
168f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * @param accountId The account id this notification is being built for.
169958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param ticker Text displayed when the notification is first shown. May be {@code null}.
170958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param title The first line of text. May NOT be {@code null}.
171958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param contentText The second line of text. May NOT be {@code null}.
172958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param intent The intent to start if the user clicks on the notification.
173958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * @param notificationId The ID of the notification to register with the service.
174958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     */
175f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private void showNotification(long accountId, String ticker, String title,
176958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            String contentText, Intent intent, int notificationId) {
177b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        final NotificationCompat.Builder builder = createBaseAccountNotificationBuilder(accountId,
178b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                ticker, title, contentText, intent, null, null, true,
179f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                needsOngoingNotification(notificationId));
180b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        mNotificationManager.notify(notificationId, builder.build());
1811ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
1821ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
1831ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
18483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * Tells the notification controller if it should be watching for changes to the message table.
18583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * This is the main life cycle method for message notifications. When we stop observing
18683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * database changes, we save the state [e.g. message ID and count] of the most recent
18783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * notification shown to the user. And, when we start observing database changes, we restore
18883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * the saved state.
189899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     */
190b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    public void watchForMessages() {
1915701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        ensureHandlerExists();
19283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        // Run this on the message notification handler
193e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        sNotificationHandler.post(new Runnable() {
19483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            @Override
19583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            public void run() {
19683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                ContentResolver resolver = mContext.getContentResolver();
19783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
19883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                // otherwise, start new observers for all notified accounts
1995701e0a555a5c263862156c1291aa13b06850425Todd Kennedy                registerMessageNotification(Account.ACCOUNT_ID_COMBINED_VIEW);
200e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                // If we're already observing account changes, don't do anything else
201e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                if (mAccountObserver == null) {
202b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                    LogUtils.i(LOG_TAG, "Observing account changes for notifications");
203e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    mAccountObserver = new AccountContentObserver(sNotificationHandler, mContext);
204e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    resolver.registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver);
20583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                }
20683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
20783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        });
208899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
209899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
210899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    /**
2115701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     * Ensures the notification handler exists and is ready to handle requests.
2125701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     */
213a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
214a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    /**
215a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler     * TODO: Notifications jump around too much because we get too many content updates.
216a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler     * We should try to make the provider generate fewer updates instead.
217a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler     */
218a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
219a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static final int NOTIFICATION_DELAYED_MESSAGE = 0;
220a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static final long NOTIFICATION_DELAY = 15 * DateUtils.SECOND_IN_MILLIS;
221a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    // True if we're coalescing notification updates
222a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static boolean sNotificationDelayedMessagePending;
223a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    // True if accounts have changed and we need to refresh everything
224a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static boolean sRefreshAllNeeded;
225a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    // Set of accounts we need to regenerate notifications for
226a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static final HashSet<Long> sRefreshAccountSet = new HashSet<Long>();
227a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    // These should all be accessed on-thread, but just in case...
228a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static final Object sNotificationDelayedMessageLock = new Object();
229a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
2305701e0a555a5c263862156c1291aa13b06850425Todd Kennedy    private static synchronized void ensureHandlerExists() {
2315701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        if (sNotificationThread == null) {
2325701e0a555a5c263862156c1291aa13b06850425Todd Kennedy            sNotificationThread = new NotificationThread();
233a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            sNotificationHandler = new Handler(sNotificationThread.getLooper(),
234a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    new Handler.Callback() {
235a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        @Override
236a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        public boolean handleMessage(final android.os.Message message) {
237a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            /**
238a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                             * To reduce spamming the notifications, we quiesce updates for a few
239a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                             * seconds to batch them up, then handle them here.
240a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                             */
241a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            LogUtils.d(LOG_TAG, "Delayed notification processing");
242a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            synchronized (sNotificationDelayedMessageLock) {
243a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                sNotificationDelayedMessagePending = false;
244a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                final Context context = (Context)message.obj;
245a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                if (sRefreshAllNeeded) {
246a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                    sRefreshAllNeeded = false;
247a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                    refreshAllNotificationsInternal(context);
248a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                }
249a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                for (final Long accountId : sRefreshAccountSet) {
250a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                    refreshNotificationsForAccountInternal(context, accountId);
251a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                }
252a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                sRefreshAccountSet.clear();
253a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            }
254a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            return true;
255a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        }
256a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    });
2575701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        }
2585701e0a555a5c263862156c1291aa13b06850425Todd Kennedy    }
2595701e0a555a5c263862156c1291aa13b06850425Todd Kennedy
2605701e0a555a5c263862156c1291aa13b06850425Todd Kennedy    /**
261f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Registers an observer for changes to mailboxes in the given account.
26283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * NOTE: This must be called on the notification handler thread.
26383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * @param accountId The ID of the account to register the observer for. May be
2645701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     *                  {@link Account#ACCOUNT_ID_COMBINED_VIEW} to register observers for all
2655701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     *                  accounts that allow for user notification.
266899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki     */
267b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private void registerMessageNotification(final long accountId) {
26883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        ContentResolver resolver = mContext.getContentResolver();
2695701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
27083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            Cursor c = resolver.query(
27183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    Account.CONTENT_URI, EmailContent.ID_PROJECTION,
272b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                    null, null, null);
27383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            try {
27483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                while (c.moveToNext()) {
27583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
27683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    registerMessageNotification(id);
27783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                }
27883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            } finally {
27983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                c.close();
28083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
28183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        } else {
282aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            ContentObserver obs = mNotificationMap.get(accountId);
283aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            if (obs != null) return;  // we're already observing; nothing to do
284b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            LogUtils.i(LOG_TAG, "Registering for notifications for account " + accountId);
28583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            ContentObserver observer = new MessageContentObserver(
286f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    sNotificationHandler, mContext, accountId);
28783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            resolver.registerContentObserver(Message.NOTIFIER_URI, true, observer);
288aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            mNotificationMap.put(accountId, observer);
289e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // Now, ping the observer for any initial notifications
290aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            observer.onChange(true);
291c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        }
29283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    }
29383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
29483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    /**
29583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * Unregisters the observer for the given account. If the specified account does not have
296e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     * a registered observer, no action is performed. This will not clear any existing notification
297e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     * for the specified account. Use {@link NotificationManager#cancel(int)}.
29883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * NOTE: This must be called on the notification handler thread.
2995701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     * @param accountId The ID of the account to unregister from. To unregister all accounts that
3005701e0a555a5c263862156c1291aa13b06850425Todd Kennedy     *                  have observers, specify an ID of {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
30183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     */
302b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private void unregisterMessageNotification(final long accountId) {
30383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        ContentResolver resolver = mContext.getContentResolver();
3045701e0a555a5c263862156c1291aa13b06850425Todd Kennedy        if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
305b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            LogUtils.i(LOG_TAG, "Unregistering notifications for all accounts");
30683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            // cancel all existing message observers
307aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            for (ContentObserver observer : mNotificationMap.values()) {
30883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                resolver.unregisterContentObserver(observer);
30983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
31083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            mNotificationMap.clear();
311c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        } else {
312b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            LogUtils.i(LOG_TAG, "Unregistering notifications for account " + accountId);
313aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            ContentObserver observer = mNotificationMap.remove(accountId);
314aca94265813e72e692eace527f43eb4c02b09c76Marc Blank            if (observer != null) {
31583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                resolver.unregisterContentObserver(observer);
31683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
31783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        }
31883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    }
31983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
320f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public static final String EXTRA_ACCOUNT = "account";
321f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public static final String EXTRA_CONVERSATION = "conversationUri";
322f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public static final String EXTRA_FOLDER = "folder";
323f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
324b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    /** Sets up the notification's sound and vibration based upon account details. */
325b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private void setupSoundAndVibration(
326b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            NotificationCompat.Builder builder, Account account) {
327b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        String ringtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
328b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        boolean vibrate = false;
329f986f44ecac2b65fa50dcd8543945ff85d3b2245Mindy Pereira
330b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        // Use the Inbox notification preferences
331b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        final Cursor accountCursor = mContext.getContentResolver().query(EmailProvider.uiUri(
332b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                "uiaccount", account.mId), UIProvider.ACCOUNTS_PROJECTION, null, null, null);
333986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook
334b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        com.android.mail.providers.Account uiAccount = null;
335986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook        try {
336b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            if (accountCursor.moveToFirst()) {
337b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                uiAccount = new com.android.mail.providers.Account(accountCursor);
338986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook            }
339986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook        } finally {
340b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            accountCursor.close();
341986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook        }
342986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook
343b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        if (uiAccount != null) {
344b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            final Cursor folderCursor =
345b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                    mContext.getContentResolver().query(uiAccount.settings.defaultInbox,
346b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                            UIProvider.FOLDERS_PROJECTION, null, null, null);
347899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
348b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            if (folderCursor == null) {
34995bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                // This can happen when the notification is for the security policy notification
35095bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                // that happens before the account is setup
35195bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                LogUtils.w(LOG_TAG, "Null folder cursor for mailbox %s",
352b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                        uiAccount.settings.defaultInbox);
35395bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook            } else {
35495bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                Folder folder = null;
35595bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                try {
35695bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    if (folderCursor.moveToFirst()) {
35795bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                        folder = new Folder(folderCursor);
35895bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    }
35995bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                } finally {
36095bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    folderCursor.close();
361f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                }
362f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
36395bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                if (folder != null) {
36495bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    final FolderPreferences folderPreferences = new FolderPreferences(
36532614f3746f0fa8a09370672abaca68fa795fc33Tony Mantler                            mContext, uiAccount.getEmailAddress(), folder, true /* inbox */);
366899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
36795bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    ringtoneUri = folderPreferences.getNotificationRingtoneUri();
36895bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    vibrate = folderPreferences.isNotificationVibrateEnabled();
36995bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                } else {
37095bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                    LogUtils.e(LOG_TAG,
37195bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                            "Null folder for mailbox %s", uiAccount.settings.defaultInbox);
37295bb350f38d08beeabee68323b8514f3cbfd5db5Paul Westbrook                }
373b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            }
37474e094834c6efd3260c717090a89232a892c411bMakoto Onuki        } else {
375b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            LogUtils.e(LOG_TAG, "Null uiAccount for account id %d", account.mId);
37674e094834c6efd3260c717090a89232a892c411bMakoto Onuki        }
377899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
378958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        int defaults = Notification.DEFAULT_LIGHTS;
379f9997921cbea7feb13bbbd897e2f179494e5400eScott Kennedy        if (vibrate) {
380958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            defaults |= Notification.DEFAULT_VIBRATE;
381899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki        }
382899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki
383722b7bc29d3f5be029d21f5587b7bc675051073bScott Kennedy        builder.setSound(TextUtils.isEmpty(ringtoneUri) ? null : Uri.parse(ringtoneUri))
384958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            .setDefaults(defaults);
385899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki    }
386d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
387d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    /**
388958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Show (or update) a notification that the given attachment could not be forwarded. This
389958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * is a very unusual case, and perhaps we shouldn't even send a notification. For now,
390958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * it's helpful for debugging.
391958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     *
392c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
393d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank     */
394c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler    public void showDownloadForwardFailedNotification(Attachment attachment) {
395f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        Message message = Message.restoreMessageWithId(mContext, attachment.mMessageKey);
396f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (message == null) return;
397f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
398f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(mailbox.mAccountKey,
399d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank                mContext.getString(R.string.forward_download_failed_ticker),
400c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                mContext.getString(R.string.forward_download_failed_title),
401c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                attachment.mFileName,
402c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                null,
403c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                NOTIFICATION_ID_ATTACHMENT_WARNING);
404d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
405d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
406d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    /**
407958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Returns a notification ID for login failed notifications for the given account account.
408d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank     */
409b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy    private static int getLoginFailedNotificationId(long accountId) {
410d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int)accountId;
411d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
412d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
413c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler    /**
414958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Show (or update) a notification that there was a login failure for the given account.
415958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     *
416c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
417c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler     */
418d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    public void showLoginFailedNotification(long accountId) {
419dba0b20d955d88831ce94d96dbdadc49dba4761aMarc Blank        showLoginFailedNotification(accountId, null);
420dba0b20d955d88831ce94d96dbdadc49dba4761aMarc Blank    }
421dba0b20d955d88831ce94d96dbdadc49dba4761aMarc Blank
422dba0b20d955d88831ce94d96dbdadc49dba4761aMarc Blank    public void showLoginFailedNotification(long accountId, String reason) {
423d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        final Account account = Account.restoreAccountWithId(mContext, accountId);
424d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        if (account == null) return;
425f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        final Mailbox mailbox = Mailbox.restoreMailboxOfType(mContext, account.mId,
426f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                Mailbox.TYPE_INBOX);
427f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (mailbox == null) return;
428f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(mailbox.mAccountKey,
429d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank                mContext.getString(R.string.login_failed_ticker, account.mDisplayName),
430c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                mContext.getString(R.string.login_failed_title),
431c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler                account.getDisplayName(),
4321b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy                AccountSettings.createAccountSettingsIntent(accountId,
4331b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy                        account.mDisplayName, reason), getLoginFailedNotificationId(accountId));
434d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
435d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank
436958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy    /**
437958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Cancels the login failed notification for the given account.
438958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     */
439d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    public void cancelLoginFailedNotification(long accountId) {
440d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank        mNotificationManager.cancel(getLoginFailedNotificationId(accountId));
441d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank    }
442308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
443308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
444958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Show (or update) a notification that the user's password is expiring. The given account
445958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * is used to update the display text, but, all accounts share the same notification ID.
446308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     *
447958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
448308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
449308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void showPasswordExpiringNotification(long accountId) {
450308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Account account = Account.restoreAccountWithId(mContext, accountId);
451308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        if (account == null) return;
452958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy
453308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext,
454308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                accountId, false);
455958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        String accountName = account.getDisplayName();
456958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        String ticker =
457958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy            mContext.getString(R.string.password_expire_warning_ticker_fmt, accountName);
458958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        String title = mContext.getString(R.string.password_expire_warning_content_title);
459f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(accountId, ticker, title, accountName, intent,
460308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                NOTIFICATION_ID_PASSWORD_EXPIRING);
461308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
462308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
463308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
464958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Show (or update) a notification that the user's password has expired. The given account
465958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * is used to update the display text, but, all accounts share the same notification ID.
466308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     *
467958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
468308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
469308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void showPasswordExpiredNotification(long accountId) {
470308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Account account = Account.restoreAccountWithId(mContext, accountId);
471308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        if (account == null) return;
472958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy
473308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext,
474308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki                accountId, true);
475958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        String accountName = account.getDisplayName();
476308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        String ticker = mContext.getString(R.string.password_expired_ticker);
477958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        String title = mContext.getString(R.string.password_expired_content_title);
478f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(accountId, ticker, title, accountName, intent,
479958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy                NOTIFICATION_ID_PASSWORD_EXPIRED);
480308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
481308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
482308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
483958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy     * Cancels any password expire notifications [both expired & expiring].
484308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
485308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void cancelPasswordExpirationNotifications() {
48683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRING);
48783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRED);
488308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
489308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
490308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
491f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Show (or update) a security needed notification. If tapped, the user is taken to a
492f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * dialog asking whether he wants to update his settings.
493308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
494308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void showSecurityNeededNotification(Account account) {
495308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki        Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, account.mId, true);
496958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        String accountName = account.getDisplayName();
497958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy        String ticker =
498f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            mContext.getString(R.string.security_needed_ticker_fmt, accountName);
499f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        String title = mContext.getString(R.string.security_notification_content_update_title);
500f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(account.mId, ticker, title, accountName, intent,
501f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                (int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
502f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    }
503f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
504f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    /**
505f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Show (or update) a security changed notification. If tapped, the user is taken to the
506f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * account settings screen where he can view the list of enforced policies
507f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     */
508f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public void showSecurityChangedNotification(Account account) {
5091b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy        Intent intent = AccountSettings.createAccountSettingsIntent(account.mId, null, null);
510f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        String accountName = account.getDisplayName();
511f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        String ticker =
512f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            mContext.getString(R.string.security_changed_ticker_fmt, accountName);
513f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        String title = mContext.getString(R.string.security_notification_content_change_title);
514f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(account.mId, ticker, title, accountName, intent,
515f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                (int)(NOTIFICATION_ID_BASE_SECURITY_CHANGED + account.mId));
516308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
517308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki
518308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    /**
519f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Show (or update) a security unsupported notification. If tapped, the user is taken to the
520f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * account settings screen where he can view the list of unsupported policies
521f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     */
522f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public void showSecurityUnsupportedNotification(Account account) {
5231b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy        Intent intent = AccountSettings.createAccountSettingsIntent(account.mId, null, null);
524f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        String accountName = account.getDisplayName();
525f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        String ticker =
526f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            mContext.getString(R.string.security_unsupported_ticker_fmt, accountName);
527f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        String title = mContext.getString(R.string.security_notification_content_unsupported_title);
528f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        showNotification(account.mId, ticker, title, accountName, intent,
529f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                (int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
530f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank   }
531f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
532f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    /**
533f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Cancels all security needed notifications.
534308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki     */
535308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    public void cancelSecurityNeededNotification() {
536f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        EmailAsyncTask.runAsyncParallel(new Runnable() {
537f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            @Override
538f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            public void run() {
539f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
540f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        Account.ID_PROJECTION, null, null, null);
541f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                try {
542f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    while (c.moveToNext()) {
543f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        long id = c.getLong(Account.ID_PROJECTION_COLUMN);
544f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        mNotificationManager.cancel(
545f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                               (int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + id));
546f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    }
547f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                }
548f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                finally {
549f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    c.close();
550f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                }
551f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            }});
552308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki    }
553c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy
554c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy    /**
555d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy     * Cancels all notifications for the specified account id. This includes new mail notifications,
556d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy     * as well as special login/security notifications.
557d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy     */
558d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy    public static void cancelNotifications(final Context context, final Account account) {
559ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler        final EmailServiceUtils.EmailServiceInfo serviceInfo
560ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler                = EmailServiceUtils.getServiceInfoForAccount(context, account.mId);
561ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler        if (serviceInfo == null) {
562ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler            LogUtils.d(LOG_TAG, "Can't cancel notification for missing account %d", account.mId);
563ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler            return;
564ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler        }
565840408c41cfc95ffa491aaf11f1c3f4075eae9c9Tony Mantler        final android.accounts.Account notifAccount
566ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler                = account.getAccountManagerAccount(serviceInfo.accountType);
567ac1d3249fbb50714ad1bee5aec41968574befd90Tony Mantler
568840408c41cfc95ffa491aaf11f1c3f4075eae9c9Tony Mantler        NotificationUtils.clearAccountNotifications(context, notifAccount);
569d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy
570d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy        final NotificationManager notificationManager = getInstance(context).mNotificationManager;
571d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy
572d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy        notificationManager.cancel((int) (NOTIFICATION_ID_BASE_LOGIN_WARNING + account.mId));
573d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy        notificationManager.cancel((int) (NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
574d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy        notificationManager.cancel((int) (NOTIFICATION_ID_BASE_SECURITY_CHANGED + account.mId));
575d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy    }
576d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy
577a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static void refreshNotificationsForAccount(final Context context,
578a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            final long accountId) {
579a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        synchronized (sNotificationDelayedMessageLock) {
580a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            if (sNotificationDelayedMessagePending) {
581a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sRefreshAccountSet.add(accountId);
582a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            } else {
583a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                ensureHandlerExists();
584a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sNotificationHandler.sendMessageDelayed(
585a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        android.os.Message.obtain(sNotificationHandler,
586a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                NOTIFICATION_DELAYED_MESSAGE, context), NOTIFICATION_DELAY);
587a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sNotificationDelayedMessagePending = true;
588a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                refreshNotificationsForAccountInternal(context, accountId);
589a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            }
590a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
591a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    }
592a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
593a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static void refreshNotificationsForAccountInternal(final Context context,
594a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            final long accountId) {
595a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        final ContentResolver contentResolver = context.getContentResolver();
596a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
597a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        final Cursor accountCursor = contentResolver.query(
598a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                EmailProvider.uiUri("uiaccount", accountId), UIProvider.ACCOUNTS_PROJECTION,
599a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                null, null, null);
600a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
601a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        if (accountCursor == null) {
602a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            LogUtils.e(LOG_TAG, "Null account cursor for account id %d", accountId);
603a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            return;
604a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
605a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
606a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        com.android.mail.providers.Account account = null;
607a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        try {
608a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            if (accountCursor.moveToFirst()) {
609a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                account = new com.android.mail.providers.Account(accountCursor);
610a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            }
611a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        } finally {
612a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            accountCursor.close();
613a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
614a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
615a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        if (account == null) {
616a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            LogUtils.d(LOG_TAG, "Tried to create a notification for a missing account %d",
617a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    accountId);
618a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            return;
619a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
620a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
621a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        final Cursor mailboxCursor = contentResolver.query(
622a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                ContentUris.withAppendedId(EmailContent.MAILBOX_NOTIFICATION_URI, accountId),
623a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                null, null, null, null);
624a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        try {
625a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            while (mailboxCursor.moveToNext()) {
626a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                final long mailboxId =
627a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        mailboxCursor.getLong(EmailContent.NOTIFICATION_MAILBOX_ID_COLUMN);
628a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                if (mailboxId == 0) continue;
629a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
630a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                final int unreadCount = mailboxCursor.getInt(
631a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        EmailContent.NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN);
632a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                final int unseenCount = mailboxCursor.getInt(
633a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        EmailContent.NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN);
634a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
635a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                final Cursor folderCursor = contentResolver.query(
636a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        EmailProvider.uiUri("uifolder", mailboxId),
637a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        UIProvider.FOLDERS_PROJECTION, null, null, null);
638a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
639a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                if (folderCursor == null) {
640a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    LogUtils.e(LOG_TAG, "Null folder cursor for account %d, mailbox %d",
641a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                            accountId, mailboxId);
642a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    continue;
643a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                }
644a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
645a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                Folder folder = null;
646a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                try {
647a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    if (folderCursor.moveToFirst()) {
648a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        folder = new Folder(folderCursor);
649a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    } else {
650a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        LogUtils.e(LOG_TAG, "Empty folder cursor for account %d, mailbox %d",
651a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                accountId, mailboxId);
652a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        continue;
653a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    }
654a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                } finally {
655a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                    folderCursor.close();
656a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                }
657a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
658a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                LogUtils.d(LOG_TAG, "Changes to account " + account.name + ", folder: "
659a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        + folder.name + ", unreadCount: " + unreadCount + ", unseenCount: "
660a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        + unseenCount);
661a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
662a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                NotificationUtils.setNewEmailIndicator(context, unreadCount, unseenCount,
663a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        account, folder, true);
664a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            }
665a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        } finally {
666a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            mailboxCursor.close();
667a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
668a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    }
669a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
670a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static void refreshAllNotifications(final Context context) {
671a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        synchronized (sNotificationDelayedMessageLock) {
672a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            if (sNotificationDelayedMessagePending) {
673a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sRefreshAllNeeded = true;
674a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            } else {
675a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                ensureHandlerExists();
676a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sNotificationHandler.sendMessageDelayed(
677a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                        android.os.Message.obtain(sNotificationHandler,
678a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                                NOTIFICATION_DELAYED_MESSAGE, context), NOTIFICATION_DELAY);
679a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                sNotificationDelayedMessagePending = true;
680a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler                refreshAllNotificationsInternal(context);
681a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            }
682a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        }
683a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    }
684a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
685a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    private static void refreshAllNotificationsInternal(final Context context) {
686a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler        NotificationUtils.resendNotifications(context, false, null, null);
687a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler    }
688a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler
689d8302b01faa8fc7f175c93e90458aa84e8a663c7Scott Kennedy    /**
690c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy     * Observer invoked whenever a message we're notifying the user about changes.
691c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy     */
692c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy    private static class MessageContentObserver extends ContentObserver {
693c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        private final Context mContext;
69483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        private final long mAccountId;
695c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy
69683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        public MessageContentObserver(
697b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                final Handler handler, final Context context, final long accountId) {
69883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            super(handler);
699c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy            mContext = context;
700c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy            mAccountId = accountId;
701c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        }
702c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy
703c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy        @Override
704b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public void onChange(final boolean selfChange) {
705a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            refreshNotificationsForAccount(mContext, mAccountId);
706f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        }
707f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    }
708f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
709e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    /**
710e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     * Observer invoked whenever an account is modified. This could mean the user changed the
711e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     * notification settings.
712e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy     */
713e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    private static class AccountContentObserver extends ContentObserver {
714e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        private final Context mContext;
715b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public AccountContentObserver(final Handler handler, final Context context) {
716e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            super(handler);
717e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            mContext = context;
718e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        }
719e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy
720e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        @Override
721b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public void onChange(final boolean selfChange) {
722e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            final ContentResolver resolver = mContext.getContentResolver();
723f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            final Cursor c = resolver.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
724b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                null, null, null);
725b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            final Set<Long> newAccountList = new HashSet<Long>();
726b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            final Set<Long> removedAccountList = new HashSet<Long>();
727e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            if (c == null) {
728e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                // Suspender time ... theoretically, this will never happen
729b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy                LogUtils.wtf(LOG_TAG, "#onChange(); NULL response for account id query");
730e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                return;
731e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
732e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            try {
733e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                while (c.moveToNext()) {
734e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    long accountId = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
735e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    newAccountList.add(accountId);
736e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                }
737e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            } finally {
7381b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy                c.close();
739e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
740e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // NOTE: Looping over three lists is not necessarily the most efficient. However, the
741e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // account lists are going to be very small, so, this will not be necessarily bad.
742e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // Cycle through existing notification list and adjust as necessary
743b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            for (final long accountId : sInstance.mNotificationMap.keySet()) {
744e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                if (!newAccountList.remove(accountId)) {
745e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    // account id not in the current set of notifiable accounts
746e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                    removedAccountList.add(accountId);
747e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                }
748e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
749e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // A new account was added to the notification list
750b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            for (final long accountId : newAccountList) {
751e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                sInstance.registerMessageNotification(accountId);
752e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
753e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            // An account was removed from the notification list
754b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy            for (final long accountId : removedAccountList) {
755e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy                sInstance.unregisterMessageNotification(accountId);
756e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy            }
757b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy
758a2b82c1a9297b15d8df2ae627b8059ca5bcb34aeTony Mantler            refreshAllNotifications(mContext);
759e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy        }
760e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy    }
761e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy
76283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    /**
76383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     * Thread to handle all notification actions through its own {@link Looper}.
76483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy     */
76583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy    private static class NotificationThread implements Runnable {
76683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        /** Lock to ensure proper initialization */
76783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        private final Object mLock = new Object();
76883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        /** The {@link Looper} that handles messages for this thread */
76983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        private Looper mLooper;
77083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
771b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public NotificationThread() {
77283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            new Thread(null, this, "EmailNotification").start();
77383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            synchronized (mLock) {
77483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                while (mLooper == null) {
77583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    try {
77683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                        mLock.wait();
77783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    } catch (InterruptedException ex) {
778f086deac70a5ee893ebd14be9d0502132e0fa988Tony Mantler                        // Loop around and wait again
77983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                    }
78083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                }
78183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
78283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        }
78383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy
78483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        @Override
78583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        public void run() {
78683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            synchronized (mLock) {
78783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                Looper.prepare();
78883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                mLooper = Looper.myLooper();
78983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy                mLock.notifyAll();
79083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            }
79183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
79283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            Looper.loop();
79383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        }
794b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy
795b34608228f0b55e401415b67b8150ca9e00cee7dScott Kennedy        public Looper getLooper() {
79683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy            return mLooper;
79783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy        }
798c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy    }
799899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki}
800