1d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy/*
2d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy * Copyright (C) 2013 The Android Open Source Project
3d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy *
4d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy * Licensed under the Apache License, Version 2.0 (the "License");
5d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy * you may not use this file except in compliance with the License.
6d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy * You may obtain a copy of the License at
7d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy *
8d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy *      http://www.apache.org/licenses/LICENSE-2.0
9d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy *
10d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy * Unless required by applicable law or agreed to in writing, software
11d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy * distributed under the License is distributed on an "AS IS" BASIS,
12d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy * See the License for the specific language governing permissions and
14d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy * limitations under the License.
15d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy */
16d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedypackage com.android.mail.utils;
17d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
18d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.app.Notification;
19d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.app.PendingIntent;
20d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.content.ContentResolver;
21d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.content.ContentUris;
22d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.content.ContentValues;
23d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.content.Context;
24d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.content.Intent;
25d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.content.res.Resources;
26d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.database.Cursor;
27d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.graphics.Bitmap;
28d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.graphics.BitmapFactory;
291afed73f41aafe70badfbe5861099c80bef14183Rohan Shahimport android.net.MailTo;
30d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.net.Uri;
318913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sappersteinimport android.os.Looper;
32d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.provider.ContactsContract;
33d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.provider.ContactsContract.CommonDataKinds.Email;
34d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.support.v4.app.NotificationCompat;
35439aa5d7d9226de4682c3697c68c0703b8847518Alan Lauimport android.support.v4.app.NotificationManagerCompat;
36f58109699c207a2eaeca38d557d9b9a7e7664decAndrew Sappersteinimport android.support.v4.text.BidiFormatter;
378f10ced86b3c8e7229900d6089f56bf12b026116Andy Huangimport android.support.v4.util.ArrayMap;
38d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.text.SpannableString;
39d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.text.SpannableStringBuilder;
40d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.text.TextUtils;
41d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.text.style.CharacterStyle;
42d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.text.style.TextAppearanceSpan;
43d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport android.util.Pair;
4461bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedyimport android.util.SparseArray;
45d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
46821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.emailcommon.mail.Address;
47d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.EmailAddress;
48d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.MailIntentService;
49d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.R;
504fe0af81874976a1995191321e35c844b2229811Andy Huangimport com.android.mail.analytics.Analytics;
5189bf7ce646a0ee67e0a8ee10d8834bcf54257d97Andrew Sappersteinimport com.android.mail.browse.ConversationItemView;
52d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.browse.MessageCursor;
53d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.browse.SendersView;
54de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shahimport com.android.mail.photo.ContactFetcher;
55fe23512c2be21d7c87677f5263078021002d89cbScott Kennedyimport com.android.mail.photomanager.LetterTileProvider;
56d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.preferences.AccountPreferences;
57d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.preferences.FolderPreferences;
58d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.preferences.MailPrefs;
59d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.providers.Account;
60d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.providers.Conversation;
61d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.providers.Folder;
62d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.providers.Message;
63d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.providers.UIProvider;
64c94c80c8b8c92c019f19f2e315dea1ff03ad573fScott Kennedyimport com.android.mail.ui.ImageCanvas.Dimensions;
65d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport com.android.mail.utils.NotificationActionUtils.NotificationAction;
661bdbfefe4b144c7b031a1d9242a0fa061a0ae6b5Scott Kennedyimport com.google.android.mail.common.html.parser.HTML;
671bdbfefe4b144c7b031a1d9242a0fa061a0ae6b5Scott Kennedyimport com.google.android.mail.common.html.parser.HTML4;
681bdbfefe4b144c7b031a1d9242a0fa061a0ae6b5Scott Kennedyimport com.google.android.mail.common.html.parser.HtmlDocument;
691bdbfefe4b144c7b031a1d9242a0fa061a0ae6b5Scott Kennedyimport com.google.android.mail.common.html.parser.HtmlTree;
70ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sappersteinimport com.google.common.base.Objects;
717f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedyimport com.google.common.collect.ImmutableList;
7209400efa442422299acf21abe20e3470f9d965abScott Kennedyimport com.google.common.collect.Lists;
7309400efa442422299acf21abe20e3470f9d965abScott Kennedyimport com.google.common.collect.Sets;
74644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lauimport com.google.common.io.Closeables;
75d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
76644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lauimport java.io.InputStream;
77644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lauimport java.lang.ref.WeakReference;
78d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport java.util.ArrayList;
79d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport java.util.Arrays;
80d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport java.util.Collection;
8115490239c82cdb4765e87e926c8c1945adedd1edAlan Lauimport java.util.HashMap;
8215490239c82cdb4765e87e926c8c1945adedd1edAlan Lauimport java.util.HashSet;
83d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport java.util.List;
8415490239c82cdb4765e87e926c8c1945adedd1edAlan Lauimport java.util.Map;
85d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport java.util.Set;
86d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedyimport java.util.concurrent.ConcurrentHashMap;
87d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
88d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedypublic class NotificationUtils {
89c7d754703a0b5c2ca4b4dc9675d733881f85b746Scott Kennedy    public static final String LOG_TAG = "NotifUtils";
90d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
918913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    public static final String EXTRA_UNREAD_COUNT = "unread-count";
928913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    public static final String EXTRA_UNSEEN_COUNT = "unseen-count";
938913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    public static final String EXTRA_GET_ATTENTION = "get-attention";
948913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
95d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /** Contains a list of <(account, label), unread conversations> */
96d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static NotificationMap sActiveNotificationMap = null;
97d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
9861bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy    private static final SparseArray<Bitmap> sNotificationIcons = new SparseArray<Bitmap>();
99644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau    private static WeakReference<Bitmap> sDefaultWearableBg = new WeakReference<Bitmap>(null);
100d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
101d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static TextAppearanceSpan sNotificationUnreadStyleSpan;
102d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static CharacterStyle sNotificationReadStyleSpan;
103d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
104d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /** A factory that produces a plain text converter that removes elided text. */
10577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao    private static final HtmlTree.ConverterFactory MESSAGE_CONVERTER_FACTORY =
10677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            new HtmlTree.ConverterFactory() {
107d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                @Override
10877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                public HtmlTree.Converter<String> createInstance() {
109d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    return new MailMessagePlainTextConverter();
110d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
111d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            };
112d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
113963c7e4f178037acebddfd13e16fc627ef368c82Andrew Sapperstein    private static BidiFormatter sBidiFormatter = BidiFormatter.getInstance();
114f58109699c207a2eaeca38d557d9b9a7e7664decAndrew Sapperstein
115e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau    // Maps summary notification to conversation notification ids.
116e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau    private static Map<NotificationKey, Set<Integer>> sConversationNotificationMap =
11715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            new HashMap<NotificationKey, Set<Integer>>();
11815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
119d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
120d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Clears all notifications in response to the user tapping "Clear" in the status bar.
121d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
122d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    public static void clearAllNotfications(Context context) {
1232f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy        LogUtils.v(LOG_TAG, "Clearing all notifications.");
124d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final NotificationMap notificationMap = getNotificationMap(context);
125d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        notificationMap.clear();
126d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        notificationMap.saveNotificationMap(context);
127d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
128d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
129d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
130d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Returns the notification map, creating it if necessary.
131d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
132d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static synchronized NotificationMap getNotificationMap(Context context) {
133d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (sActiveNotificationMap == null) {
134d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            sActiveNotificationMap = new NotificationMap();
135d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
136d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // populate the map from the cached data
137d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            sActiveNotificationMap.loadNotificationMap(context);
138d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
139d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return sActiveNotificationMap;
140d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
141d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
142d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
143d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Class representing the existing notifications, and the number of unread and
144d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * unseen conversations that triggered each.
145d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
1462354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee    private static final class NotificationMap {
147d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
148d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        private static final String NOTIFICATION_PART_SEPARATOR = " ";
149d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        private static final int NUM_NOTIFICATION_PARTS= 4;
1502354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        private final ConcurrentHashMap<NotificationKey, Pair<Integer, Integer>> mMap =
1512354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee            new ConcurrentHashMap<NotificationKey, Pair<Integer, Integer>>();
152d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
153d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        /**
1542354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * Returns the number of key values pairs in the inner map.
1552354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         */
1562354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        public int size() {
1572354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee            return mMap.size();
1582354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        }
1592354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee
1602354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        /**
1612354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * Returns a set of key values.
1622354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         */
1632354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        public Set<NotificationKey> keySet() {
1642354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee            return mMap.keySet();
1652354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        }
1662354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee
1672354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        /**
1682354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * Remove the key from the inner map and return its value.
1692354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         *
1702354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * @param key The key {@link NotificationKey} to be removed.
1712354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * @return The value associated with this key.
1722354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         */
1732354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        public Pair<Integer, Integer> remove(NotificationKey key) {
1742354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee            return mMap.remove(key);
1752354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        }
1762354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee
1772354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        /**
1782354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * Clear all key-value pairs in the map.
1792354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         */
1802354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        public void clear() {
1812354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee            mMap.clear();
1822354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        }
1832354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee
1842354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        /**
1852354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * Discover if a key-value pair with this key exists.
1862354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         *
1872354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * @param key The key {@link NotificationKey} to be checked.
1882354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * @return If a key-value pair with this key exists in the map.
1892354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         */
1902354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        public boolean containsKey(NotificationKey key) {
1912354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee            return mMap.containsKey(key);
1922354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        }
1932354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee
1942354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee        /**
1952354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * Returns the unread count for the given NotificationKey.
196d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy         */
197d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public Integer getUnread(NotificationKey key) {
1982354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee            final Pair<Integer, Integer> value = mMap.get(key);
199d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return value != null ? value.first : null;
200d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
201d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
202d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        /**
2032354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee         * Returns the unread unseen count for the given NotificationKey.
204d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy         */
205d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public Integer getUnseen(NotificationKey key) {
2062354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee            final Pair<Integer, Integer> value = mMap.get(key);
207d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return value != null ? value.second : null;
208d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
209d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
210d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        /**
211d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy         * Store the unread and unseen value for the given NotificationKey
212d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy         */
213d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public void put(NotificationKey key, int unread, int unseen) {
214d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Pair<Integer, Integer> value =
215d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    new Pair<Integer, Integer>(Integer.valueOf(unread), Integer.valueOf(unseen));
2162354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee            mMap.put(key, value);
217d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
218d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
219d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        /**
220d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy         * Populates the notification map with previously cached data.
221d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy         */
222d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public synchronized void loadNotificationMap(final Context context) {
223d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final MailPrefs mailPrefs = MailPrefs.get(context);
224d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Set<String> notificationSet = mailPrefs.getActiveNotificationSet();
225d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (notificationSet != null) {
226d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                for (String notificationEntry : notificationSet) {
227d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    // Get the parts of the string that make the notification entry
228d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    final String[] notificationParts =
229d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            TextUtils.split(notificationEntry, NOTIFICATION_PART_SEPARATOR);
230d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    if (notificationParts.length == NUM_NOTIFICATION_PARTS) {
231d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        final Uri accountUri = Uri.parse(notificationParts[0]);
232d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        final Cursor accountCursor = context.getContentResolver().query(
233d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                accountUri, UIProvider.ACCOUNTS_PROJECTION, null, null, null);
234bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux
235bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux                        if (accountCursor == null) {
236bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux                            throw new IllegalStateException("Unable to locate account for uri: " +
237bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux                                    LogUtils.contentUriToString(accountUri));
238bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux                        }
239bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux
240d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        final Account account;
241d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        try {
242d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            if (accountCursor.moveToFirst()) {
2434b0c0127d24e387a4f49d442b63b5c60cedb6922Ray Chen                                account = Account.builder().buildFrom(accountCursor);
244d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            } else {
245d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                continue;
246d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            }
247d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        } finally {
248d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            accountCursor.close();
249d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        }
250d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
251d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        final Uri folderUri = Uri.parse(notificationParts[1]);
252d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        final Cursor folderCursor = context.getContentResolver().query(
253d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                folderUri, UIProvider.FOLDERS_PROJECTION, null, null, null);
254bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux
255bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux                        if (folderCursor == null) {
256bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux                            throw new IllegalStateException("Unable to locate folder for uri: " +
257bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux                                    LogUtils.contentUriToString(folderUri));
258bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux                        }
259bf0da35d8b36e49935ca1727bc1c86ff99953c74James Lemieux
260d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        final Folder folder;
261d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        try {
262d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            if (folderCursor.moveToFirst()) {
263d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                folder = new Folder(folderCursor);
264d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            } else {
265d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                continue;
266d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            }
267d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        } finally {
268d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            folderCursor.close();
269d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        }
270d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
271d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        final NotificationKey key = new NotificationKey(account, folder);
272d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        final Integer unreadValue = Integer.valueOf(notificationParts[2]);
273d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        final Integer unseenValue = Integer.valueOf(notificationParts[3]);
2742354fd98dec04736bd35c1ec8c130cb1aa18ff9cAnthony Lee                        put(key, unreadValue, unseenValue);
275d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    }
276d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
277d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
278d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
279d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
280d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        /**
281d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy         * Cache the notification map.
282d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy         */
283d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public synchronized void saveNotificationMap(Context context) {
284d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Set<String> notificationSet = Sets.newHashSet();
285bbfc1432baca1a3e4517ea4edc1fb209cbe6997fJay Shrauner            final Set<NotificationKey> keys = keySet();
286d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            for (NotificationKey key : keys) {
287bbfc1432baca1a3e4517ea4edc1fb209cbe6997fJay Shrauner                final Integer unreadCount = getUnread(key);
288bbfc1432baca1a3e4517ea4edc1fb209cbe6997fJay Shrauner                final Integer unseenCount = getUnseen(key);
289ff8553f20964f4c31b0c503a9e1daff6ae08a9c7Scott Kennedy                if (unreadCount != null && unseenCount != null) {
290d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    final String[] partValues = new String[] {
291259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                            key.account.uri.toString(), key.folder.folderUri.fullUri.toString(),
292d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            unreadCount.toString(), unseenCount.toString()};
293d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    notificationSet.add(TextUtils.join(NOTIFICATION_PART_SEPARATOR, partValues));
294d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
295d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
296d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final MailPrefs mailPrefs = MailPrefs.get(context);
297d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            mailPrefs.cacheActiveNotificationSet(notificationSet);
298d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
299d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
300d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
301d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
302d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @return the title of this notification with each account and the number of unread and unseen
303d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * conversations for it. Also remove any account in the map that has 0 unread.
304d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
305d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static String createNotificationString(NotificationMap notifications) {
306d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        StringBuilder result = new StringBuilder();
307d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        int i = 0;
308d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        Set<NotificationKey> keysToRemove = Sets.newHashSet();
309d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        for (NotificationKey key : notifications.keySet()) {
310d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            Integer unread = notifications.getUnread(key);
311d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            Integer unseen = notifications.getUnseen(key);
312d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (unread == null || unread.intValue() == 0) {
313d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                keysToRemove.add(key);
314d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            } else {
315d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                if (i > 0) result.append(", ");
316d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                result.append(key.toString() + " (" + unread + ", " + unseen + ")");
317d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                i++;
318d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
319d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
320d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
321d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        for (NotificationKey key : keysToRemove) {
322d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            notifications.remove(key);
323d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
324d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
325d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return result.toString();
326d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
327d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
328d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
329d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Get all notifications for all accounts and cancel them.
330d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     **/
331d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    public static void cancelAllNotifications(Context context) {
3322f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy        LogUtils.d(LOG_TAG, "cancelAllNotifications - cancelling all");
33315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        NotificationManagerCompat nm = NotificationManagerCompat.from(context);
334d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        nm.cancelAll();
335d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        clearAllNotfications(context);
336d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
337d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
338d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
339d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Get all notifications for all accounts, cancel them, and repost.
340d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * This happens when locale changes.
341d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     **/
3428913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    public static void cancelAndResendNotificationsOnLocaleChange(
343de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            Context context, final ContactFetcher contactFetcher) {
344963c7e4f178037acebddfd13e16fc627ef368c82Andrew Sapperstein        LogUtils.d(LOG_TAG, "cancelAndResendNotificationsOnLocaleChange");
345963c7e4f178037acebddfd13e16fc627ef368c82Andrew Sapperstein        sBidiFormatter = BidiFormatter.getInstance();
346de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah        resendNotifications(context, true, null, null, contactFetcher);
347d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
348d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
349d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
350d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Get all notifications for all accounts, optionally cancel them, and repost.
351ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     * This happens when locale changes. If you only want to resend messages from one
352ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     * account-folder pair, pass in the account and folder that should be resent.
353ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     * All other account-folder pairs will not have their notifications resent.
354ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     * All notifications will be resent if account or folder is null.
355ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     *
356ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     * @param context Current context.
357ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     * @param cancelExisting True, if all notifications should be canceled before resending.
358ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     *                       False, otherwise.
359ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     * @param accountUri The {@link Uri} of the {@link Account} of the notification
360612eb3287c1261952f16dff7bdf5715b9b92b8daRégis Décamps     *                   upon which an action occurred, or {@code null}.
361ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     * @param folderUri The {@link Uri} of the {@link Folder} of the notification
362612eb3287c1261952f16dff7bdf5715b9b92b8daRégis Décamps     *                  upon which an action occurred, or {@code null}.
363ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein     */
364ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein    public static void resendNotifications(Context context, final boolean cancelExisting,
3658913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            final Uri accountUri, final FolderUri folderUri,
366de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            final ContactFetcher contactFetcher) {
367612eb3287c1261952f16dff7bdf5715b9b92b8daRégis Décamps        LogUtils.i(LOG_TAG, "resendNotifications cancelExisting: %b, account: %s, folder: %s",
368612eb3287c1261952f16dff7bdf5715b9b92b8daRégis Décamps                cancelExisting,
369612eb3287c1261952f16dff7bdf5715b9b92b8daRégis Décamps                accountUri == null ? null : LogUtils.sanitizeName(LOG_TAG, accountUri.toString()),
370612eb3287c1261952f16dff7bdf5715b9b92b8daRégis Décamps                folderUri == null ? null : LogUtils.sanitizeName(LOG_TAG, folderUri.toString()));
3712638b48e063e469d48daf6a7c0d3a9b2d56d7beaScott Kennedy
372d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (cancelExisting) {
3732f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy            LogUtils.d(LOG_TAG, "resendNotifications - cancelling all");
37415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            NotificationManagerCompat nm = NotificationManagerCompat.from(context);
375d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            nm.cancelAll();
376d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
377d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Re-validate the notifications.
378d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final NotificationMap notificationMap = getNotificationMap(context);
379d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final Set<NotificationKey> keys = notificationMap.keySet();
380d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        for (NotificationKey notification : keys) {
381d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Folder folder = notification.folder;
382b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler            final int notificationId =
383b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler                    getNotificationId(notification.account.getAccountManagerAccount(), folder);
384ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein
385ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein            // Only resend notifications if the notifications are from the same folder
386ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein            // and same account as the undo notification that was previously displayed.
38726b29ef558cce109c51584081c52781760a02dd9Scott Kennedy            if (accountUri != null && !Objects.equal(accountUri, notification.account.uri) &&
388259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                    folderUri != null && !Objects.equal(folderUri, folder.folderUri)) {
3892f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy                LogUtils.d(LOG_TAG, "resendNotifications - not resending %s / %s"
3902638b48e063e469d48daf6a7c0d3a9b2d56d7beaScott Kennedy                        + " because it doesn't match %s / %s",
391259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                        notification.account.uri, folder.folderUri, accountUri, folderUri);
392ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein                continue;
393ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein            }
394d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
3952f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy            LogUtils.d(LOG_TAG, "resendNotifications - resending %s / %s",
396259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                    notification.account.uri, folder.folderUri);
3972638b48e063e469d48daf6a7c0d3a9b2d56d7beaScott Kennedy
398d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final NotificationAction undoableAction =
399d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    NotificationActionUtils.sUndoNotifications.get(notificationId);
400d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (undoableAction == null) {
401ddd17bc2963b67ba07dfd1e2d9b1f17abf8c8e4aAndrew Sapperstein                validateNotifications(context, folder, notification.account, true,
402de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                        false, notification, contactFetcher);
403d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            } else {
404d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // Create an undo notification
405d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                NotificationActionUtils.createUndoNotification(context, undoableAction);
406d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
407d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
408d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
409d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
410d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
411d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Validate the notifications for the specified account.
412d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
413fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook    public static void validateAccountNotifications(Context context, Account account) {
414fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook        final String email = account.getEmailAddress();
415fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook        LogUtils.d(LOG_TAG, "validateAccountNotifications - %s", email);
4164162f83633a001cf03ff7bbb5e1abae92392049bScott Kennedy
417d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        List<NotificationKey> notificationsToCancel = Lists.newArrayList();
418d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Iterate through the notification map to see if there are any entries that correspond to
419d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // labels that are not in the sync set.
420d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final NotificationMap notificationMap = getNotificationMap(context);
421d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        Set<NotificationKey> keys = notificationMap.keySet();
422fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook        final AccountPreferences accountPreferences = new AccountPreferences(context,
423fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook                account.getAccountId());
424d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final boolean enabled = accountPreferences.areNotificationsEnabled();
425d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (!enabled) {
426d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Cancel all notifications for this account
427d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            for (NotificationKey notification : keys) {
428fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook                if (notification.account.getAccountManagerAccount().name.equals(email)) {
429d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    notificationsToCancel.add(notification);
430d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
431d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
432d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } else {
433d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Iterate through the notification map to see if there are any entries that
434d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // correspond to labels that are not in the notification set.
435d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            for (NotificationKey notification : keys) {
436fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook                if (notification.account.getAccountManagerAccount().name.equals(email)) {
437d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    // If notification is not enabled for this label, remember this NotificationKey
438d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    // to later cancel the notification, and remove the entry from the map
439d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    final Folder folder = notification.folder;
440259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                    final boolean isInbox = folder.folderUri.equals(
441259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                            notification.account.settings.defaultInbox);
442d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    final FolderPreferences folderPreferences = new FolderPreferences(
443fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook                            context, notification.account.getAccountId(), folder, isInbox);
444d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
445d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    if (!folderPreferences.areNotificationsEnabled()) {
446d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        notificationsToCancel.add(notification);
447d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    }
448d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
449d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
450d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
451d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
452d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Cancel & remove the invalid notifications.
453d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (notificationsToCancel.size() > 0) {
454b9027cc33cd7b4c5c6f9f3efc8e044e49b491dc6Alan Lau            NotificationManagerCompat nm = NotificationManagerCompat.from(context);
455d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            for (NotificationKey notification : notificationsToCancel) {
456d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                final Folder folder = notification.folder;
457b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler                final int notificationId =
458b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler                        getNotificationId(notification.account.getAccountManagerAccount(), folder);
4592f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy                LogUtils.d(LOG_TAG, "validateAccountNotifications - cancelling %s / %s",
46026a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                        notification.account.getEmailAddress(), folder.persistentId);
461d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                nm.cancel(notificationId);
462d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                notificationMap.remove(notification);
463d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                NotificationActionUtils.sUndoNotifications.remove(notificationId);
464d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                NotificationActionUtils.sNotificationTimestamps.delete(notificationId);
465b9027cc33cd7b4c5c6f9f3efc8e044e49b491dc6Alan Lau
466e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                cancelConversationNotifications(notification, nm);
467d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
468d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            notificationMap.saveNotificationMap(context);
469d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
470d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
471d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
4728913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    public static void sendSetNewEmailIndicatorIntent(Context context, final int unreadCount,
4738913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            final int unseenCount, final Account account, final Folder folder,
4748913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            final boolean getAttention) {
4758913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        LogUtils.i(LOG_TAG, "sendSetNewEmailIndicator account: %s, folder: %s",
4768913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                LogUtils.sanitizeName(LOG_TAG, account.getEmailAddress()),
4778913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                LogUtils.sanitizeName(LOG_TAG, folder.name));
4788913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
4798913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        final Intent intent = new Intent(MailIntentService.ACTION_SEND_SET_NEW_EMAIL_INDICATOR);
4808913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        intent.setPackage(context.getPackageName()); // Make sure we only deliver this to ourselves
4818913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        intent.putExtra(EXTRA_UNREAD_COUNT, unreadCount);
4828913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        intent.putExtra(EXTRA_UNSEEN_COUNT, unseenCount);
4838913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        intent.putExtra(Utils.EXTRA_ACCOUNT, account);
4848913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        intent.putExtra(Utils.EXTRA_FOLDER, folder);
4858913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        intent.putExtra(EXTRA_GET_ATTENTION, getAttention);
4868913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        context.startService(intent);
4878913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    }
4888913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
489d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
4908913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein     * Display only one notification. Should only be called from
4918913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein     * {@link com.android.mail.MailIntentService}. Use {@link #sendSetNewEmailIndicatorIntent}
4928913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein     * if you need to perform this action anywhere else.
493d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
494d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    public static void setNewEmailIndicator(Context context, final int unreadCount,
495d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final int unseenCount, final Account account, final Folder folder,
496de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            final boolean getAttention, final ContactFetcher contactFetcher) {
4972f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy        LogUtils.d(LOG_TAG, "setNewEmailIndicator unreadCount = %d, unseenCount = %d, account = %s,"
49826a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                + " folder = %s, getAttention = %b", unreadCount, unseenCount,
49926a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                account.getEmailAddress(), folder.folderUri, getAttention);
5004162f83633a001cf03ff7bbb5e1abae92392049bScott Kennedy
501d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        boolean ignoreUnobtrusiveSetting = false;
502d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
503b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler        final int notificationId = getNotificationId(account.getAccountManagerAccount(), folder);
504dab8a94a939988e99597845e89cfe3fd1da0ab88Scott Kennedy
505d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Update the notification map
506d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final NotificationMap notificationMap = getNotificationMap(context);
507d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final NotificationKey key = new NotificationKey(account, folder);
508d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (unreadCount == 0) {
50926a20758413f0f8629ea71a0943304e3ae740165Tony Mantler            LogUtils.d(LOG_TAG, "setNewEmailIndicator - cancelling %s / %s",
51026a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                    account.getEmailAddress(), folder.persistentId);
511d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            notificationMap.remove(key);
512b9027cc33cd7b4c5c6f9f3efc8e044e49b491dc6Alan Lau
513b9027cc33cd7b4c5c6f9f3efc8e044e49b491dc6Alan Lau            NotificationManagerCompat nm = NotificationManagerCompat.from(context);
514b9027cc33cd7b4c5c6f9f3efc8e044e49b491dc6Alan Lau            nm.cancel(notificationId);
515e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            cancelConversationNotifications(key, nm);
516d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } else {
517b1b855b71e502459e1571444fb1a9f74883c8330Paul Westbrook            LogUtils.d(LOG_TAG, "setNewEmailIndicator - update count for: %s / %s " +
51826a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                    "to: unread: %d unseen %d", account.getEmailAddress(), folder.persistentId,
519b1b855b71e502459e1571444fb1a9f74883c8330Paul Westbrook                    unreadCount, unseenCount);
520d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (!notificationMap.containsKey(key)) {
521d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // This account previously didn't have any unread mail; ignore the "unobtrusive
522d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // notifications" setting and play sound and/or vibrate the device even if a
523d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // notification already exists (bug 2412348).
524b1b855b71e502459e1571444fb1a9f74883c8330Paul Westbrook                LogUtils.d(LOG_TAG, "setNewEmailIndicator - ignoringUnobtrusiveSetting");
525d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                ignoreUnobtrusiveSetting = true;
526d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
527d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            notificationMap.put(key, unreadCount, unseenCount);
528d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
529d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        notificationMap.saveNotificationMap(context);
530d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
531d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (LogUtils.isLoggable(LOG_TAG, LogUtils.VERBOSE)) {
5322f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy            LogUtils.v(LOG_TAG, "New email: %s mapSize: %d getAttention: %b",
533d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    createNotificationString(notificationMap), notificationMap.size(),
534d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    getAttention);
535d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
536d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
537d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (NotificationActionUtils.sUndoNotifications.get(notificationId) == null) {
538d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            validateNotifications(context, folder, account, getAttention, ignoreUnobtrusiveSetting,
539de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                    key, contactFetcher);
540d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
541d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
542d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
543d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
544d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Validate the notifications notification.
545d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
546d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static void validateNotifications(Context context, final Folder folder,
547d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Account account, boolean getAttention, boolean ignoreUnobtrusiveSetting,
548de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            NotificationKey key, final ContactFetcher contactFetcher) {
549d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
55015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        NotificationManagerCompat nm = NotificationManagerCompat.from(context);
551d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
552d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final NotificationMap notificationMap = getNotificationMap(context);
553d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (LogUtils.isLoggable(LOG_TAG, LogUtils.VERBOSE)) {
55432fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy            LogUtils.i(LOG_TAG, "Validating Notification: %s mapSize: %d "
555b1b855b71e502459e1571444fb1a9f74883c8330Paul Westbrook                    + "folder: %s getAttention: %b ignoreUnobtrusive: %b",
556b1b855b71e502459e1571444fb1a9f74883c8330Paul Westbrook                    createNotificationString(notificationMap),
557b1b855b71e502459e1571444fb1a9f74883c8330Paul Westbrook                    notificationMap.size(), folder.name, getAttention, ignoreUnobtrusiveSetting);
55832fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy        } else {
55932fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy            LogUtils.i(LOG_TAG, "Validating Notification, mapSize: %d "
560b1b855b71e502459e1571444fb1a9f74883c8330Paul Westbrook                    + "getAttention: %b ignoreUnobtrusive: %b", notificationMap.size(),
561b1b855b71e502459e1571444fb1a9f74883c8330Paul Westbrook                    getAttention, ignoreUnobtrusiveSetting);
562d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
563d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // The number of unread messages for this account and label.
564d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final Integer unread = notificationMap.getUnread(key);
565d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final int unreadCount = unread != null ? unread.intValue() : 0;
566d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final Integer unseen = notificationMap.getUnseen(key);
567d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        int unseenCount = unseen != null ? unseen.intValue() : 0;
568d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
569d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        Cursor cursor = null;
570d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
571d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        try {
572d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Uri.Builder uriBuilder = folder.conversationListUri.buildUpon();
573d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            uriBuilder.appendQueryParameter(
574d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    UIProvider.SEEN_QUERY_PARAMETER, Boolean.FALSE.toString());
5750be9243458fc713bc34b3ab76bfb0529f7e67871Andy Huang            // Do not allow this quick check to disrupt any active network-enabled conversation
5760be9243458fc713bc34b3ab76bfb0529f7e67871Andy Huang            // cursor.
5770be9243458fc713bc34b3ab76bfb0529f7e67871Andy Huang            uriBuilder.appendQueryParameter(
5780be9243458fc713bc34b3ab76bfb0529f7e67871Andy Huang                    UIProvider.ConversationListQueryParameters.USE_NETWORK,
5790be9243458fc713bc34b3ab76bfb0529f7e67871Andy Huang                    Boolean.FALSE.toString());
580d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            cursor = context.getContentResolver().query(uriBuilder.build(),
581d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    UIProvider.CONVERSATION_PROJECTION, null, null, null);
5823a7b45bd1a03ce6158d766ac8837f1c8cb3758e1Yu Ping Hu            if (cursor == null) {
5833a7b45bd1a03ce6158d766ac8837f1c8cb3758e1Yu Ping Hu                // This folder doesn't exist.
58432fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy                LogUtils.i(LOG_TAG,
58532fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy                        "The cursor is null, so the specified folder probably does not exist");
5863a7b45bd1a03ce6158d766ac8837f1c8cb3758e1Yu Ping Hu                clearFolderNotification(context, account, folder, false);
5873a7b45bd1a03ce6158d766ac8837f1c8cb3758e1Yu Ping Hu                return;
5883a7b45bd1a03ce6158d766ac8837f1c8cb3758e1Yu Ping Hu            }
589d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final int cursorUnseenCount = cursor.getCount();
590d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
591d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Make sure the unseen count matches the number of items in the cursor.  But, we don't
592d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // want to overwrite a 0 unseen count that was specified in the intent
593d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (unseenCount != 0 && unseenCount != cursorUnseenCount) {
59432fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy                LogUtils.i(LOG_TAG,
5952f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy                        "Unseen count doesn't match cursor count.  unseen: %d cursor count: %d",
596d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        unseenCount, cursorUnseenCount);
597d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                unseenCount = cursorUnseenCount;
598d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
599d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
600d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // For the purpose of the notifications, the unseen count should be capped at the num of
601d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // unread conversations.
602d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (unseenCount > unreadCount) {
603d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                unseenCount = unreadCount;
604d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
605d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
606b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler            final int notificationId =
607b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler                    getNotificationId(account.getAccountManagerAccount(), folder);
608d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
60915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            NotificationKey notificationKey = new NotificationKey(account, folder);
61015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
611d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (unseenCount == 0) {
61232fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy                LogUtils.i(LOG_TAG, "validateNotifications - cancelling account %s / folder %s",
61326a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                        LogUtils.sanitizeName(LOG_TAG, account.getEmailAddress()),
61432fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy                        LogUtils.sanitizeName(LOG_TAG, folder.persistentId));
615d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                nm.cancel(notificationId);
616e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                cancelConversationNotifications(notificationKey, nm);
61715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
618d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                return;
619d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
620d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
621d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // We now have all we need to create the notification and the pending intent
6224e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux            PendingIntent clickIntent = null;
623d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
624d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            NotificationCompat.Builder notification = new NotificationCompat.Builder(context);
625439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau            NotificationCompat.WearableExtender wearableExtender =
626439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau                    new NotificationCompat.WearableExtender();
627c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau            Map<Integer, NotificationBuilders> msgNotifications =
628c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau                    new ArrayMap<Integer, NotificationBuilders>();
629ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah
630ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah            if (com.android.mail.utils.Utils.isRunningLOrLater()) {
631ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah                notification.setColor(
632160f8a7166daa4e300157527bc3e65ffcd7a0c1cAndrew Sapperstein                        context.getResources().getColor(R.color.notification_icon_color));
633ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah            }
63410b39450191f95d37e29526ada7a239dcbc858adRohan Shah
63510b39450191f95d37e29526ada7a239dcbc858adRohan Shah            if(unseenCount > 1) {
636722c8c8e9797a021178c9feb7f2902a2a095ab36Rohan Shah                notification.setSmallIcon(R.drawable.ic_notification_multiple_mail_24dp);
63710b39450191f95d37e29526ada7a239dcbc858adRohan Shah            } else {
638722c8c8e9797a021178c9feb7f2902a2a095ab36Rohan Shah                notification.setSmallIcon(R.drawable.ic_notification_mail_24dp);
63910b39450191f95d37e29526ada7a239dcbc858adRohan Shah            }
64026a20758413f0f8629ea71a0943304e3ae740165Tony Mantler            notification.setTicker(account.getDisplayName());
6414e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux            notification.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
64276fb85a30a41254537c0ad26cabec06030fcdbbaPaul Westbrook            notification.setCategory(NotificationCompat.CATEGORY_EMAIL);
643d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
644d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final long when;
645d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
646d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final long oldWhen =
647d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    NotificationActionUtils.sNotificationTimestamps.get(notificationId);
648d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (oldWhen != 0) {
649d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                when = oldWhen;
650d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            } else {
651d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                when = System.currentTimeMillis();
652d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
653d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
654d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            notification.setWhen(when);
655d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
656d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // The timestamp is now stored in the notification, so we can remove it from here
657d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            NotificationActionUtils.sNotificationTimestamps.delete(notificationId);
658d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
659d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Dispatch a CLEAR_NEW_MAIL_NOTIFICATIONS intent if the user taps the "X" next to a
660d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // notification.  Also this intent gets fired when the user taps on a notification as
661d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // the AutoCancel flag has been set
662d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Intent cancelNotificationIntent =
663d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    new Intent(MailIntentService.ACTION_CLEAR_NEW_MAIL_NOTIFICATIONS);
664d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            cancelNotificationIntent.setPackage(context.getPackageName());
66509400efa442422299acf21abe20e3470f9d965abScott Kennedy            cancelNotificationIntent.setData(Utils.appendVersionQueryParameter(context,
666259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                    folder.folderUri.fullUri));
66748cfe4613549cafdf36e2a524afba730522bf291Scott Kennedy            cancelNotificationIntent.putExtra(Utils.EXTRA_ACCOUNT, account);
66848cfe4613549cafdf36e2a524afba730522bf291Scott Kennedy            cancelNotificationIntent.putExtra(Utils.EXTRA_FOLDER, folder);
669d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
670d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            notification.setDeleteIntent(PendingIntent.getService(
671d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    context, notificationId, cancelNotificationIntent, 0));
672d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
673d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Ensure that the notification is cleared when the user selects it
674d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            notification.setAutoCancel(true);
675d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
676d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            boolean eventInfoConfigured = false;
677d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
678259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy            final boolean isInbox = folder.folderUri.equals(account.settings.defaultInbox);
679d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final FolderPreferences folderPreferences =
680fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook                    new FolderPreferences(context, account.getAccountId(), folder, isInbox);
681d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
682d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (isInbox) {
683d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                final AccountPreferences accountPreferences =
684fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook                        new AccountPreferences(context, account.getAccountId());
685d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                moveNotificationSetting(accountPreferences, folderPreferences);
686d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
687d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
688d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (!folderPreferences.areNotificationsEnabled()) {
68932fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy                LogUtils.i(LOG_TAG, "Notifications are disabled for this folder; not notifying");
690d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // Don't notify
691d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                return;
692d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
693d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
694d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (unreadCount > 0) {
695d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // How can I order this properly?
696d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                if (cursor.moveToNext()) {
697bd8acad098abbf074184aa1273d269f7f7a54b37Scott Kennedy                    final Intent notificationIntent;
698d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
699bd8acad098abbf074184aa1273d269f7f7a54b37Scott Kennedy                    // Launch directly to the conversation, if there is only 1 unseen conversation
7004fe0af81874976a1995191321e35c844b2229811Andy Huang                    final boolean launchConversationMode = (unseenCount == 1);
7014fe0af81874976a1995191321e35c844b2229811Andy Huang                    if (launchConversationMode) {
70209400efa442422299acf21abe20e3470f9d965abScott Kennedy                        notificationIntent = createViewConversationIntent(context, account, folder,
70309400efa442422299acf21abe20e3470f9d965abScott Kennedy                                cursor);
704bd8acad098abbf074184aa1273d269f7f7a54b37Scott Kennedy                    } else {
705bd8acad098abbf074184aa1273d269f7f7a54b37Scott Kennedy                        notificationIntent = createViewConversationIntent(context, account, folder,
706bd8acad098abbf074184aa1273d269f7f7a54b37Scott Kennedy                                null);
707d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    }
708d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
7094fe0af81874976a1995191321e35c844b2229811Andy Huang                    Analytics.getInstance().sendEvent("notification_create",
7104fe0af81874976a1995191321e35c844b2229811Andy Huang                            launchConversationMode ? "conversation" : "conversation_list",
7114fe0af81874976a1995191321e35c844b2229811Andy Huang                            folder.getTypeDescription(), unseenCount);
7124fe0af81874976a1995191321e35c844b2229811Andy Huang
713d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    if (notificationIntent == null) {
7142f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy                        LogUtils.e(LOG_TAG, "Null intent when building notification");
715d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        return;
716d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    }
717d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
71805a18446a96fa8ea2ae077b482499abf67366e32Alan Lau                    clickIntent = createClickPendingIntent(context, notificationIntent);
7194fe0af81874976a1995191321e35c844b2229811Andy Huang
720d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    configureLatestEventInfoFromConversation(context, account, folderPreferences,
721439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau                            notification, wearableExtender, msgNotifications, notificationId,
72215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                            cursor, clickIntent, notificationIntent, unreadCount, unseenCount,
723de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                            folder, when, contactFetcher);
724d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    eventInfoConfigured = true;
725d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
726d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
727d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
728d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final boolean vibrate = folderPreferences.isNotificationVibrateEnabled();
729d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final String ringtoneUri = folderPreferences.getNotificationRingtoneUri();
730d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final boolean notifyOnce = !folderPreferences.isEveryMessageNotificationEnabled();
731d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
732ff8553f20964f4c31b0c503a9e1daff6ae08a9c7Scott Kennedy            if (!ignoreUnobtrusiveSetting && notifyOnce) {
733d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // If the user has "unobtrusive notifications" enabled, only alert the first time
734d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // new mail is received in this account.  This is the default behavior.  See
735d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // bugs 2412348 and 2413490.
736b1b855b71e502459e1571444fb1a9f74883c8330Paul Westbrook                LogUtils.d(LOG_TAG, "Setting Alert Once");
737d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                notification.setOnlyAlertOnce(true);
738d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
739d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
74032fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy            LogUtils.i(LOG_TAG, "Account: %s vibrate: %s",
74126a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                    LogUtils.sanitizeName(LOG_TAG, account.getEmailAddress()),
742ff8553f20964f4c31b0c503a9e1daff6ae08a9c7Scott Kennedy                    Boolean.toString(folderPreferences.isNotificationVibrateEnabled()));
743d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
744d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            int defaults = 0;
745d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
746e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            // Check if any current conversation notifications exist previously.  Only notify if
747e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            // one of them is new.
748e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            boolean hasNewConversationNotification;
749e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            Set<Integer> prevConversationNotifications =
750e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                    sConversationNotificationMap.get(notificationKey);
751e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            if (prevConversationNotifications != null) {
752e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                hasNewConversationNotification = false;
75315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                for (Integer currentNotificationId : msgNotifications.keySet()) {
754e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                    if (!prevConversationNotifications.contains(currentNotificationId)) {
755e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                        hasNewConversationNotification = true;
75615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                        break;
75715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    }
75815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                }
75915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            } else {
760e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                hasNewConversationNotification = true;
76115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            }
76215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
763e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            LogUtils.d(LOG_TAG, "getAttention=%s,oldWhen=%s,hasNewConversationNotification=%s",
764e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                    getAttention, oldWhen, hasNewConversationNotification);
76515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
766d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            /*
767d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy             * We do not want to notify if this is coming back from an Undo notification, hence the
768d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy             * oldWhen check.
769d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy             */
770e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            if (getAttention && oldWhen == 0 && hasNewConversationNotification) {
771d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                final AccountPreferences accountPreferences =
772fd79237327f6803675cad2a64a74411ebda854e3Paul Westbrook                        new AccountPreferences(context, account.getAccountId());
773d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                if (accountPreferences.areNotificationsEnabled()) {
774d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    if (vibrate) {
775d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        defaults |= Notification.DEFAULT_VIBRATE;
776d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    }
777d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
778d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    notification.setSound(TextUtils.isEmpty(ringtoneUri) ? null
779d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            : Uri.parse(ringtoneUri));
78032fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy                    LogUtils.i(LOG_TAG, "New email in %s vibrateWhen: %s, playing notification: %s",
78126a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                            LogUtils.sanitizeName(LOG_TAG, account.getEmailAddress()), vibrate,
78226a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                            ringtoneUri);
783d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
784d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
785d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
78632fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy            // TODO(skennedy) Why do we do any of the above if we're just going to bail here?
787d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (eventInfoConfigured) {
788d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                defaults |= Notification.DEFAULT_LIGHTS;
789d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                notification.setDefaults(defaults);
790d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
791d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                if (oldWhen != 0) {
792d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    // We do not want to display the ticker again if we are re-displaying this
793d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    // notification (like from an Undo notification)
794d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    notification.setTicker(null);
795d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
796d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
797439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau                notification.extend(wearableExtender);
7984e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux
7994e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                // create the *public* form of the *private* notification we have been assembling
8004e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                final Notification publicNotification = createPublicNotification(context, account,
8014e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                        folder, when, unseenCount, unreadCount, clickIntent);
802c41edc70b7de16b7e43f77680d51846daae1fa2aRohan Shah
8034e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                notification.setPublicVersion(publicNotification);
8044e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux
805c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau                nm.notify(notificationId, notification.build());
80615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
807e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                if (prevConversationNotifications != null) {
80815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    Set<Integer> currentNotificationIds = msgNotifications.keySet();
809e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                    for (Integer prevConversationNotificationId : prevConversationNotifications) {
810e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                        if (!currentNotificationIds.contains(prevConversationNotificationId)) {
811e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            nm.cancel(prevConversationNotificationId);
812e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            LogUtils.d(LOG_TAG, "canceling conversation notification %s",
813e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                                    prevConversationNotificationId);
81415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                        }
81515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    }
81615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                }
81715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
818c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau                for (Map.Entry<Integer, NotificationBuilders> entry : msgNotifications.entrySet()) {
819c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau                    NotificationBuilders builders = entry.getValue();
820439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau                    builders.notifBuilder.extend(builders.wearableNotifBuilder);
821c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau                    nm.notify(entry.getKey(), builders.notifBuilder.build());
822e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                    LogUtils.d(LOG_TAG, "notifying conversation notification %s", entry.getKey());
82315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                }
82415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
825e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                Set<Integer> conversationNotificationIds = new HashSet<Integer>();
826e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                conversationNotificationIds.addAll(msgNotifications.keySet());
827e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                sConversationNotificationMap.put(notificationKey, conversationNotificationIds);
82832fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy            } else {
82932fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy                LogUtils.i(LOG_TAG, "event info not configured - not notifying");
830d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
831d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } finally {
832d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (cursor != null) {
833d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                cursor.close();
834d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
835d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
836d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
837d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
8384e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux    /**
8394e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * Build and return a redacted form of a notification using the given information. This redacted
8404e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * form is shown above the lock screen and is devoid of sensitive information.
8414e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     *
8424e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param context a context used to construct the notification
8434e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param account the account for which the notification is being generated
8444e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param folder the folder for which the notification is being generated
8454e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param when the timestamp of the notification
8464e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param unseenCount the number of unseen messages
8474e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param unreadCount the number of unread messages
8484e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param clickIntent the behavior to invoke if the notification is tapped (note that the user
8494e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     *                    will be prompted to unlock the device before the behavior is executed)
8504e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @return the redacted form of the notification to display above the lock screen
8514e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     */
8524e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux    private static Notification createPublicNotification(Context context, Account account,
8534e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux            Folder folder, long when, int unseenCount, int unreadCount, PendingIntent clickIntent) {
8544e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux        final boolean multipleUnseen = unseenCount > 1;
85510b39450191f95d37e29526ada7a239dcbc858adRohan Shah
8564e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux        final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
8574e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                .setContentTitle(createTitle(context, unseenCount))
8584d27504d0f328312f6614cb6c535c683c6a36e08Rohan Shah                .setContentText(account.getDisplayName())
8594e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                .setContentIntent(clickIntent)
8604e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                .setNumber(unreadCount)
8614e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
86276fb85a30a41254537c0ad26cabec06030fcdbbaPaul Westbrook                .setCategory(NotificationCompat.CATEGORY_EMAIL)
8634e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                .setWhen(when);
8644e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux
865ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah        if (com.android.mail.utils.Utils.isRunningLOrLater()) {
866160f8a7166daa4e300157527bc3e65ffcd7a0c1cAndrew Sapperstein            builder.setColor(context.getResources().getColor(R.color.notification_icon_color));
867ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah        }
868ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah
8694e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux        // if this public notification summarizes multiple single notifications, mark it as the
8704e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux        // summary notification and generate the same group key as the single notifications
8714e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux        if (multipleUnseen) {
8724e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux            builder.setGroup(createGroupKey(account, folder));
8734e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux            builder.setGroupSummary(true);
874722c8c8e9797a021178c9feb7f2902a2a095ab36Rohan Shah            builder.setSmallIcon(R.drawable.ic_notification_multiple_mail_24dp);
87510b39450191f95d37e29526ada7a239dcbc858adRohan Shah        } else {
876722c8c8e9797a021178c9feb7f2902a2a095ab36Rohan Shah            builder.setSmallIcon(R.drawable.ic_notification_mail_24dp);
8774e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux        }
8784e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux
87910b39450191f95d37e29526ada7a239dcbc858adRohan Shah        return builder.build();
88010b39450191f95d37e29526ada7a239dcbc858adRohan Shah    }
881c41edc70b7de16b7e43f77680d51846daae1fa2aRohan Shah
88210b39450191f95d37e29526ada7a239dcbc858adRohan Shah    /**
8834e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param account the account in which the unread email resides
8844e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param folder the folder in which the unread email resides
8854e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @return a key that groups notifications with common accounts and folders
8864e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     */
8874e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux    private static String createGroupKey(Account account, Folder folder) {
8884e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux        return account.uri.toString() + "/" + folder.folderUri.fullUri;
8894e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux    }
8904e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux
8914e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux    /**
8924e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param context a context used to construct the title
8934e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @param unseenCount the number of unseen messages
8944e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     * @return e.g. "1 new message" or "2 new messages"
8954e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux     */
8964e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux    private static String createTitle(Context context, int unseenCount) {
8974e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux        final Resources resources = context.getResources();
8984e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux        return resources.getQuantityString(R.plurals.new_messages, unseenCount, unseenCount);
8994e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux    }
9004e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux
90105a18446a96fa8ea2ae077b482499abf67366e32Alan Lau    private static PendingIntent createClickPendingIntent(Context context,
90205a18446a96fa8ea2ae077b482499abf67366e32Alan Lau            Intent notificationIntent) {
90305a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        // Amend the click intent with a hint that its source was a notification,
90405a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        // but remove the hint before it's used to generate notification action
90505a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        // intents. This prevents the following sequence:
90605a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        // 1. generate single notification
90705a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        // 2. user clicks reply, then completes Compose activity
90805a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        // 3. main activity launches, gets FROM_NOTIFICATION hint in intent
90905a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        notificationIntent.putExtra(Utils.EXTRA_FROM_NOTIFICATION, true);
91005a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        PendingIntent clickIntent = PendingIntent.getActivity(context, -1, notificationIntent,
91105a18446a96fa8ea2ae077b482499abf67366e32Alan Lau                PendingIntent.FLAG_UPDATE_CURRENT);
91205a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        notificationIntent.removeExtra(Utils.EXTRA_FROM_NOTIFICATION);
91305a18446a96fa8ea2ae077b482499abf67366e32Alan Lau        return clickIntent;
91405a18446a96fa8ea2ae077b482499abf67366e32Alan Lau    }
91505a18446a96fa8ea2ae077b482499abf67366e32Alan Lau
916d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
917d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @return an {@link Intent} which, if launched, will display the corresponding conversation
918d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
91909400efa442422299acf21abe20e3470f9d965abScott Kennedy    private static Intent createViewConversationIntent(final Context context, final Account account,
92009400efa442422299acf21abe20e3470f9d965abScott Kennedy            final Folder folder, final Cursor cursor) {
921d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (folder == null || account == null) {
9222f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy            LogUtils.e(LOG_TAG, "createViewConversationIntent(): "
92313a732747e1cf835b04211fad09b722accf877d5Scott Kennedy                    + "Null account or folder.  account: %s folder: %s", account, folder);
924d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return null;
925d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
926d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
927d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final Intent intent;
928d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
929d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (cursor == null) {
930259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy            intent = Utils.createViewFolderIntent(context, folder.folderUri.fullUri, account);
931d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } else {
932d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // A conversation cursor has been specified, so this intent is intended to be go
933d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // directly to the one new conversation
934d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
935d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Get the Conversation object
936d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Conversation conversation = new Conversation(cursor);
937259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy            intent = Utils.createViewConversationIntent(context, conversation,
938259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                    folder.folderUri.fullUri, account);
939d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
940d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
941d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return intent;
942d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
943d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
94461bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy    private static Bitmap getIcon(final Context context, final int resId) {
94561bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy        final Bitmap cachedIcon = sNotificationIcons.get(resId);
94661bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy        if (cachedIcon != null) {
94761bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy            return cachedIcon;
94861bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy        }
94961bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy
95061bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy        final Bitmap icon = BitmapFactory.decodeResource(context.getResources(), resId);
95161bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy        sNotificationIcons.put(resId, icon);
95261bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy
95361bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy        return icon;
95461bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy    }
95561bd0e84a3b0545a722bbfe931233b47afdf52e2Scott Kennedy
956644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau    private static Bitmap getDefaultWearableBg(Context context) {
957644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        Bitmap bg = sDefaultWearableBg.get();
958644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        if (bg == null) {
959644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau            bg = BitmapFactory.decodeResource(context.getResources(), R.drawable.bg_email);
960c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux            sDefaultWearableBg = new WeakReference<>(bg);
961644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        }
962644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        return bg;
963644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau    }
964644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau
965d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static void configureLatestEventInfoFromConversation(final Context context,
966d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Account account, final FolderPreferences folderPreferences,
967de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            final NotificationCompat.Builder notificationBuilder,
968439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau            final NotificationCompat.WearableExtender wearableExtender,
969c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau            final Map<Integer, NotificationBuilders> msgNotifications,
97015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            final int summaryNotificationId, final Cursor conversationCursor,
971d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final PendingIntent clickIntent, final Intent notificationIntent,
972b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler            final int unreadCount, final int unseenCount,
973de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            final Folder folder, final long when, final ContactFetcher contactFetcher) {
974d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final Resources res = context.getResources();
975c41edc70b7de16b7e43f77680d51846daae1fa2aRohan Shah        final boolean multipleUnseen = unseenCount > 1;
976d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
97732fc8e41dde99760eb769a0d0c17f6c485a0b5cfScott Kennedy        LogUtils.i(LOG_TAG, "Showing notification with unreadCount of %d and unseenCount of %d",
9782f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy                unreadCount, unseenCount);
979d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
980d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        String notificationTicker = null;
981d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
982d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Boolean indicating that this notification is for a non-inbox label.
983259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy        final boolean isInbox = folder.folderUri.fullUri.equals(account.settings.defaultInbox);
984d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
985d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Notification label name for user label notifications.
986d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final String notificationLabelName = isInbox ? null : folder.name;
987d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
988c41edc70b7de16b7e43f77680d51846daae1fa2aRohan Shah        if (multipleUnseen) {
989d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Build the string that describes the number of new messages
9904e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux            final String newMessagesString = createTitle(context, unseenCount);
991d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
992d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // The ticker initially start as the new messages string.
993d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            notificationTicker = newMessagesString;
994d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
995d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // The title of the notification is the new messages string
996de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            notificationBuilder.setContentTitle(newMessagesString);
997d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
998c8bc5d10567d917c73e8fd286ea19a4c7f076d89Scott Kennedy            // TODO(skennedy) Can we remove this check?
999d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (com.android.mail.utils.Utils.isRunningJellybeanOrLater()) {
1000d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // For a new-style notification
1001d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                final int maxNumDigestItems = context.getResources().getInteger(
1002d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        R.integer.max_num_notification_digest_items);
1003d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1004d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // The body of the notification is the account name, or the label name.
1005de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                notificationBuilder.setSubText(
1006c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux                        isInbox ? account.getDisplayName() : notificationLabelName);
1007d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1008d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                final NotificationCompat.InboxStyle digest =
1009de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                        new NotificationCompat.InboxStyle(notificationBuilder);
1010d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
10114e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                // Group by account and folder
10124e75cc92043d9d37aa3bda16ce6b0034b64ed329James Lemieux                final String notificationGroupKey = createGroupKey(account, folder);
1013de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                // Track all senders to later tag them along with the digest notification
10141afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                final HashSet<String> senderAddressesSet = new HashSet<String>();
1015de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                notificationBuilder.setGroup(notificationGroupKey).setGroupSummary(true);
101615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
1017644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                ConfigResult firstResult = null;
1018d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                int numDigestItems = 0;
1019d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                do {
1020d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    final Conversation conversation = new Conversation(conversationCursor);
1021d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1022d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    if (!conversation.read) {
1023d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        boolean multipleUnreadThread = false;
1024d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        // TODO(cwren) extract this pattern into a helper
1025d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1026d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        Cursor cursor = null;
1027d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        MessageCursor messageCursor = null;
1028d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        try {
1029d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            final Uri.Builder uriBuilder = conversation.messageListUri.buildUpon();
1030d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            uriBuilder.appendQueryParameter(
1031d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                    UIProvider.LABEL_QUERY_PARAMETER, notificationLabelName);
1032d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            cursor = context.getContentResolver().query(uriBuilder.build(),
1033d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                    UIProvider.MESSAGE_PROJECTION, null, null, null);
1034d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            messageCursor = new MessageCursor(cursor);
1035d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1036b18d75ad0651b8806773d2b79da35dc725390edbYu Ping Hu                            String from = "";
1037d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            String fromAddress = "";
1038d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            if (messageCursor.moveToPosition(messageCursor.getCount() - 1)) {
1039d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                final Message message = messageCursor.getMessage();
1040d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                fromAddress = message.getFrom();
1041b18d75ad0651b8806773d2b79da35dc725390edbYu Ping Hu                                if (fromAddress == null) {
1042b18d75ad0651b8806773d2b79da35dc725390edbYu Ping Hu                                    fromAddress = "";
1043b18d75ad0651b8806773d2b79da35dc725390edbYu Ping Hu                                }
1044d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                from = getDisplayableSender(fromAddress);
10451afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                                addEmailAddressToSet(fromAddress, senderAddressesSet);
1046d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            }
1047d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            while (messageCursor.moveToPosition(messageCursor.getPosition() - 1)) {
1048d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                final Message message = messageCursor.getMessage();
1049d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                if (!message.read &&
1050d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                        !fromAddress.contentEquals(message.getFrom())) {
1051d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                    multipleUnreadThread = true;
10521afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                                    addEmailAddressToSet(message.getFrom(), senderAddressesSet);
1053d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                }
1054d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            }
1055d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            final SpannableStringBuilder sendersBuilder;
1056d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            if (multipleUnreadThread) {
1057d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                final int sendersLength =
1058d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                        res.getInteger(R.integer.swipe_senders_length);
1059d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1060d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                sendersBuilder = getStyledSenders(context, conversationCursor,
1061c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux                                        sendersLength, account);
1062d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            } else {
106312fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook                                sendersBuilder =
106412fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook                                        new SpannableStringBuilder(getWrappedFromString(from));
1065d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            }
1066d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            final CharSequence digestLine = getSingleMessageInboxLine(context,
1067d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                    sendersBuilder.toString(),
106889bf7ce646a0ee67e0a8ee10d8834bcf54257d97Andrew Sapperstein                                    ConversationItemView.filterTag(context, conversation.subject),
1069edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                                    conversation.getSnippet());
1070d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            digest.addLine(digestLine);
1071d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            numDigestItems++;
107215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
1073e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            // Adding conversation notification for Wear.
1074e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            NotificationCompat.Builder conversationNotif =
107515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                                    new NotificationCompat.Builder(context);
107676fb85a30a41254537c0ad26cabec06030fcdbbaPaul Westbrook                            conversationNotif.setCategory(NotificationCompat.CATEGORY_EMAIL);
1077c41edc70b7de16b7e43f77680d51846daae1fa2aRohan Shah
107810b39450191f95d37e29526ada7a239dcbc858adRohan Shah                            conversationNotif.setSmallIcon(
1079722c8c8e9797a021178c9feb7f2902a2a095ab36Rohan Shah                                    R.drawable.ic_notification_multiple_mail_24dp);
1080c41edc70b7de16b7e43f77680d51846daae1fa2aRohan Shah
1081ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah                            if (com.android.mail.utils.Utils.isRunningLOrLater()) {
1082ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah                                conversationNotif.setColor(
1083ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah                                        context.getResources()
1084160f8a7166daa4e300157527bc3e65ffcd7a0c1cAndrew Sapperstein                                                .getColor(R.color.notification_icon_color));
1085ccec014e1f11770dd406c2421fdb48a6eccad86aRohan Shah                            }
1086e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            conversationNotif.setContentText(digestLine);
1087e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            Intent conversationNotificationIntent = createViewConversationIntent(
1088e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                                    context, account, folder, conversationCursor);
1089e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            PendingIntent conversationClickIntent = createClickPendingIntent(
1090e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                                    context, conversationNotificationIntent);
1091e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            conversationNotif.setContentIntent(conversationClickIntent);
1092e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            conversationNotif.setAutoCancel(true);
1093e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau
1094c92dc65f64e8b09ee78ba2fa799d7d3627373913Alan Lau                            // Conversations are sorted in descending order, but notification sort
1095c92dc65f64e8b09ee78ba2fa799d7d3627373913Alan Lau                            // key is in ascending order.  Invert the order key to get the right
1096c92dc65f64e8b09ee78ba2fa799d7d3627373913Alan Lau                            // order.  Left pad 19 zeros because it's a long.
1097c92dc65f64e8b09ee78ba2fa799d7d3627373913Alan Lau                            String groupSortKey = String.format("%019d",
1098c92dc65f64e8b09ee78ba2fa799d7d3627373913Alan Lau                                    (Long.MAX_VALUE - conversation.orderKey));
1099e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            conversationNotif.setGroup(notificationGroupKey);
1100e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            conversationNotif.setSortKey(groupSortKey);
110146ec24391831ee15185dccc8fb1220529f767694Alan Lau                            conversationNotif.setWhen(conversation.dateMs);
1102439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau
1103e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            int conversationNotificationId = getNotificationId(
1104e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                                    summaryNotificationId, conversation.hashCode());
110515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
1106e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            final NotificationCompat.WearableExtender conversationWearExtender =
1107439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau                                    new NotificationCompat.WearableExtender();
11088913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                            final ConfigResult result =
11098913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                                    configureNotifForOneConversation(context, account,
1110e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                                    folderPreferences, conversationNotif, conversationWearExtender,
1111644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                                    conversationCursor, notificationIntent, folder, when, res,
1112e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                                    isInbox, notificationLabelName, conversationNotificationId,
1113de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                                    contactFetcher);
1114e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                            msgNotifications.put(conversationNotificationId,
1115e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                                    NotificationBuilders.of(conversationNotif,
1116e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                                            conversationWearExtender));
1117644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau
1118644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                            if (firstResult == null) {
1119644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                                firstResult = result;
1120644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                            }
1121d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        } finally {
1122d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            if (messageCursor != null) {
1123d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                messageCursor.close();
1124d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            }
1125d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            if (cursor != null) {
1126d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                cursor.close();
1127d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            }
1128d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        }
1129d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    }
1130d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                } while (numDigestItems <= maxNumDigestItems && conversationCursor.moveToNext());
1131644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau
1132de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                // Tag main digest notification with the senders
11331afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                tagNotificationsWithPeople(notificationBuilder, senderAddressesSet);
1134de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah
1135644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                if (firstResult != null && firstResult.contactIconInfo != null) {
1136439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau                    wearableExtender.setBackground(firstResult.contactIconInfo.wearableBg);
1137644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                } else {
1138644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                    LogUtils.w(LOG_TAG, "First contact icon is null!");
1139439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau                    wearableExtender.setBackground(getDefaultWearableBg(context));
1140644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                }
1141d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            } else {
1142d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                // The body of the notification is the account name, or the label name.
1143de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                notificationBuilder.setContentText(
1144c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux                        isInbox ? account.getDisplayName() : notificationLabelName);
1145d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
1146d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } else {
114715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            // For notifications for a single new conversation, we want to get the information
114815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            // from the conversation
1149d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1150d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Move the cursor to the most recent unread conversation
1151d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            seekToLatestUnreadConversation(conversationCursor);
1152d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
11538913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            final ConfigResult result = configureNotifForOneConversation(context, account,
1154de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                    folderPreferences, notificationBuilder, wearableExtender, conversationCursor,
1155c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux                    notificationIntent, folder, when, res, isInbox, notificationLabelName,
1156de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                    summaryNotificationId, contactFetcher);
1157644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau            notificationTicker = result.notificationTicker;
1158644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau
115985003dc5fe29370cd3dfe6a2551b04284bfef2b7Jay Shrauner            if (result.contactIconInfo != null) {
116085003dc5fe29370cd3dfe6a2551b04284bfef2b7Jay Shrauner                wearableExtender.setBackground(result.contactIconInfo.wearableBg);
116185003dc5fe29370cd3dfe6a2551b04284bfef2b7Jay Shrauner            } else {
116285003dc5fe29370cd3dfe6a2551b04284bfef2b7Jay Shrauner                wearableExtender.setBackground(getDefaultWearableBg(context));
116385003dc5fe29370cd3dfe6a2551b04284bfef2b7Jay Shrauner            }
116415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        }
1165d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
116615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        // Build the notification ticker
116715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        if (notificationLabelName != null && notificationTicker != null) {
116815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            // This is a per label notification, format the ticker with that information
116915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            notificationTicker = res.getString(R.string.label_notification_ticker,
117015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    notificationLabelName, notificationTicker);
117115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        }
1172d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
117315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        if (notificationTicker != null) {
117415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            // If we didn't generate a notification ticker, it will default to account name
1175de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            notificationBuilder.setTicker(notificationTicker);
117615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        }
117715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
117815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        // Set the number in the notification
117915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        if (unreadCount > 1) {
1180de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            notificationBuilder.setNumber(unreadCount);
118115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        }
1182d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1183de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah        notificationBuilder.setContentIntent(clickIntent);
118415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau    }
1185d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
118615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau    /**
118715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau     * Configure the notification for one conversation.  When there are multiple conversations,
118815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau     * this method is used to configure bundled notification for Android Wear.
118915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau     */
1190644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau    private static ConfigResult configureNotifForOneConversation(Context context,
1191644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau            Account account, FolderPreferences folderPreferences,
1192de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            NotificationCompat.Builder notificationBuilder,
1193439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau            NotificationCompat.WearableExtender wearExtender, Cursor conversationCursor,
119415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            Intent notificationIntent, Folder folder, long when, Resources res,
11958913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            boolean isInbox, String notificationLabelName, int notificationId,
1196de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            final ContactFetcher contactFetcher) {
1197d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
11988913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        final ConfigResult result = new ConfigResult();
1199d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
120015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        final Conversation conversation = new Conversation(conversationCursor);
1201d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1202de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah        // Set of all unique senders for unseen messages
12031afed73f41aafe70badfbe5861099c80bef14183Rohan Shah        final HashSet<String> senderAddressesSet = new HashSet<String>();
120415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        Cursor cursor = null;
120515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        MessageCursor messageCursor = null;
120615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        boolean multipleUnseenThread = false;
120715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        String from = null;
120815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        try {
120915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            final Uri uri = conversation.messageListUri.buildUpon().appendQueryParameter(
121015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    UIProvider.LABEL_QUERY_PARAMETER, folder.persistentId).build();
121115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            cursor = context.getContentResolver().query(uri, UIProvider.MESSAGE_PROJECTION,
121215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    null, null, null);
121315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            messageCursor = new MessageCursor(cursor);
121415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            // Use the information from the last sender in the conversation that triggered
121515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            // this notification.
121615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
121715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            String fromAddress = "";
121815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            if (messageCursor.moveToPosition(messageCursor.getCount() - 1)) {
121915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                final Message message = messageCursor.getMessage();
122015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                fromAddress = message.getFrom();
12219461561d3f0ea554138c3b24207a69306e07d4bcAnthony Lee                if (fromAddress == null) {
12229461561d3f0ea554138c3b24207a69306e07d4bcAnthony Lee                    // No sender. Go back to default value.
12235a5b99b97c06fbe54497af0ea9adb8b14a62f91dJay Shrauner                    LogUtils.e(LOG_TAG, "No sender found for message: %d", message.getId());
12249461561d3f0ea554138c3b24207a69306e07d4bcAnthony Lee                    fromAddress = "";
12259461561d3f0ea554138c3b24207a69306e07d4bcAnthony Lee                }
122615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                from = getDisplayableSender(fromAddress);
12278913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                result.contactIconInfo = getContactIcon(
12288913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                        context, account.getAccountManagerAccount().name, from,
1229de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                        getSenderAddress(fromAddress), folder, contactFetcher);
12301afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                addEmailAddressToSet(fromAddress, senderAddressesSet);
1231de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                notificationBuilder.setLargeIcon(result.contactIconInfo.icon);
123215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            }
1233d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
123415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            // Assume that the last message in this conversation is unread
123515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            int firstUnseenMessagePos = messageCursor.getPosition();
123615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            while (messageCursor.moveToPosition(messageCursor.getPosition() - 1)) {
123715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                final Message message = messageCursor.getMessage();
123815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                final boolean unseen = !message.seen;
123915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                if (unseen) {
124015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    firstUnseenMessagePos = messageCursor.getPosition();
12411afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                    addEmailAddressToSet(message.getFrom(), senderAddressesSet);
124215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    if (!multipleUnseenThread
124315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                            && !fromAddress.contentEquals(message.getFrom())) {
124415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                        multipleUnseenThread = true;
124592b21746be07f31fdbe2aee510f9fcd8726a9cb3Paul Westbrook                    }
124615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                }
124715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            }
1248d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
124989bf7ce646a0ee67e0a8ee10d8834bcf54257d97Andrew Sapperstein            final String subject = ConversationItemView.filterTag(context, conversation.subject);
125089bf7ce646a0ee67e0a8ee10d8834bcf54257d97Andrew Sapperstein
125115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            // TODO(skennedy) Can we remove this check?
125215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            if (Utils.isRunningJellybeanOrLater()) {
125315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // For a new-style notification
1254d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
125515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                if (multipleUnseenThread) {
125615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    // The title of a single conversation is the list of senders.
125715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    int sendersLength = res.getInteger(R.integer.swipe_senders_length);
1258d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
125915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    final SpannableStringBuilder sendersBuilder = getStyledSenders(
1260c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux                            context, conversationCursor, sendersLength, account);
126115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
1262de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                    notificationBuilder.setContentTitle(sendersBuilder);
126315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    // For a single new conversation, the ticker is based on the sender's name.
1264644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                    result.notificationTicker = sendersBuilder.toString();
1265d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                } else {
126615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    from = getWrappedFromString(from);
126715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    // The title of a single message the sender.
1268de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                    notificationBuilder.setContentTitle(from);
126915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    // For a single new conversation, the ticker is based on the sender's name.
1270644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                    result.notificationTicker = from;
127115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                }
1272d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
127315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // The notification content will be the subject of the conversation.
1274de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                notificationBuilder.setContentText(getSingleMessageLittleText(context, subject));
1275d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
127615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // The notification subtext will be the subject of the conversation for inbox
127715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // notifications, or will based on the the label name for user label
127815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // notifications.
1279de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                notificationBuilder.setSubText(isInbox ?
1280c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux                        account.getDisplayName() : notificationLabelName);
1281d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
128215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                final NotificationCompat.BigTextStyle bigText =
1283de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                        new NotificationCompat.BigTextStyle(notificationBuilder);
128415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
128515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // Seek the message cursor to the first unread message
128615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                final Message message;
128715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                if (messageCursor.moveToPosition(firstUnseenMessagePos)) {
128815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    message = messageCursor.getMessage();
128989bf7ce646a0ee67e0a8ee10d8834bcf54257d97Andrew Sapperstein                    bigText.bigText(getSingleMessageBigText(context, subject, message));
129015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                } else {
129115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    LogUtils.e(LOG_TAG, "Failed to load message");
129215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    message = null;
1293d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
129415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
129515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                if (message != null) {
129615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    final Set<String> notificationActions =
129715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                            folderPreferences.getNotificationActions(account);
129815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
129915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                    NotificationActionUtils.addNotificationActions(context, notificationIntent,
1300de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                            notificationBuilder, wearExtender, account, conversation, message,
130115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                            folder, notificationId, when, notificationActions);
1302d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
130315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            } else {
130415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // For an old-style notification
1305d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
130615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // The title of a single conversation notification is built from both the sender
130715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // and subject of the new message.
1308de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                notificationBuilder.setContentTitle(
130989bf7ce646a0ee67e0a8ee10d8834bcf54257d97Andrew Sapperstein                        getSingleMessageNotificationTitle(context, from, subject));
1310d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
131115490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // The notification content will be the subject of the conversation for inbox
131215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // notifications, or will based on the the label name for user label
131315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // notifications.
1314de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                notificationBuilder.setContentText(
1315c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux                        isInbox ? account.getDisplayName() : notificationLabelName);
1316d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
131715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                // For a single new conversation, the ticker is based on the sender's name.
1318644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                result.notificationTicker = from;
131915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            }
1320de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah
13211afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            tagNotificationsWithPeople(notificationBuilder, senderAddressesSet);
132215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        } finally {
132315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            if (messageCursor != null) {
132415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                messageCursor.close();
132515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            }
132615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            if (cursor != null) {
132715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                cursor.close();
132815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            }
1329d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1330644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        return result;
1331d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1332d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1333de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah    /**
13341afed73f41aafe70badfbe5861099c80bef14183Rohan Shah     * Iterates through all senders and adds their respective Uris to the notifications. Each Uri
13351afed73f41aafe70badfbe5861099c80bef14183Rohan Shah     * string consists of the prefix "mailto:" followed by the sender address.
1336de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah     * @param notificationBuilder
13371afed73f41aafe70badfbe5861099c80bef14183Rohan Shah     * @param senderAddressesSet List of unique senders to be tagged with the conversation
1338de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah     */
13391afed73f41aafe70badfbe5861099c80bef14183Rohan Shah    private static void tagNotificationsWithPeople(NotificationCompat.Builder notificationBuilder,
13401afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            HashSet<String> senderAddressesSet) {
13411afed73f41aafe70badfbe5861099c80bef14183Rohan Shah        for (final String sender : senderAddressesSet) {
13421afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            if (TextUtils.isEmpty(sender)) {
13431afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                continue;
1344de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            }
13451afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            // Tag a notification with a person using "mailto:<sender address>"
13461afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            notificationBuilder.addPerson(MailTo.MAILTO_SCHEME.concat(sender));
1347de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah        }
1348de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah    }
1349de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah
135012fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook    private static String getWrappedFromString(String from) {
135112fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook        if (from == null) {
135212fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook            LogUtils.e(LOG_TAG, "null from string in getWrappedFromString");
135312fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook            from = "";
135412fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook        }
1355963c7e4f178037acebddfd13e16fc627ef368c82Andrew Sapperstein        from = sBidiFormatter.unicodeWrap(from);
135612fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook        return from;
135712fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook    }
135812fb02b96c7394172ad9798a9de25eeb7611aafdPaul Westbrook
1359d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static SpannableStringBuilder getStyledSenders(final Context context,
1360c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux            final Cursor conversationCursor, final int maxLength, final Account account) {
1361d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final Conversation conversation = new Conversation(conversationCursor);
1362d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final com.android.mail.providers.ConversationInfo conversationInfo =
1363d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                conversation.conversationInfo;
1364c9e00fcb05c5311633dc3a414b9bf685816b4350James Lemieux        final ArrayList<SpannableString> senders = new ArrayList<>();
1365d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (sNotificationUnreadStyleSpan == null) {
1366d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            sNotificationUnreadStyleSpan = new TextAppearanceSpan(
1367d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    context, R.style.NotificationSendersUnreadTextAppearance);
1368d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            sNotificationReadStyleSpan =
1369d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    new TextAppearanceSpan(context, R.style.NotificationSendersReadTextAppearance);
1370d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1371d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        SendersView.format(context, conversationInfo, "", maxLength, senders, null, null, account,
137210ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux                sNotificationUnreadStyleSpan, sNotificationReadStyleSpan,
137310ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux                false /* showToHeader */, false /* resourceCachingRequired */);
1374d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1375d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return ellipsizeStyledSenders(context, senders);
1376d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1377d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1378d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static String sSendersSplitToken = null;
1379d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static String sElidedPaddingToken = null;
1380d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1381d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static SpannableStringBuilder ellipsizeStyledSenders(final Context context,
1382d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            ArrayList<SpannableString> styledSenders) {
1383d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (sSendersSplitToken == null) {
1384d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            sSendersSplitToken = context.getString(R.string.senders_split_token);
1385d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            sElidedPaddingToken = context.getString(R.string.elided_padding_token);
1386d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1387d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1388d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        SpannableStringBuilder builder = new SpannableStringBuilder();
1389d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        SpannableString prevSender = null;
1390d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        for (SpannableString sender : styledSenders) {
1391d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (sender == null) {
13922f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy                LogUtils.e(LOG_TAG, "null sender iterating over styledSenders");
1393d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                continue;
1394d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
1395d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            CharacterStyle[] spans = sender.getSpans(0, sender.length(), CharacterStyle.class);
1396d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (SendersView.sElidedString.equals(sender.toString())) {
1397d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                prevSender = sender;
1398d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                sender = copyStyles(spans, sElidedPaddingToken + sender + sElidedPaddingToken);
1399d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            } else if (builder.length() > 0
1400d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    && (prevSender == null || !SendersView.sElidedString.equals(prevSender
1401d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            .toString()))) {
1402d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                prevSender = sender;
1403d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                sender = copyStyles(spans, sSendersSplitToken + sender);
1404d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            } else {
1405d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                prevSender = sender;
1406d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
1407d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            builder.append(sender);
1408d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1409d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return builder;
1410d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1411d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1412d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static SpannableString copyStyles(CharacterStyle[] spans, CharSequence newText) {
1413d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        SpannableString s = new SpannableString(newText);
1414d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (spans != null && spans.length > 0) {
1415d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            s.setSpan(spans[0], 0, s.length(), 0);
1416d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1417d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return s;
1418d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1419d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1420d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
1421d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Seeks the cursor to the position of the most recent unread conversation. If no unread
1422d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * conversation is found, the position of the cursor will be restored, and false will be
1423d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * returned.
1424d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
1425d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static boolean seekToLatestUnreadConversation(final Cursor cursor) {
1426d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final int initialPosition = cursor.getPosition();
1427d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        do {
1428d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Conversation conversation = new Conversation(cursor);
1429d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (!conversation.read) {
1430d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                return true;
1431d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
1432d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } while (cursor.moveToNext());
1433d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1434d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Didn't find an unread conversation, reset the position.
1435d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        cursor.moveToPosition(initialPosition);
1436d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return false;
1437d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1438d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1439d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
1440d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Sets the bigtext for a notification for a single new conversation
1441d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     *
1442d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param context
1443d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param senders Sender of the new message that triggered the notification.
1444d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param subject Subject of the new message that triggered the notification
1445d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param snippet Snippet of the new message that triggered the notification
1446d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @return a {@link CharSequence} suitable for use in
1447d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     *         {@link android.support.v4.app.NotificationCompat.BigTextStyle}
1448d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
1449d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static CharSequence getSingleMessageInboxLine(Context context,
1450d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            String senders, String subject, String snippet) {
1451d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // TODO(cwren) finish this step toward commmon code with getSingleMessageBigText
1452d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1453d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final String subjectSnippet = !TextUtils.isEmpty(subject) ? subject : snippet;
1454d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1455d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final TextAppearanceSpan notificationPrimarySpan =
1456d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                new TextAppearanceSpan(context, R.style.NotificationPrimaryText);
1457d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1458d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (TextUtils.isEmpty(senders)) {
1459d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // If the senders are empty, just use the subject/snippet.
1460d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return subjectSnippet;
1461d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } else if (TextUtils.isEmpty(subjectSnippet)) {
1462d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // If the subject/snippet is empty, just use the senders.
1463d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final SpannableString spannableString = new SpannableString(senders);
1464d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            spannableString.setSpan(notificationPrimarySpan, 0, senders.length(), 0);
1465d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1466d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return spannableString;
1467d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } else {
1468d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final String formatString = context.getResources().getString(
1469d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    R.string.multiple_new_message_notification_item);
1470d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final TextAppearanceSpan notificationSecondarySpan =
1471d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    new TextAppearanceSpan(context, R.style.NotificationSecondaryText);
1472d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
14738173cf41d2acf48c7b45ae3e7868fc391aae0359Andrew Sapperstein            // senders is already individually unicode wrapped so it does not need to be done here
1474f58109699c207a2eaeca38d557d9b9a7e7664decAndrew Sapperstein            final String instantiatedString = String.format(formatString,
14758173cf41d2acf48c7b45ae3e7868fc391aae0359Andrew Sapperstein                    senders,
1476963c7e4f178037acebddfd13e16fc627ef368c82Andrew Sapperstein                    sBidiFormatter.unicodeWrap(subjectSnippet));
1477d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1478d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final SpannableString spannableString = new SpannableString(instantiatedString);
1479d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1480d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final boolean isOrderReversed = formatString.indexOf("%2$s") <
1481d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    formatString.indexOf("%1$s");
1482d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final int primaryOffset =
1483d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    (isOrderReversed ? instantiatedString.lastIndexOf(senders) :
1484d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                     instantiatedString.indexOf(senders));
1485d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final int secondaryOffset =
1486d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    (isOrderReversed ? instantiatedString.lastIndexOf(subjectSnippet) :
1487d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                     instantiatedString.indexOf(subjectSnippet));
1488d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            spannableString.setSpan(notificationPrimarySpan,
1489d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    primaryOffset, primaryOffset + senders.length(), 0);
1490d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            spannableString.setSpan(notificationSecondarySpan,
1491d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    secondaryOffset, secondaryOffset + subjectSnippet.length(), 0);
1492d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return spannableString;
1493d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1494d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1495d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1496d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
1497d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Sets the bigtext for a notification for a single new conversation
1498d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param context
1499d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param subject Subject of the new message that triggered the notification
1500f58109699c207a2eaeca38d557d9b9a7e7664decAndrew Sapperstein     * @return a {@link CharSequence} suitable for use in
1501f58109699c207a2eaeca38d557d9b9a7e7664decAndrew Sapperstein     * {@link NotificationCompat.Builder#setContentText}
1502d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
1503d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static CharSequence getSingleMessageLittleText(Context context, String subject) {
1504d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan(
1505d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                context, R.style.NotificationPrimaryText);
1506d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1507d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final SpannableString spannableString = new SpannableString(subject);
1508d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0);
1509d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1510d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return spannableString;
1511d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1512d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1513d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
1514d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Sets the bigtext for a notification for a single new conversation
1515d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     *
1516d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param context
1517d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param subject Subject of the new message that triggered the notification
1518d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param message the {@link Message} to be displayed.
1519d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @return a {@link CharSequence} suitable for use in
1520d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     *         {@link android.support.v4.app.NotificationCompat.BigTextStyle}
1521d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
1522d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static CharSequence getSingleMessageBigText(Context context, String subject,
1523d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final Message message) {
1524d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1525d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan(
1526d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                context, R.style.NotificationPrimaryText);
1527d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
152800d1c3df5d9c33d140c169a2b6ce0494d6028920Rohan Shah        final String snippet = getMessageBodyWithoutElidedText(message);
1529d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1530d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Change multiple newlines (with potential white space between), into a single new line
1531d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final String collapsedSnippet =
1532d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                !TextUtils.isEmpty(snippet) ? snippet.replaceAll("\\n\\s+", "\n") : "";
1533d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1534d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (TextUtils.isEmpty(subject)) {
1535d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // If the subject is empty, just use the snippet.
1536d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return snippet;
1537d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } else if (TextUtils.isEmpty(collapsedSnippet)) {
1538d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // If the snippet is empty, just use the subject.
1539d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final SpannableString spannableString = new SpannableString(subject);
1540d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0);
1541d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1542d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return spannableString;
1543d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } else {
1544d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final String notificationBigTextFormat = context.getResources().getString(
1545d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    R.string.single_new_message_notification_big_text);
1546d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1547d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Localizers may change the order of the parameters, look at how the format
1548d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // string is structured.
1549d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final boolean isSubjectFirst = notificationBigTextFormat.indexOf("%2$s") >
1550d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    notificationBigTextFormat.indexOf("%1$s");
1551d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final String bigText =
1552d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    String.format(notificationBigTextFormat, subject, collapsedSnippet);
1553d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final SpannableString spannableString = new SpannableString(bigText);
1554d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1555d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final int subjectOffset =
1556d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    (isSubjectFirst ? bigText.indexOf(subject) : bigText.lastIndexOf(subject));
1557d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            spannableString.setSpan(notificationSubjectSpan,
1558d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    subjectOffset, subjectOffset + subject.length(), 0);
1559d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1560d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return spannableString;
1561d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1562d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1563d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1564d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
1565d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Gets the title for a notification for a single new conversation
1566d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param context
1567d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param sender Sender of the new message that triggered the notification.
1568d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @param subject Subject of the new message that triggered the notification
1569d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * @return a {@link CharSequence} suitable for use as a {@link Notification} title.
1570d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
1571d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static CharSequence getSingleMessageNotificationTitle(Context context,
1572d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            String sender, String subject) {
1573d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1574d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (TextUtils.isEmpty(subject)) {
1575d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // If the subject is empty, just set the title to the sender's information.
1576d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return sender;
1577d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } else {
1578d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final String notificationTitleFormat = context.getResources().getString(
1579d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    R.string.single_new_message_notification_title);
1580d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1581d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Localizers may change the order of the parameters, look at how the format
1582d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // string is structured.
1583d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final boolean isSubjectLast = notificationTitleFormat.indexOf("%2$s") >
1584d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    notificationTitleFormat.indexOf("%1$s");
1585d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final String titleString = String.format(notificationTitleFormat, sender, subject);
1586d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1587d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Format the string so the subject is using the secondaryText style
1588d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final SpannableString titleSpannable = new SpannableString(titleString);
1589d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1590d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // Find the offset of the subject.
1591d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final int subjectOffset =
1592d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    isSubjectLast ? titleString.lastIndexOf(subject) : titleString.indexOf(subject);
1593d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final TextAppearanceSpan notificationSubjectSpan =
1594d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    new TextAppearanceSpan(context, R.style.NotificationSecondaryText);
1595d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            titleSpannable.setSpan(notificationSubjectSpan,
1596d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    subjectOffset, subjectOffset + subject.length(), 0);
1597d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return titleSpannable;
1598d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1599d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1600d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1601d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
16027f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy     * Clears the notifications for the specified account/folder.
1603d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
16047f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy    public static void clearFolderNotification(Context context, Account account, Folder folder,
16057f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy            final boolean markSeen) {
160626a20758413f0f8629ea71a0943304e3ae740165Tony Mantler        LogUtils.v(LOG_TAG, "Clearing all notifications for %s/%s", account.getEmailAddress(),
160726a20758413f0f8629ea71a0943304e3ae740165Tony Mantler                folder.name);
1608d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final NotificationMap notificationMap = getNotificationMap(context);
1609d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final NotificationKey key = new NotificationKey(account, folder);
1610d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        notificationMap.remove(key);
1611d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        notificationMap.saveNotificationMap(context);
1612d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
161315490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        final NotificationManagerCompat notificationManager =
161415490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                NotificationManagerCompat.from(context);
1615b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler        notificationManager.cancel(getNotificationId(account.getAccountManagerAccount(), folder));
16167f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy
1617e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau        cancelConversationNotifications(key, notificationManager);
161815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
16197f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        if (markSeen) {
16207f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy            markSeen(context, folder);
16217f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        }
16227f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy    }
16237f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy
16247f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy    /**
1625e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau     * Use content resolver to update a conversation.  Should not be called from a main thread.
1626e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau     */
1627e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau    public static void markConversationAsReadAndSeen(Context context, Uri conversationUri) {
1628e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau        LogUtils.v(LOG_TAG, "markConversationAsReadAndSeen=%s", conversationUri);
1629e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau
1630e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau        final ContentValues values = new ContentValues(2);
1631e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau        values.put(UIProvider.ConversationColumns.SEEN, Boolean.TRUE);
1632e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau        values.put(UIProvider.ConversationColumns.READ, Boolean.TRUE);
1633e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau        context.getContentResolver().update(conversationUri, values, null, null);
1634e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau    }
1635e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau
1636e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau    /**
16377f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy     * Clears all notifications for the specified account.
16387f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy     */
1639b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler    public static void clearAccountNotifications(final Context context,
1640b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler            final android.accounts.Account account) {
16412f97af9484bb3a226856c441943c5b0f32910155Scott Kennedy        LogUtils.v(LOG_TAG, "Clearing all notifications for %s", account);
16427f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        final NotificationMap notificationMap = getNotificationMap(context);
16437f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy
16447f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        // Find all NotificationKeys for this account
16457f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        final ImmutableList.Builder<NotificationKey> keyBuilder = ImmutableList.builder();
16467f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy
16477f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        for (final NotificationKey key : notificationMap.keySet()) {
1648b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler            if (account.equals(key.account.getAccountManagerAccount())) {
16497f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy                keyBuilder.add(key);
16507f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy            }
16517f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        }
16527f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy
16537f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        final List<NotificationKey> notificationKeys = keyBuilder.build();
16547f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy
165515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        final NotificationManagerCompat notificationManager =
165615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau                NotificationManagerCompat.from(context);
16577f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy
16587f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        for (final NotificationKey notificationKey : notificationKeys) {
16597f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy            final Folder folder = notificationKey.folder;
16607f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy            notificationManager.cancel(getNotificationId(account, folder));
16617f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy            notificationMap.remove(notificationKey);
166215490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
1663e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            cancelConversationNotifications(notificationKey, notificationManager);
16647f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        }
16657f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy
16667f8aed6bea276d6069abbfbdb25263a9ec92a684Scott Kennedy        notificationMap.saveNotificationMap(context);
1667d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1668d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1669e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau    private static void cancelConversationNotifications(NotificationKey key,
167015490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            NotificationManagerCompat nm) {
1671e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau        final Set<Integer> conversationNotifications = sConversationNotificationMap.get(key);
1672e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau        if (conversationNotifications != null) {
1673e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            for (Integer conversationNotification : conversationNotifications) {
1674e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                nm.cancel(conversationNotification);
167515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau            }
1676e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau            sConversationNotificationMap.remove(key);
167715490239c82cdb4765e87e926c8c1945adedd1edAlan Lau        }
167815490239c82cdb4765e87e926c8c1945adedd1edAlan Lau    }
167915490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
16808913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    private static ContactIconInfo getContactIcon(final Context context, String accountName,
16818913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            final String displayName, final String senderAddress, final Folder folder,
1682de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            final ContactFetcher contactFetcher) {
16838913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        if (Looper.myLooper() == Looper.getMainLooper()) {
16848913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            throw new IllegalStateException(
16858913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                    "getContactIcon should not be called on the main thread.");
16868913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        }
16878913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
1688a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner        final ContactIconInfo contactIconInfo;
1689f47f70414d0c88213f7b8091f67cc335c1212fd6Tony Mantler        if (TextUtils.isEmpty(senderAddress)) {
1690a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            contactIconInfo = new ContactIconInfo();
1691a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner        } else {
1692a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            // Get the ideal size for this icon.
1693a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            final Resources res = context.getResources();
1694a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            final int idealIconHeight =
1695a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                    res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
1696a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            final int idealIconWidth =
1697a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                    res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
1698a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            final int idealWearableBgWidth =
1699a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                    res.getDimensionPixelSize(R.dimen.wearable_background_width);
1700a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            final int idealWearableBgHeight =
1701a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                    res.getDimensionPixelSize(R.dimen.wearable_background_height);
1702a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner
1703de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah            if (contactFetcher != null) {
1704de2f3f40ab2403b89bebd219dcab4d561fd07387Rohan Shah                contactIconInfo = contactFetcher.getContactPhoto(context, accountName,
1705a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                        senderAddress, idealIconWidth, idealIconHeight, idealWearableBgWidth,
1706a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                        idealWearableBgHeight);
1707a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            } else {
1708a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                contactIconInfo = getContactInfo(context, senderAddress, idealIconWidth,
1709a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                        idealIconHeight, idealWearableBgWidth, idealWearableBgHeight);
1710a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            }
17118913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
1712a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            if (contactIconInfo.icon == null) {
1713a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                // Make a colorful tile!
1714a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                final Dimensions dimensions = new Dimensions(idealIconWidth, idealIconHeight,
1715a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner                        Dimensions.SCALE_ONE);
17168913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
1717d267498a916882165afb88e7e42cd898d5e748d0Régis Décamps                contactIconInfo.icon = new LetterTileProvider(context.getResources())
1718d267498a916882165afb88e7e42cd898d5e748d0Régis Décamps                        .getLetterTile(dimensions, displayName, senderAddress);
1719a242bf72f4c5d085a00b723b8cd09e8856eaf265Jay Shrauner            }
172028d44b2bec95381693b203f0515218aaf5418f04Rohan Shah
172128d44b2bec95381693b203f0515218aaf5418f04Rohan Shah            // Only turn the square photo/letter tile into a circle for L and later
172228d44b2bec95381693b203f0515218aaf5418f04Rohan Shah            if (Utils.isRunningLOrLater()) {
1723d267498a916882165afb88e7e42cd898d5e748d0Régis Décamps                contactIconInfo.icon = BitmapUtil.frameBitmapInCircle(contactIconInfo.icon);
172428d44b2bec95381693b203f0515218aaf5418f04Rohan Shah            }
17258913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        }
17268913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
17278913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        if (contactIconInfo.icon == null) {
172810b39450191f95d37e29526ada7a239dcbc858adRohan Shah            // Use anonymous icon due to lack of sender
172910b39450191f95d37e29526ada7a239dcbc858adRohan Shah            contactIconInfo.icon = getIcon(context,
173010b39450191f95d37e29526ada7a239dcbc858adRohan Shah                    R.drawable.ic_notification_anonymous_avatar_32dp);
17318913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        }
17328913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
17338913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        if (contactIconInfo.wearableBg == null) {
17348913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            contactIconInfo.wearableBg = getDefaultWearableBg(context);
17358913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        }
17368913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
17378913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        return contactIconInfo;
17388913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    }
17398913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein
1740d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static ArrayList<Long> findContacts(Context context, Collection<String> addresses) {
1741d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        ArrayList<String> whereArgs = new ArrayList<String>();
1742d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        StringBuilder whereBuilder = new StringBuilder();
1743d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        String[] questionMarks = new String[addresses.size()];
1744d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1745d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        whereArgs.addAll(addresses);
1746d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        Arrays.fill(questionMarks, "?");
1747d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        whereBuilder.append(Email.DATA1 + " IN (").
1748d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                append(TextUtils.join(",", questionMarks)).
1749d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                append(")");
1750d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1751d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        ContentResolver resolver = context.getContentResolver();
1752d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        Cursor c = resolver.query(Email.CONTENT_URI,
1753e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau                new String[] {Email.CONTACT_ID}, whereBuilder.toString(),
1754d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                whereArgs.toArray(new String[0]), null);
1755d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1756d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        ArrayList<Long> contactIds = new ArrayList<Long>();
1757d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (c == null) {
1758d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return contactIds;
1759d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1760d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        try {
1761d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            while (c.moveToNext()) {
1762d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                contactIds.add(c.getLong(0));
1763d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
1764d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        } finally {
1765d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            c.close();
1766d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1767d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return contactIds;
1768d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1769d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
17708913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    public static ContactIconInfo getContactInfo(
17718913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            final Context context, final String senderAddress,
17728913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            final int idealIconWidth, final int idealIconHeight,
17738913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein            final int idealWearableBgWidth, final int idealWearableBgHeight) {
17748913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein        final ContactIconInfo contactIconInfo = new ContactIconInfo();
1775c41edc70b7de16b7e43f77680d51846daae1fa2aRohan Shah        final List<Long> contactIds = findContacts(context, Arrays.asList(
1776c41edc70b7de16b7e43f77680d51846daae1fa2aRohan Shah                new String[]{senderAddress}));
1777d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1778d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (contactIds != null) {
17794046d0695e102368201bb2f3a268a4f85c8bc4dbScott Kennedy            for (final long id : contactIds) {
1780644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                final Uri contactUri = ContentUris.withAppendedId(
1781644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                        ContactsContract.Contacts.CONTENT_URI, id);
17828913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                final InputStream inputStream =
17838913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                        ContactsContract.Contacts.openContactPhotoInputStream(
17848913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                                context.getContentResolver(), contactUri, true /*preferHighres*/);
1785d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1786644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                if (inputStream != null) {
1787d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    try {
17888913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein                        final Bitmap source = BitmapFactory.decodeStream(inputStream);
1789644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                        if (source != null) {
1790644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                            // We should scale this image to fit the intended size
1791644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                            contactIconInfo.icon = Bitmap.createScaledBitmap(source, idealIconWidth,
1792644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                                    idealIconHeight, true);
1793644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau
1794644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                            contactIconInfo.wearableBg = Bitmap.createScaledBitmap(source,
1795644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                                    idealWearableBgWidth, idealWearableBgHeight, true);
1796644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                        }
1797644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau
1798644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                        if (contactIconInfo.icon != null) {
1799644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                            break;
1800d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        }
1801d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    } finally {
1802644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau                        Closeables.closeQuietly(inputStream);
1803d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    }
1804d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
1805d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
1806d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1807c94c80c8b8c92c019f19f2e315dea1ff03ad573fScott Kennedy
1808644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        return contactIconInfo;
1809d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1810d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1811d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static String getMessageBodyWithoutElidedText(final Message message) {
18125e7e88b9f5ac1f239753e13ffb3ee4b249722c9aScott Kennedy        return getMessageBodyWithoutElidedText(message.getBodyAsHtml());
1813d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1814d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1815d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    public static String getMessageBodyWithoutElidedText(String html) {
1816d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (TextUtils.isEmpty(html)) {
1817d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return "";
1818d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1819d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Get the html "tree" for this message body
1820d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final HtmlTree htmlTree = com.android.mail.utils.Utils.getHtmlTree(html);
182177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        htmlTree.setConverterFactory(MESSAGE_CONVERTER_FACTORY);
1822d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1823d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return htmlTree.getPlainText();
1824d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1825d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1826d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    public static void markSeen(final Context context, final Folder folder) {
1827259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy        final Uri uri = folder.folderUri.fullUri;
1828d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1829d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final ContentValues values = new ContentValues(1);
1830d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        values.put(UIProvider.ConversationColumns.SEEN, 1);
1831d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1832d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        context.getContentResolver().update(uri, values, null, null);
1833d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1834d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1835d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
1836d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Returns a displayable string representing
1837d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * the message sender. It has a preference toward showing the name,
1838d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * but will fall back to the address if that is all that is available.
1839d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
1840d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static String getDisplayableSender(String sender) {
1841d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final EmailAddress address = EmailAddress.getEmailAddress(sender);
1842d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1843d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        String displayableSender = address.getName();
1844390cab939304705cc8adeb8e4810abf904fec22fScott Kennedy
1845390cab939304705cc8adeb8e4810abf904fec22fScott Kennedy        if (!TextUtils.isEmpty(displayableSender)) {
1846821e578a71c7015646522e729600618f0ec16fc0Tony Mantler            return Address.decodeAddressPersonal(displayableSender);
1847d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1848390cab939304705cc8adeb8e4810abf904fec22fScott Kennedy
1849390cab939304705cc8adeb8e4810abf904fec22fScott Kennedy        // If that fails, default to the sender address.
1850390cab939304705cc8adeb8e4810abf904fec22fScott Kennedy        displayableSender = address.getAddress();
1851390cab939304705cc8adeb8e4810abf904fec22fScott Kennedy
1852d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // If we were unable to tokenize a name or address,
1853d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // just use whatever was in the sender.
1854d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (TextUtils.isEmpty(displayableSender)) {
1855d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            displayableSender = sender;
1856d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1857d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return displayableSender;
1858d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1859d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1860d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
1861d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Returns only the address portion of a message sender.
1862d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
1863d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static String getSenderAddress(String sender) {
1864d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        final EmailAddress address = EmailAddress.getEmailAddress(sender);
1865d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1866d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        String tokenizedAddress = address.getAddress();
1867d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1868d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // If we were unable to tokenize a name or address,
1869d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // just use whatever was in the sender.
1870d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (TextUtils.isEmpty(tokenizedAddress)) {
1871d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            tokenizedAddress = sender;
1872d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1873d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return tokenizedAddress;
1874d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1875d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
18761afed73f41aafe70badfbe5861099c80bef14183Rohan Shah    /**
18771afed73f41aafe70badfbe5861099c80bef14183Rohan Shah     * Given a sender, retrieve the email address. If an email address is extracted, add it to the
18781afed73f41aafe70badfbe5861099c80bef14183Rohan Shah     * input set, otherwise ignore it.
18791afed73f41aafe70badfbe5861099c80bef14183Rohan Shah     * @param sender
18801afed73f41aafe70badfbe5861099c80bef14183Rohan Shah     * @param senderAddressesSet
18811afed73f41aafe70badfbe5861099c80bef14183Rohan Shah     */
18821afed73f41aafe70badfbe5861099c80bef14183Rohan Shah    private static void addEmailAddressToSet(String sender, HashSet<String> senderAddressesSet) {
18831afed73f41aafe70badfbe5861099c80bef14183Rohan Shah        // Only continue if we have a non-empty, non-null sender
18841afed73f41aafe70badfbe5861099c80bef14183Rohan Shah        if (!TextUtils.isEmpty(sender)) {
18851afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            final EmailAddress address = EmailAddress.getEmailAddress(sender);
18861afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            final String senderEmailAddress = address.getAddress();
18871afed73f41aafe70badfbe5861099c80bef14183Rohan Shah
18881afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            // Add to set only if we have a non-empty email address
18891afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            if (!TextUtils.isEmpty(senderEmailAddress)) {
18901afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                senderAddressesSet.add(senderEmailAddress);
18911afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            } else {
18921afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                LogUtils.i(LOG_TAG, "Unable to grab email from \"%s\" for notification tagging",
18931afed73f41aafe70badfbe5861099c80bef14183Rohan Shah                        LogUtils.sanitizeName(LOG_TAG, sender));
18941afed73f41aafe70badfbe5861099c80bef14183Rohan Shah            }
18951afed73f41aafe70badfbe5861099c80bef14183Rohan Shah        }
18961afed73f41aafe70badfbe5861099c80bef14183Rohan Shah    }
18971afed73f41aafe70badfbe5861099c80bef14183Rohan Shah
1898b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler    public static int getNotificationId(final android.accounts.Account account,
1899b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler            final Folder folder) {
1900d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        return 1 ^ account.hashCode() ^ folder.hashCode();
1901d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1902d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1903e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau    private static int getNotificationId(int summaryNotificationId, int conversationHashCode) {
1904e806c9447c7137d2a7a828e7ccdc1f8961aa1c2aAlan Lau        return summaryNotificationId ^ conversationHashCode;
190515490239c82cdb4765e87e926c8c1945adedd1edAlan Lau    }
190615490239c82cdb4765e87e926c8c1945adedd1edAlan Lau
1907d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    private static class NotificationKey {
1908d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public final Account account;
1909d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public final Folder folder;
1910d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1911d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public NotificationKey(Account account, Folder folder) {
1912d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            this.account = account;
1913d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            this.folder = folder;
1914d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1915d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1916d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        @Override
1917d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public boolean equals(Object other) {
1918d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (!(other instanceof NotificationKey)) {
1919d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                return false;
1920d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
1921d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            NotificationKey key = (NotificationKey) other;
1922b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler            return account.getAccountManagerAccount().equals(key.account.getAccountManagerAccount())
1923b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler                    && folder.equals(key.folder);
1924d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1925d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1926d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        @Override
1927d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public String toString() {
192826a20758413f0f8629ea71a0943304e3ae740165Tony Mantler            return account.getDisplayName() + " " + folder.name;
1929d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1930d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1931d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        @Override
1932d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public int hashCode() {
1933b90a813b107d8f43236e2d3fe12f299d94c82ce8Tony Mantler            final int accountHashCode = account.getAccountManagerAccount().hashCode();
1934d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final int folderHashCode = folder.hashCode();
1935d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            return accountHashCode ^ folderHashCode;
1936d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1937d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1938d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1939d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
1940d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * Contains the logic for converting the contents of one HtmlTree into
1941d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * plaintext.
1942d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
1943d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    public static class MailMessagePlainTextConverter extends HtmlTree.DefaultPlainTextConverter {
1944d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        // Strings for parsing html message bodies
1945d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        private static final String ELIDED_TEXT_ELEMENT_NAME = "div";
1946d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        private static final String ELIDED_TEXT_ELEMENT_ATTRIBUTE_NAME = "class";
1947d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        private static final String ELIDED_TEXT_ELEMENT_ATTRIBUTE_CLASS_VALUE = "elided-text";
1948d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1949d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        private static final HTML.Attribute ELIDED_TEXT_ATTRIBUTE =
1950d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                new HTML.Attribute(ELIDED_TEXT_ELEMENT_ATTRIBUTE_NAME, HTML.Attribute.NO_TYPE);
1951d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1952d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        private static final HtmlDocument.Node ELIDED_TEXT_REPLACEMENT_NODE =
1953d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                HtmlDocument.createSelfTerminatingTag(HTML4.BR_ELEMENT, null, null, null);
1954d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1955d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        private int mEndNodeElidedTextBlock = -1;
1956d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1957d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        @Override
1958d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        public void addNode(HtmlDocument.Node n, int nodeNum, int endNum) {
1959d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // If we are in the middle of an elided text block, don't add this node
1960d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (nodeNum < mEndNodeElidedTextBlock) {
1961d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                return;
1962d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            } else if (nodeNum == mEndNodeElidedTextBlock) {
1963d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                super.addNode(ELIDED_TEXT_REPLACEMENT_NODE, nodeNum, endNum);
1964d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                return;
1965d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
1966d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1967d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // If this tag starts another elided text block, we want to remember the end
1968d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (n instanceof HtmlDocument.Tag) {
1969d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                boolean foundElidedTextTag = false;
1970d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                final HtmlDocument.Tag htmlTag = (HtmlDocument.Tag)n;
1971d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                final HTML.Element htmlElement = htmlTag.getElement();
1972d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                if (ELIDED_TEXT_ELEMENT_NAME.equals(htmlElement.getName())) {
1973d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    // Make sure that the class is what is expected
1974d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    final List<HtmlDocument.TagAttribute> attributes =
1975d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            htmlTag.getAttributes(ELIDED_TEXT_ATTRIBUTE);
1976d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    for (HtmlDocument.TagAttribute attribute : attributes) {
1977d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        if (ELIDED_TEXT_ELEMENT_ATTRIBUTE_CLASS_VALUE.equals(
1978d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                                attribute.getValue())) {
1979d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            // Found an "elided-text" div.  Remember information about this tag
1980d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            mEndNodeElidedTextBlock = endNum;
1981d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            foundElidedTextTag = true;
1982d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                            break;
1983d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        }
1984d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    }
1985d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
1986d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1987d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                if (foundElidedTextTag) {
1988d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    return;
1989d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                }
1990d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
1991d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1992c56b233013cf107c702ef9f61305282670ad804aScott Kennedy            super.addNode(n, nodeNum, endNum);
1993d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
1994d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
1995d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
1996d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    /**
1997d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * During account setup in Email, we may not have an inbox yet, so the notification setting had
1998d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * to be stored in {@link AccountPreferences}. If it is still there, we need to move it to the
1999d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     * {@link FolderPreferences} now.
2000d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy     */
2001d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    public static void moveNotificationSetting(final AccountPreferences accountPreferences,
2002d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            final FolderPreferences folderPreferences) {
2003d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        if (accountPreferences.isDefaultInboxNotificationsEnabledSet()) {
2004d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            // If this setting has been changed some other way, don't overwrite it
2005d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            if (!folderPreferences.isNotificationsEnabledSet()) {
2006d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                final boolean notificationsEnabled =
2007d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                        accountPreferences.getDefaultInboxNotificationsEnabled();
2008d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
2009d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                folderPreferences.setNotificationsEnabled(notificationsEnabled);
2010d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            }
2011d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy
2012d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy            accountPreferences.clearDefaultInboxNotificationsEnabled();
2013d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy        }
2014d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy    }
2015c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau
2016c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau    private static class NotificationBuilders {
2017c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau        public final NotificationCompat.Builder notifBuilder;
2018439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau        public final NotificationCompat.WearableExtender wearableNotifBuilder;
2019c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau
2020c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau        private NotificationBuilders(NotificationCompat.Builder notifBuilder,
2021439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau                NotificationCompat.WearableExtender wearableNotifBuilder) {
2022c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau            this.notifBuilder = notifBuilder;
2023c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau            this.wearableNotifBuilder = wearableNotifBuilder;
2024c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau        }
2025c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau
2026c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau        public static NotificationBuilders of(NotificationCompat.Builder notifBuilder,
2027439aa5d7d9226de4682c3697c68c0703b8847518Alan Lau                NotificationCompat.WearableExtender wearableNotifBuilder) {
2028c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau            return new NotificationBuilders(notifBuilder, wearableNotifBuilder);
2029c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau        }
2030c4288f2504ddf203bf558c7ae0143f36f842535bAlan Lau    }
2031644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau
2032644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau    private static class ConfigResult {
2033644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        public String notificationTicker;
2034644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        public ContactIconInfo contactIconInfo;
2035644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau    }
2036644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau
20378913ca6aebe6017efe8060b61b8ad38eda95aa8bAndrew Sapperstein    public static class ContactIconInfo {
2038644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        public Bitmap icon;
2039644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau        public Bitmap wearableBg;
2040644ec1e2e65bedbbfff4dbb396d3e4f95d244c62Alan Lau    }
2041d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy}
2042