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