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; 25aca94265813e72e692eace527f43eb4c02b09c76Marc Blankimport android.content.ContentValues; 26899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.content.Context; 27d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blankimport android.content.Intent; 28f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.content.res.Resources; 29c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedyimport android.database.ContentObserver; 3083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedyimport android.database.Cursor; 31899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.graphics.Bitmap; 3274e094834c6efd3260c717090a89232a892c411bMakoto Onukiimport android.graphics.BitmapFactory; 33899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.media.AudioManager; 34899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.net.Uri; 35bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.os.Build; 36c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedyimport android.os.Handler; 3783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedyimport android.os.Looper; 3883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedyimport android.os.Process; 3974e094834c6efd3260c717090a89232a892c411bMakoto Onukiimport android.text.SpannableString; 40899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport android.text.TextUtils; 41f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.text.style.TextAppearanceSpan; 4283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedyimport android.util.Log; 43899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 446e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.email.activity.ContactStatusLoader; 45bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport com.android.email.activity.Welcome; 466e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.email.activity.setup.AccountSecurity; 476e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.email.activity.setup.AccountSettings; 486e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.Logging; 496e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.mail.Address; 506e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.Account; 516e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.EmailContent; 52bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport com.android.emailcommon.provider.EmailContent.AccountColumns; 536e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment; 546e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.EmailContent.MailboxColumns; 556e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.EmailContent.Message; 56bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport com.android.emailcommon.provider.EmailContent.MessageColumns; 576e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.provider.Mailbox; 586e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.android.emailcommon.utility.Utility; 596e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.google.common.annotations.VisibleForTesting; 606e418aa41a17136be0dddb816d843428a0a1e722Marc Blank 61c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedyimport java.util.HashMap; 62e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedyimport java.util.HashSet; 63c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy 64899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki/** 65899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * Class that manages notifications. 66899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki */ 67899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukipublic class NotificationController { 68bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private static final int NOTIFICATION_ID_SECURITY_NEEDED = 1; 69958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy /** Reserved for {@link com.android.exchange.CalendarSyncEnabler} */ 70958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy @SuppressWarnings("unused") 71958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy private static final int NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED = 2; 72308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki private static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3; 73308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki private static final int NOTIFICATION_ID_PASSWORD_EXPIRING = 4; 74308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki private static final int NOTIFICATION_ID_PASSWORD_EXPIRED = 5; 75d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank 76d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank private static final int NOTIFICATION_ID_BASE_NEW_MESSAGES = 0x10000000; 77d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000; 78899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 7983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy /** Selection to retrieve accounts that should we notify user for changes */ 8083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy private final static String NOTIFIED_ACCOUNT_SELECTION = 8183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy Account.FLAGS + "&" + Account.FLAGS_NOTIFY_NEW_MAIL + " != 0"; 8283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy 83e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy private static NotificationThread sNotificationThread; 84e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy private static Handler sNotificationHandler; 85899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki private static NotificationController sInstance; 86899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki private final Context mContext; 87899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki private final NotificationManager mNotificationManager; 88899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki private final AudioManager mAudioManager; 89c1c3b6f21ecddbb237195caedc36af442ebf00e8Andy Stadler private final Bitmap mGenericSenderIcon; 90f13fee5d78e8975e05a7379eb7972282242c68b7Ben Komalo private final Bitmap mGenericMultipleSenderIcon; 9174e094834c6efd3260c717090a89232a892c411bMakoto Onuki private final Clock mClock; 92bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // TODO We're maintaining all of our structures based upon the account ID. This is fine 93bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // for now since the assumption is that we only ever look for changes in an account's 94bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // INBOX. We should adjust our logic to use the mailbox ID instead. 95bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook /** Maps account id to the message data */ 96aca94265813e72e692eace527f43eb4c02b09c76Marc Blank private final HashMap<Long, ContentObserver> mNotificationMap; 97e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy private ContentObserver mAccountObserver; 98bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook /** 99bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Suspend notifications for this account. If {@link Account#NO_ACCOUNT}, no 100bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * account notifications are suspended. If {@link Account#ACCOUNT_ID_COMBINED_VIEW}, 101bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * notifications for all accounts are suspended. 102bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */ 103bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private long mSuspendAccountId = Account.NO_ACCOUNT; 104899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 10523a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo /** 10623a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo * Timestamp indicating when the last message notification sound was played. 10723a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo * Used for throttling. 10823a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo */ 10923a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo private long mLastMessageNotifyTime; 11023a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo 11123a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo /** 11223a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo * Minimum interval between notification sounds. 11323a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo * Since a long sync (either on account setup or after a long period of being offline) can cause 11423a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo * several notifications consecutively, it can be pretty overwhelming to get a barrage of 11523a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo * notification sounds. Throttle them using this value. 11623a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo */ 11748a3a1c51c11e3a752d7178e5c12c6caec842526Ben Komalo private static final long MIN_SOUND_INTERVAL_MS = 15 * 1000; // 15 seconds 11823a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo 119bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private static boolean isRunningJellybeanOrLater() { 120bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; 121bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 122bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 123899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki /** Constructor */ 124958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy @VisibleForTesting 125958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy NotificationController(Context context, Clock clock) { 126899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki mContext = context.getApplicationContext(); 127899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki mNotificationManager = (NotificationManager) context.getSystemService( 128899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki Context.NOTIFICATION_SERVICE); 129899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 130c1c3b6f21ecddbb237195caedc36af442ebf00e8Andy Stadler mGenericSenderIcon = BitmapFactory.decodeResource(mContext.getResources(), 131c1c3b6f21ecddbb237195caedc36af442ebf00e8Andy Stadler R.drawable.ic_contact_picture); 132f13fee5d78e8975e05a7379eb7972282242c68b7Ben Komalo mGenericMultipleSenderIcon = BitmapFactory.decodeResource(mContext.getResources(), 133f13fee5d78e8975e05a7379eb7972282242c68b7Ben Komalo R.drawable.ic_notification_multiple_mail_holo_dark); 13474e094834c6efd3260c717090a89232a892c411bMakoto Onuki mClock = clock; 135aca94265813e72e692eace527f43eb4c02b09c76Marc Blank mNotificationMap = new HashMap<Long, ContentObserver>(); 136899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 137899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 138899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki /** Singleton access */ 139899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki public static synchronized NotificationController getInstance(Context context) { 140899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki if (sInstance == null) { 14174e094834c6efd3260c717090a89232a892c411bMakoto Onuki sInstance = new NotificationController(context, Clock.INSTANCE); 142899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 143899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki return sInstance; 144899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 145899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 146899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki /** 147d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank * Return whether or not a notification, based on the passed-in id, needs to be "ongoing" 148d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank * @param notificationId the notification id to check 149d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank * @return whether or not the notification must be "ongoing" 150d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank */ 151d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank private boolean needsOngoingNotification(int notificationId) { 152d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank // "Security needed" must be ongoing so that the user doesn't close it; otherwise, sync will 153d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank // be prevented until a reboot. Consider also doing this for password expired. 154bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return notificationId == NOTIFICATION_ID_SECURITY_NEEDED; 155d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank } 156d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank 157d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank /** 158bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Returns a {@link Notification.Builder} for an event with the given account. The account 159f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * contains specific rules on ring tone usage and these will be used to modify the notification 160958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * behaviour. 1611ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler * 162bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * @param account The account this notification is being built for. 163958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param ticker Text displayed when the notification is first shown. May be {@code null}. 164958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param title The first line of text. May NOT be {@code null}. 165958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param contentText The second line of text. May NOT be {@code null}. 166958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param intent The intent to start if the user clicks on the notification. 167958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param largeIcon A large icon. May be {@code null} 168dfdc8b6da3cc074c97d53a597855aeaa3f039a68Todd Kennedy * @param number A number to display using {@link Builder#setNumber(int)}. May 169958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * be {@code null}. 170c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy * @param enableAudio If {@code false}, do not play any sound. Otherwise, play sound according 171c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy * to the settings for the given account. 172958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @return A {@link Notification} that can be sent to the notification service. 1731ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler */ 174bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private Notification.Builder createBaseAccountNotificationBuilder(Account account, 175bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook String ticker, CharSequence title, String contentText, Intent intent, Bitmap largeIcon, 176d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank Integer number, boolean enableAudio, boolean ongoing) { 1771ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler // Pending Intent 178c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler PendingIntent pending = null; 179c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler if (intent != null) { 180958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy pending = PendingIntent.getActivity( 181958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 182c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler } 1831ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 184958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy // NOTE: the ticker is not shown for notifications in the Holo UX 185f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final Notification.Builder builder = new Notification.Builder(mContext) 186958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy .setContentTitle(title) 187958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy .setContentText(contentText) 188958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy .setContentIntent(pending) 189958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy .setLargeIcon(largeIcon) 190958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy .setNumber(number == null ? 0 : number) 191958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy .setSmallIcon(R.drawable.stat_notify_email_generic) 192958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy .setWhen(mClock.getTime()) 193d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank .setTicker(ticker) 194d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank .setOngoing(ongoing); 195c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy 196c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy if (enableAudio) { 197c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy setupSoundAndVibration(builder, account); 198c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy } 1991ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 200f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return builder; 201958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy } 2021ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 203958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy /** 204958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Generic notifier for any account. Uses notification rules from account. 205958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * 206bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * @param account The account this notification is being built for. 207958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param ticker Text displayed when the notification is first shown. May be {@code null}. 208958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param title The first line of text. May NOT be {@code null}. 209958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param contentText The second line of text. May NOT be {@code null}. 210958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param intent The intent to start if the user clicks on the notification. 211958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * @param notificationId The ID of the notification to register with the service. 212958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy */ 213bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private void showAccountNotification(Account account, String ticker, String title, 214958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy String contentText, Intent intent, int notificationId) { 215bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Notification.Builder builder = createBaseAccountNotificationBuilder(account, ticker, title, 216bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook contentText, intent, null, null, true, needsOngoingNotification(notificationId)); 217f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank mNotificationManager.notify(notificationId, builder.getNotification()); 2181ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler } 2191ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler 2201ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler /** 221958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Returns a notification ID for new message notifications for the given account. 222899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki */ 223bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private int getNewMessageNotificationId(long accountId) { 224958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy // We assume accountId will always be less than 0x0FFFFFFF; is there a better way? 225bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return (int) (NOTIFICATION_ID_BASE_NEW_MESSAGES + accountId); 226899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 227899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 228899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki /** 22983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * Tells the notification controller if it should be watching for changes to the message table. 23083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * This is the main life cycle method for message notifications. When we stop observing 23183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * database changes, we save the state [e.g. message ID and count] of the most recent 23283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * notification shown to the user. And, when we start observing database changes, we restore 23383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * the saved state. 23483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * @param watch If {@code true}, we register observers for all accounts whose settings have 2355701e0a555a5c263862156c1291aa13b06850425Todd Kennedy * notifications enabled. Otherwise, all observers are unregistered. 236899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki */ 23783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy public void watchForMessages(final boolean watch) { 238bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (Email.DEBUG) { 239bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Log.i(Logging.LOG_TAG, "Notifications being toggled: " + watch); 240eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo } 24183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy // Don't create the thread if we're only going to stop watching 2425701e0a555a5c263862156c1291aa13b06850425Todd Kennedy if (!watch && sNotificationThread == null) return; 24383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy 2445701e0a555a5c263862156c1291aa13b06850425Todd Kennedy ensureHandlerExists(); 24583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy // Run this on the message notification handler 246e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy sNotificationHandler.post(new Runnable() { 24783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy @Override 24883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy public void run() { 24983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy ContentResolver resolver = mContext.getContentResolver(); 25083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy if (!watch) { 2515701e0a555a5c263862156c1291aa13b06850425Todd Kennedy unregisterMessageNotification(Account.ACCOUNT_ID_COMBINED_VIEW); 252e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy if (mAccountObserver != null) { 253e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy resolver.unregisterContentObserver(mAccountObserver); 254e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy mAccountObserver = null; 255e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 25683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy 25783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy // tear down the event loop 258e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy sNotificationThread.quit(); 259e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy sNotificationThread = null; 26083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy return; 261c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy } 26283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy 26383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy // otherwise, start new observers for all notified accounts 2645701e0a555a5c263862156c1291aa13b06850425Todd Kennedy registerMessageNotification(Account.ACCOUNT_ID_COMBINED_VIEW); 265e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy // If we're already observing account changes, don't do anything else 266e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy if (mAccountObserver == null) { 267bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (Email.DEBUG) { 268eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo Log.i(Logging.LOG_TAG, "Observing account changes for notifications"); 269eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo } 270e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy mAccountObserver = new AccountContentObserver(sNotificationHandler, mContext); 271e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy resolver.registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver); 27283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 27383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 27483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy }); 275899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 276899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 277899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki /** 278bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Temporarily suspend a single account from receiving notifications. NOTE: only a single 279bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * account may ever be suspended at a time. So, if this method is invoked a second time, 280bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * notifications for the previously suspended account will automatically be re-activated. 281bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * @param suspend If {@code true}, suspend notifications for the given account. Otherwise, 282bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * re-activate notifications for the previously suspended account. 283bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * @param accountId The ID of the account. If this is the special account ID 284bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * {@link Account#ACCOUNT_ID_COMBINED_VIEW}, notifications for all accounts are 285bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * suspended. If {@code suspend} is {@code false}, the account ID is ignored. 286bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */ 287bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook public void suspendMessageNotification(boolean suspend, long accountId) { 288bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (mSuspendAccountId != Account.NO_ACCOUNT) { 289bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // we're already suspending an account; un-suspend it 290bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mSuspendAccountId = Account.NO_ACCOUNT; 291bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 292bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (suspend && accountId != Account.NO_ACCOUNT && accountId > 0L) { 293bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mSuspendAccountId = accountId; 294bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 295bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Only go onto the notification handler if we really, absolutely need to 296bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook ensureHandlerExists(); 297bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook sNotificationHandler.post(new Runnable() { 298bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook @Override 299bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook public void run() { 300bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook for (long accountId : mNotificationMap.keySet()) { 301bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mNotificationManager.cancel(getNewMessageNotificationId(accountId)); 302bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 303bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 304bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook }); 305bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } else { 306bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mNotificationManager.cancel(getNewMessageNotificationId(accountId)); 307bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 308bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 309bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 310bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 311bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook /** 3125701e0a555a5c263862156c1291aa13b06850425Todd Kennedy * Ensures the notification handler exists and is ready to handle requests. 3135701e0a555a5c263862156c1291aa13b06850425Todd Kennedy */ 3145701e0a555a5c263862156c1291aa13b06850425Todd Kennedy private static synchronized void ensureHandlerExists() { 3155701e0a555a5c263862156c1291aa13b06850425Todd Kennedy if (sNotificationThread == null) { 3165701e0a555a5c263862156c1291aa13b06850425Todd Kennedy sNotificationThread = new NotificationThread(); 3175701e0a555a5c263862156c1291aa13b06850425Todd Kennedy sNotificationHandler = new Handler(sNotificationThread.getLooper()); 3185701e0a555a5c263862156c1291aa13b06850425Todd Kennedy } 3195701e0a555a5c263862156c1291aa13b06850425Todd Kennedy } 3205701e0a555a5c263862156c1291aa13b06850425Todd Kennedy 3215701e0a555a5c263862156c1291aa13b06850425Todd Kennedy /** 322bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Registers an observer for changes to the INBOX for the given account. Since accounts 323bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * may only have a single INBOX, we will never have more than one observer for an account. 32483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * NOTE: This must be called on the notification handler thread. 32583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * @param accountId The ID of the account to register the observer for. May be 3265701e0a555a5c263862156c1291aa13b06850425Todd Kennedy * {@link Account#ACCOUNT_ID_COMBINED_VIEW} to register observers for all 3275701e0a555a5c263862156c1291aa13b06850425Todd Kennedy * accounts that allow for user notification. 328899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki */ 32983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy private void registerMessageNotification(long accountId) { 33083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy ContentResolver resolver = mContext.getContentResolver(); 3315701e0a555a5c263862156c1291aa13b06850425Todd Kennedy if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 33283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy Cursor c = resolver.query( 33383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy Account.CONTENT_URI, EmailContent.ID_PROJECTION, 33483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy NOTIFIED_ACCOUNT_SELECTION, null, null); 33583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy try { 33683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy while (c.moveToNext()) { 33783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN); 33883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy registerMessageNotification(id); 33983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 34083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } finally { 34183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy c.close(); 34283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 34383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } else { 344aca94265813e72e692eace527f43eb4c02b09c76Marc Blank ContentObserver obs = mNotificationMap.get(accountId); 345aca94265813e72e692eace527f43eb4c02b09c76Marc Blank if (obs != null) return; // we're already observing; nothing to do 346bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 347bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Mailbox mailbox = Mailbox.restoreMailboxOfType(mContext, accountId, Mailbox.TYPE_INBOX); 348bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (mailbox == null) { 349bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Log.w(Logging.LOG_TAG, "Could not load INBOX for account id: " + accountId); 350bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return; 351bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 352bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (Email.DEBUG) { 353eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo Log.i(Logging.LOG_TAG, "Registering for notifications for account " + accountId); 354eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo } 35583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy ContentObserver observer = new MessageContentObserver( 356bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook sNotificationHandler, mContext, mailbox.mId, accountId); 35783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy resolver.registerContentObserver(Message.NOTIFIER_URI, true, observer); 358aca94265813e72e692eace527f43eb4c02b09c76Marc Blank mNotificationMap.put(accountId, observer); 359e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy // Now, ping the observer for any initial notifications 360aca94265813e72e692eace527f43eb4c02b09c76Marc Blank observer.onChange(true); 361c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy } 36283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 36383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy 36483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy /** 36583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * Unregisters the observer for the given account. If the specified account does not have 366e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy * a registered observer, no action is performed. This will not clear any existing notification 367e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy * for the specified account. Use {@link NotificationManager#cancel(int)}. 36883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * NOTE: This must be called on the notification handler thread. 3695701e0a555a5c263862156c1291aa13b06850425Todd Kennedy * @param accountId The ID of the account to unregister from. To unregister all accounts that 3705701e0a555a5c263862156c1291aa13b06850425Todd Kennedy * have observers, specify an ID of {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 37183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy */ 37283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy private void unregisterMessageNotification(long accountId) { 37383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy ContentResolver resolver = mContext.getContentResolver(); 3745701e0a555a5c263862156c1291aa13b06850425Todd Kennedy if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 375bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (Email.DEBUG) { 376eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo Log.i(Logging.LOG_TAG, "Unregistering notifications for all accounts"); 377eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo } 37883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy // cancel all existing message observers 379aca94265813e72e692eace527f43eb4c02b09c76Marc Blank for (ContentObserver observer : mNotificationMap.values()) { 38083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy resolver.unregisterContentObserver(observer); 38183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 38283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy mNotificationMap.clear(); 383c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy } else { 384bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (Email.DEBUG) { 385eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo Log.i(Logging.LOG_TAG, "Unregistering notifications for account " + accountId); 386eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo } 387aca94265813e72e692eace527f43eb4c02b09c76Marc Blank ContentObserver observer = mNotificationMap.remove(accountId); 388aca94265813e72e692eace527f43eb4c02b09c76Marc Blank if (observer != null) { 38983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy resolver.unregisterContentObserver(observer); 39083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 39183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 39283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 39383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy 39483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy /** 395958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Returns a picture of the sender of the given message. If no picture is available, returns 396958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * {@code null}. 397899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * 398958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS) 399899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki */ 400899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki private Bitmap getSenderPhoto(Message message) { 401899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki Address sender = Address.unpackFirst(message.mFrom); 402899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki if (sender == null) { 403899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki return null; 404899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 405899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki String email = sender.getAddress(); 406899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki if (TextUtils.isEmpty(email)) { 407899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki return null; 408899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 409f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Bitmap photo = ContactStatusLoader.getContactInfo(mContext, email).mPhoto; 410f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 411f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (photo != null) { 412f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final Resources res = mContext.getResources(); 413f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final int idealIconHeight = 414f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height); 415f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final int idealIconWidth = 416f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width); 417f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 418f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (photo.getHeight() < idealIconHeight) { 419f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // We should scale this image to fit the intended size 420f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank photo = Bitmap.createScaledBitmap( 421f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank photo, idealIconWidth, idealIconHeight, true); 422f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 423f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 424f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return photo; 425899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 426899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 427dd6e6b850462fd8617a73dd40b5e7d32f0aaf5edMakoto Onuki /** 428958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Returns a "new message" notification for the given account. 429899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki * 430958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS) 431899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki */ 432958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy @VisibleForTesting 433bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Notification createNewMessageNotification(long accountId, long mailboxId, Cursor messageCursor, 434bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook long newestMessageId, int unseenMessageCount, int unreadCount) { 435bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final Account account = Account.restoreAccountWithId(mContext, accountId); 436899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki if (account == null) { 437899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki return null; 438899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 439899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki // Get the latest message 440bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final Message message = Message.restoreMessageWithId(mContext, newestMessageId); 441899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki if (message == null) { 442899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki return null; // no message found??? 443899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 444899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 44543c455eb266125a8e09a98e38d7639a865f52d42Makoto Onuki String senderName = Address.toFriendly(Address.unpack(message.mFrom)); 44643c455eb266125a8e09a98e38d7639a865f52d42Makoto Onuki if (senderName == null) { 44743c455eb266125a8e09a98e38d7639a865f52d42Makoto Onuki senderName = ""; // Happens when a message has no from. 44843c455eb266125a8e09a98e38d7639a865f52d42Makoto Onuki } 4496f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo final boolean multipleUnseen = unseenMessageCount > 1; 450f13fee5d78e8975e05a7379eb7972282242c68b7Ben Komalo final Bitmap senderPhoto = multipleUnseen 451f13fee5d78e8975e05a7379eb7972282242c68b7Ben Komalo ? mGenericMultipleSenderIcon 452f13fee5d78e8975e05a7379eb7972282242c68b7Ben Komalo : getSenderPhoto(message); 4536f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo final SpannableString title = getNewMessageTitle(senderName, unseenMessageCount); 4546f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo // TODO: add in display name on the second line for the text, once framework supports 4556f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo // multiline texts. 4566f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo final String text = multipleUnseen 457bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook ? account.mDisplayName 4586f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo : message.mSubject; 459958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy final Bitmap largeIcon = senderPhoto != null ? senderPhoto : mGenericSenderIcon; 4606f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo final Integer number = unreadCount > 1 ? unreadCount : null; 461986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook final Intent intent; 462bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (unseenMessageCount > 1) { 463bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook intent = Welcome.createOpenAccountInboxIntent(mContext, accountId); 464986881efe6029ad4382371e2da4e597312eda51cPaul Westbrook } else { 465bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook intent = Welcome.createOpenMessageIntent( 466d5f267ed250e7c3c97ca5e6ac1902340771d168cMark Wei mContext, accountId, mailboxId, newestMessageId); 467f4378923488abcb4422253cf2e7a9c9d244d1b96Todd Kennedy } 46865432baf25d06b58a9b50d3df9952c4eedc9b8a5Paul Westbrook intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | 46965432baf25d06b58a9b50d3df9952c4eedc9b8a5Paul Westbrook Intent.FLAG_ACTIVITY_TASK_ON_HOME); 47023a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo long now = mClock.getTime(); 47148a3a1c51c11e3a752d7178e5c12c6caec842526Ben Komalo boolean enableAudio = (now - mLastMessageNotifyTime) > MIN_SOUND_INTERVAL_MS; 472f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final Notification.Builder builder = createBaseAccountNotificationBuilder( 473bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook account, title.toString(), title, text, 474d9b2a8f237492951f71abeb8f2ce359311862f21Marc Blank intent, largeIcon, number, enableAudio, false); 475bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (isRunningJellybeanOrLater()) { 476f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // For a new-style notification 477f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (multipleUnseen) { 478bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (messageCursor != null) { 479bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final int maxNumDigestItems = mContext.getResources().getInteger( 480bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook R.integer.max_num_notification_digest_items); 481bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // The body of the notification is the account name, or the label name. 482bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook builder.setSubText(text); 483bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 484bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Notification.InboxStyle digest = new Notification.InboxStyle(builder); 485bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 486bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook digest.setBigContentTitle(title); 487bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 488bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook int numDigestItems = 0; 489bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // We can assume that the current position of the cursor is on the 490bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // newest message 491bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook do { 492bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final long messageId = 493bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook messageCursor.getLong(EmailContent.ID_PROJECTION_COLUMN); 494bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 495bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Get the latest message 496bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final Message digestMessage = 497bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Message.restoreMessageWithId(mContext, messageId); 498bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (digestMessage != null) { 499bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final CharSequence digestLine = 500bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook getSingleMessageInboxLine(mContext, digestMessage); 501bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook digest.addLine(digestLine); 502bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook numDigestItems++; 503bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 504bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } while (numDigestItems <= maxNumDigestItems && messageCursor.moveToNext()); 505bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 506bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // We want to clear the content text in this case. The content text would have 507bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // been set in createBaseAccountNotificationBuilder, but since the same string 508bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // was set in as the subtext, we don't want to show a duplicate string. 509bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook builder.setContentText(null); 510f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 511f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } else { 512f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // The notification content will be the subject of the conversation. 513f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank builder.setContentText(getSingleMessageLittleText(mContext, message.mSubject)); 514f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 515f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // The notification subtext will be the subject of the conversation for inbox 516f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // notifications, or will based on the the label name for user label notifications. 517f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank builder.setSubText(account.mDisplayName); 518f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 519f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final Notification.BigTextStyle bigText = new Notification.BigTextStyle(builder); 520f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank bigText.bigText(getSingleMessageBigText(mContext, message)); 521f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 522f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 523f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 52423a4b15e08bfe57be3242790d1a92db8dd4b9980Ben Komalo mLastMessageNotifyTime = now; 525f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return builder.getNotification(); 526f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 527f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 528f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank /** 529f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * Sets the bigtext for a notification for a single new conversation 530f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * @param context 531f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * @param message New message that triggered the notification. 532f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * @return a {@link CharSequence} suitable for use in {@link Notification.BigTextStyle} 533f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank */ 534f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank private static CharSequence getSingleMessageInboxLine(Context context, Message message) { 535f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String subject = message.mSubject; 536f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String snippet = message.mSnippet; 537f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String senders = Address.toFriendly(Address.unpack(message.mFrom)); 538f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 539f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String subjectSnippet = !TextUtils.isEmpty(subject) ? subject : snippet; 540f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 541f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final TextAppearanceSpan notificationPrimarySpan = 542f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank new TextAppearanceSpan(context, R.style.NotificationPrimaryText); 543f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 544f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (TextUtils.isEmpty(senders)) { 545f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // If the senders are empty, just use the subject/snippet. 546f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return subjectSnippet; 547f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 548f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank else if (TextUtils.isEmpty(subjectSnippet)) { 549f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // If the subject/snippet is empty, just use the senders. 550f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final SpannableString spannableString = new SpannableString(senders); 551f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank spannableString.setSpan(notificationPrimarySpan, 0, senders.length(), 0); 552f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 553f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return spannableString; 554f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } else { 555f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String formatString = context.getResources().getString( 556f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank R.string.multiple_new_message_notification_item); 557f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final TextAppearanceSpan notificationSecondarySpan = 558f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank new TextAppearanceSpan(context, R.style.NotificationSecondaryText); 559f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 560f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String instantiatedString = String.format(formatString, senders, subjectSnippet); 561f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 562f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final SpannableString spannableString = new SpannableString(instantiatedString); 563f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 564f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final boolean isOrderReversed = formatString.indexOf("%2$s") < 565f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank formatString.indexOf("%1$s"); 566f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final int primaryOffset = 567f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank (isOrderReversed ? instantiatedString.lastIndexOf(senders) : 568f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank instantiatedString.indexOf(senders)); 569f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final int secondaryOffset = 570f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank (isOrderReversed ? instantiatedString.lastIndexOf(subjectSnippet) : 571f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank instantiatedString.indexOf(subjectSnippet)); 572f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank spannableString.setSpan(notificationPrimarySpan, 573f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank primaryOffset, primaryOffset + senders.length(), 0); 574f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank spannableString.setSpan(notificationSecondarySpan, 575f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank secondaryOffset, secondaryOffset + subjectSnippet.length(), 0); 576f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return spannableString; 577f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 578f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 579f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 580f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank /** 581f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * Sets the bigtext for a notification for a single new conversation 582f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * @param context 583f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * @param subject Subject of the new message that triggered the notification 584f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * @return a {@link CharSequence} suitable for use in {@link Notification.ContentText} 585f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank */ 586f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank private static CharSequence getSingleMessageLittleText(Context context, String subject) { 587f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (subject == null) { 588f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return null; 589f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 590f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan( 591f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank context, R.style.NotificationPrimaryText); 592f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 593f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final SpannableString spannableString = new SpannableString(subject); 594f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0); 595f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 596f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return spannableString; 597f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 598f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 599f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 600f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank /** 601f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * Sets the bigtext for a notification for a single new conversation 602f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * @param context 603f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * @param message New message that triggered the notification 604f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank * @return a {@link CharSequence} suitable for use in {@link Notification.BigTextStyle} 605f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank */ 606f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank private static CharSequence getSingleMessageBigText(Context context, Message message) { 607f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan( 608f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank context, R.style.NotificationPrimaryText); 609f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 610f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String subject = message.mSubject; 611f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String snippet = message.mSnippet; 612f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 613f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (TextUtils.isEmpty(subject)) { 614f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // If the subject is empty, just use the snippet. 615f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return snippet; 616f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 617f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank else if (TextUtils.isEmpty(snippet)) { 618f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // If the snippet is empty, just use the subject. 619f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final SpannableString spannableString = new SpannableString(subject); 620f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0); 621f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 622f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return spannableString; 623f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } else { 624f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String notificationBigTextFormat = context.getResources().getString( 625f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank R.string.single_new_message_notification_big_text); 626f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 627f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // Localizers may change the order of the parameters, look at how the format 628f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // string is structured. 629f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final boolean isSubjectFirst = notificationBigTextFormat.indexOf("%2$s") > 630f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank notificationBigTextFormat.indexOf("%1$s"); 631f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final String bigText = String.format(notificationBigTextFormat, subject, snippet); 632f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final SpannableString spannableString = new SpannableString(bigText); 633f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 634f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final int subjectOffset = 635f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank (isSubjectFirst ? bigText.indexOf(subject) : bigText.lastIndexOf(subject)); 636f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank spannableString.setSpan(notificationSubjectSpan, 637f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank subjectOffset, subjectOffset + subject.length(), 0); 638f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 639f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank return spannableString; 640f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 641899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 642899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 64374e094834c6efd3260c717090a89232a892c411bMakoto Onuki /** 6446f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo * Creates a notification title for a new message. If there is only a single message, 6456f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo * show the sender name. Otherwise, show "X new messages". 64674e094834c6efd3260c717090a89232a892c411bMakoto Onuki */ 647958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy @VisibleForTesting 6486f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo SpannableString getNewMessageTitle(String sender, int unseenCount) { 6496f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo String title; 6506f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo if (unseenCount > 1) { 6516f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo title = String.format( 6526f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo mContext.getString(R.string.notification_multiple_new_messages_fmt), 6536f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo unseenCount); 65474e094834c6efd3260c717090a89232a892c411bMakoto Onuki } else { 6556f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo title = sender; 65674e094834c6efd3260c717090a89232a892c411bMakoto Onuki } 6576f93d2edca7e699503089def8935042cedbbcdf4Ben Komalo return new SpannableString(title); 65874e094834c6efd3260c717090a89232a892c411bMakoto Onuki } 65974e094834c6efd3260c717090a89232a892c411bMakoto Onuki 660958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy /** Returns the system's current ringer mode */ 661958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy @VisibleForTesting 662958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy int getRingerMode() { 66374e094834c6efd3260c717090a89232a892c411bMakoto Onuki return mAudioManager.getRingerMode(); 66474e094834c6efd3260c717090a89232a892c411bMakoto Onuki } 66574e094834c6efd3260c717090a89232a892c411bMakoto Onuki 666958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy /** Sets up the notification's sound and vibration based upon account details. */ 667958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy @VisibleForTesting 668958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy void setupSoundAndVibration(Notification.Builder builder, Account account) { 669899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki final int flags = account.mFlags; 670899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki final String ringtoneUri = account.mRingtoneUri; 6713c0b8eeaeb194de986f8adf2fd882080938afd37Scott Kennedy final boolean vibrate = (flags & Account.FLAGS_VIBRATE) != 0; 672899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 673958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy int defaults = Notification.DEFAULT_LIGHTS; 6743c0b8eeaeb194de986f8adf2fd882080938afd37Scott Kennedy if (vibrate) { 675958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy defaults |= Notification.DEFAULT_VIBRATE; 676899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 677899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki 678978959e6199410419792e89fcea16413f56aeb12Scott Kennedy builder.setSound(TextUtils.isEmpty(ringtoneUri) ? null : Uri.parse(ringtoneUri)) 679958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy .setDefaults(defaults); 680899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki } 681d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank 682d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank /** 683958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Show (or update) a notification that the given attachment could not be forwarded. This 684958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * is a very unusual case, and perhaps we shouldn't even send a notification. For now, 685958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * it's helpful for debugging. 686958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * 687c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS) 688d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank */ 689c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler public void showDownloadForwardFailedNotification(Attachment attachment) { 690bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final Account account = Account.restoreAccountWithId(mContext, attachment.mAccountKey); 691bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (account == null) return; 692bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook showAccountNotification(account, 693d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank mContext.getString(R.string.forward_download_failed_ticker), 694c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler mContext.getString(R.string.forward_download_failed_title), 695c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler attachment.mFileName, 696c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler null, 697c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler NOTIFICATION_ID_ATTACHMENT_WARNING); 698d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank } 699d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank 700d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank /** 701958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Returns a notification ID for login failed notifications for the given account account. 702d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank */ 703d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank private int getLoginFailedNotificationId(long accountId) { 704d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int)accountId; 705d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank } 706d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank 707c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler /** 708958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Show (or update) a notification that there was a login failure for the given account. 709958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * 710c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS) 711c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler */ 712d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank public void showLoginFailedNotification(long accountId) { 713d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank final Account account = Account.restoreAccountWithId(mContext, accountId); 714d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank if (account == null) return; 715bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook showAccountNotification(account, 716d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank mContext.getString(R.string.login_failed_ticker, account.mDisplayName), 717c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler mContext.getString(R.string.login_failed_title), 718c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler account.getDisplayName(), 7192866284a6d4ee4b368fcaf412c540b21d457e065Ben Komalo AccountSettings.createAccountSettingsIntent(mContext, accountId, 720bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook account.mDisplayName), 721c6d344ad2aab6bc46a87033af53d5a19a080e5f4Andy Stadler getLoginFailedNotificationId(accountId)); 722d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank } 723d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank 724958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy /** 725958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Cancels the login failed notification for the given account. 726958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy */ 727d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank public void cancelLoginFailedNotification(long accountId) { 728d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank mNotificationManager.cancel(getLoginFailedNotificationId(accountId)); 729d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank } 730308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki 731308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki /** 732958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Show (or update) a notification that the user's password is expiring. The given account 733958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * is used to update the display text, but, all accounts share the same notification ID. 734308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki * 735958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS) 736308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki */ 737308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki public void showPasswordExpiringNotification(long accountId) { 738308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki Account account = Account.restoreAccountWithId(mContext, accountId); 739308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki if (account == null) return; 740958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy 741308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext, 742308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki accountId, false); 743958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy String accountName = account.getDisplayName(); 744958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy String ticker = 745958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy mContext.getString(R.string.password_expire_warning_ticker_fmt, accountName); 746958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy String title = mContext.getString(R.string.password_expire_warning_content_title); 747bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook showAccountNotification(account, ticker, title, accountName, intent, 748308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki NOTIFICATION_ID_PASSWORD_EXPIRING); 749308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki } 750308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki 751308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki /** 752958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Show (or update) a notification that the user's password has expired. The given account 753958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * is used to update the display text, but, all accounts share the same notification ID. 754308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki * 755958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS) 756308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki */ 757308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki public void showPasswordExpiredNotification(long accountId) { 758308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki Account account = Account.restoreAccountWithId(mContext, accountId); 759308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki if (account == null) return; 760958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy 761308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext, 762308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki accountId, true); 763958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy String accountName = account.getDisplayName(); 764308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki String ticker = mContext.getString(R.string.password_expired_ticker); 765958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy String title = mContext.getString(R.string.password_expired_content_title); 766bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook showAccountNotification(account, ticker, title, accountName, intent, 767958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy NOTIFICATION_ID_PASSWORD_EXPIRED); 768308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki } 769308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki 770308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki /** 771958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Cancels any password expire notifications [both expired & expiring]. 772308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki */ 773308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki public void cancelPasswordExpirationNotifications() { 77483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRING); 77583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRED); 776308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki } 777308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki 778308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki /** 779bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Show (or update) a security needed notification. The given account is used to update 780bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * the display text, but, all accounts share the same notification ID. 781308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki */ 782308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki public void showSecurityNeededNotification(Account account) { 783308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, account.mId, true); 784958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy String accountName = account.getDisplayName(); 785958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy String ticker = 786bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mContext.getString(R.string.security_notification_ticker_fmt, accountName); 787bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook String title = mContext.getString(R.string.security_notification_content_title); 788bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook showAccountNotification(account, ticker, title, accountName, intent, 789bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook NOTIFICATION_ID_SECURITY_NEEDED); 790f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 791f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 792f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank /** 793bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Cancels the security needed notification. 794308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki */ 795308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki public void cancelSecurityNeededNotification() { 796bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mNotificationManager.cancel(NOTIFICATION_ID_SECURITY_NEEDED); 797308ce9284793b597797994dfb1fb25155cbe0b20Makoto Onuki } 798c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy 799c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy /** 800c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy * Observer invoked whenever a message we're notifying the user about changes. 801c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy */ 802c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy private static class MessageContentObserver extends ContentObserver { 803bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook /** A selection to get messages the user hasn't seen before */ 804bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private final static String MESSAGE_SELECTION = 805bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook MessageColumns.MAILBOX_KEY + "=? AND " 806bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook + MessageColumns.ID + ">? AND " 807bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook + MessageColumns.FLAG_READ + "=0 AND " 808bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook + Message.FLAG_LOADED_SELECTION; 809c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy private final Context mContext; 810bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private final long mMailboxId; 81183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy private final long mAccountId; 812c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy 81383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy public MessageContentObserver( 814bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Handler handler, Context context, long mailboxId, long accountId) { 81583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy super(handler); 816c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy mContext = context; 817bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mMailboxId = mailboxId; 818c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy mAccountId = accountId; 819c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy } 820c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy 821c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy @Override 822c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy public void onChange(boolean selfChange) { 823bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (mAccountId == sInstance.mSuspendAccountId 824bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook || sInstance.mSuspendAccountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 825bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return; 826bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 827bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 828aca94265813e72e692eace527f43eb4c02b09c76Marc Blank ContentObserver observer = sInstance.mNotificationMap.get(mAccountId); 829bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (observer == null) { 830bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Notification for a mailbox that we aren't observing; account is probably 831bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // being deleted. 832bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Log.w(Logging.LOG_TAG, "Received notification when observer data was null"); 833bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return; 834bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 835aca94265813e72e692eace527f43eb4c02b09c76Marc Blank Account account = Account.restoreAccountWithId(mContext, mAccountId); 836bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (account == null) { 837eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo Log.w(Logging.LOG_TAG, "Couldn't find account for changed message notification"); 838eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo return; 839eb9dcfaf18fcc8c1bf0a51238fc6e13873e8334eBen Komalo } 840bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook long oldMessageId = account.mNotifiedMessageId; 841bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook int oldMessageCount = account.mNotifiedMessageCount; 842c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy 843c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy ContentResolver resolver = mContext.getContentResolver(); 844bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Long lastSeenMessageId = Utility.getFirstRowLong( 845bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mContext, ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailboxId), 846bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook new String[] { MailboxColumns.LAST_SEEN_MESSAGE_KEY }, 847bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook null, null, null, 0); 848bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (lastSeenMessageId == null) { 849bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Mailbox got nuked. Could be that the account is in the process of being deleted 850bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Log.w(Logging.LOG_TAG, "Couldn't find mailbox for changed message notification"); 851bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return; 852bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 853bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 854bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Cursor c = resolver.query( 855bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Message.CONTENT_URI, EmailContent.ID_PROJECTION, 856bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook MESSAGE_SELECTION, 857bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook new String[] { Long.toString(mMailboxId), Long.toString(lastSeenMessageId) }, 858bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook MessageColumns.ID + " DESC"); 859bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (c == null) { 860bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Couldn't find message info - things may be getting deleted in bulk. 861bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Log.w(Logging.LOG_TAG, "#onChange(); NULL response for message id query"); 862bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return; 863bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 864c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy try { 865bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook int newMessageCount = c.getCount(); 866bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook long newMessageId = 0L; 867bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (c.moveToNext()) { 868bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook newMessageId = c.getLong(EmailContent.ID_PROJECTION_COLUMN); 869bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 870bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 871bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (newMessageCount == 0) { 872bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // No messages to notify for; clear the notification 873bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook int notificationId = sInstance.getNewMessageNotificationId(mAccountId); 874bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook sInstance.mNotificationManager.cancel(notificationId); 875bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } else if (newMessageCount != oldMessageCount 876bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook || (newMessageId != 0 && newMessageId != oldMessageId)) { 877bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Either the count or last message has changed; update the notification 878bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Integer unreadCount = Utility.getFirstRowInt( 879bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mContext, ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailboxId), 880bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook new String[] { MailboxColumns.UNREAD_COUNT }, 881bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook null, null, null, 0); 882bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (unreadCount == null) { 883bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Log.w(Logging.LOG_TAG, "Couldn't find unread count for mailbox"); 884bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return; 885bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 886bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 887bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Notification n = sInstance.createNewMessageNotification( 888bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook mAccountId, mMailboxId, c, newMessageId, 889bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook newMessageCount, unreadCount); 890bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (n != null) { 891bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Make the notification visible 892bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook sInstance.mNotificationManager.notify( 893bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook sInstance.getNewMessageNotificationId(mAccountId), n); 894bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 89583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 896bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Save away the new values 897bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook ContentValues cv = new ContentValues(); 898bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook cv.put(AccountColumns.NOTIFIED_MESSAGE_ID, newMessageId); 899bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook cv.put(AccountColumns.NOTIFIED_MESSAGE_COUNT, newMessageCount); 900bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook resolver.update(ContentUris.withAppendedId(Account.CONTENT_URI, mAccountId), cv, 901bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook null, null); 90283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } finally { 90383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy c.close(); 904c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy } 905c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy } 906c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy } 907c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy 908e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy /** 909e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy * Observer invoked whenever an account is modified. This could mean the user changed the 910e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy * notification settings. 911e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy */ 912e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy private static class AccountContentObserver extends ContentObserver { 913e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy private final Context mContext; 914e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy public AccountContentObserver(Handler handler, Context context) { 915e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy super(handler); 916e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy mContext = context; 917e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 918e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy 919e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy @Override 920e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy public void onChange(boolean selfChange) { 921e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy final ContentResolver resolver = mContext.getContentResolver(); 922bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final Cursor c = resolver.query( 923bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Account.CONTENT_URI, EmailContent.ID_PROJECTION, 924e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy NOTIFIED_ACCOUNT_SELECTION, null, null); 925e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy final HashSet<Long> newAccountList = new HashSet<Long>(); 926e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy final HashSet<Long> removedAccountList = new HashSet<Long>(); 927e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy if (c == null) { 928e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy // Suspender time ... theoretically, this will never happen 929e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy Log.wtf(Logging.LOG_TAG, "#onChange(); NULL response for account id query"); 930e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy return; 931e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 932e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy try { 933e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy while (c.moveToNext()) { 934e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy long accountId = c.getLong(EmailContent.ID_PROJECTION_COLUMN); 935e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy newAccountList.add(accountId); 936e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 937e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } finally { 938e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy if (c != null) { 939e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy c.close(); 940e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 941e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 942e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy // NOTE: Looping over three lists is not necessarily the most efficient. However, the 943e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy // account lists are going to be very small, so, this will not be necessarily bad. 944e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy // Cycle through existing notification list and adjust as necessary 945e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy for (long accountId : sInstance.mNotificationMap.keySet()) { 946e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy if (!newAccountList.remove(accountId)) { 947e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy // account id not in the current set of notifiable accounts 948e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy removedAccountList.add(accountId); 949e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 950e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 951e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy // A new account was added to the notification list 952e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy for (long accountId : newAccountList) { 953e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy sInstance.registerMessageNotification(accountId); 954e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 955e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy // An account was removed from the notification list 956e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy for (long accountId : removedAccountList) { 957e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy sInstance.unregisterMessageNotification(accountId); 958e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy int notificationId = sInstance.getNewMessageNotificationId(accountId); 959e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy sInstance.mNotificationManager.cancel(notificationId); 960e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 961e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 962e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy } 963e7fb4ac9e3b098ece98d004403a89652f88bbe7aTodd Kennedy 96483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy /** 96583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy * Thread to handle all notification actions through its own {@link Looper}. 96683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy */ 96783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy private static class NotificationThread implements Runnable { 96883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy /** Lock to ensure proper initialization */ 96983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy private final Object mLock = new Object(); 97083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy /** The {@link Looper} that handles messages for this thread */ 97183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy private Looper mLooper; 97283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy 97383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy NotificationThread() { 97483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy new Thread(null, this, "EmailNotification").start(); 97583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy synchronized (mLock) { 97683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy while (mLooper == null) { 97783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy try { 97883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy mLock.wait(); 97983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } catch (InterruptedException ex) { 98083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 98183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 98283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 98383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 98483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy 98583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy @Override 98683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy public void run() { 98783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy synchronized (mLock) { 98883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy Looper.prepare(); 98983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy mLooper = Looper.myLooper(); 99083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy mLock.notifyAll(); 99183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 99283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 99383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy Looper.loop(); 99483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 99583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy void quit() { 99683693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy mLooper.quit(); 99783693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 99883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy Looper getLooper() { 99983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy return mLooper; 100083693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 1001c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy } 1002899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onuki} 1003