15e5ac748eadbb17eee84b39a424b8b9270ade46cVikram Aggarwal/*
25e5ac748eadbb17eee84b39a424b8b9270ade46cVikram Aggarwal * Copyright (C) 2012 Google Inc.
35e5ac748eadbb17eee84b39a424b8b9270ade46cVikram Aggarwal * Licensed to The Android Open Source Project.
46f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira *
56f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
66f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * you may not use this file except in compliance with the License.
76f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * You may obtain a copy of the License at
86f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira *
95e5ac748eadbb17eee84b39a424b8b9270ade46cVikram Aggarwal *      http://www.apache.org/licenses/LICENSE-2.0
106f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira *
116f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * Unless required by applicable law or agreed to in writing, software
126f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
136f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
146f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * See the License for the specific language governing permissions and
156f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * limitations under the License.
166f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira */
176f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
1830e2c24b056542f3b1b438aeb798305d1226d0c8Andy Huangpackage com.android.mail.browse;
196f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
206f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport android.content.Context;
216f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport android.graphics.Bitmap;
22b1207e3ec8fe3ce988996722e13b80a4dfdf1c72Mindy Pereiraimport android.text.SpannableString;
236f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport android.text.SpannableStringBuilder;
246f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport android.text.StaticLayout;
25f9573c5f07bcc05409b5d4c15772884c5313c407Mindy Pereiraimport android.text.TextUtils;
260b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereiraimport android.text.format.DateUtils;
276f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport android.util.LruCache;
286f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport android.util.Pair;
296f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
30732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huangimport com.android.mail.R;
31732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huangimport com.android.mail.providers.Conversation;
3253f262e1c93fc20c9c44d46ebb9fc1b5a44cd06bMindy Pereiraimport com.android.mail.providers.Folder;
333b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieuximport com.android.mail.providers.ParticipantInfo;
3454467a21dce72130bb04eb5aa1c9813d88308a80Mindy Pereiraimport com.android.mail.providers.UIProvider;
35259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedyimport com.android.mail.utils.FolderUri;
3607fe8df87bde8732398434e55cce366a8528c181Andy Huangimport com.google.common.annotations.VisibleForTesting;
3707fe8df87bde8732398434e55cce366a8528c181Andy Huangimport com.google.common.base.Objects;
38732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huang
396f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport java.util.ArrayList;
40b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huangimport java.util.List;
416f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
426f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira/**
436f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * This is the view model for the conversation header. It includes all the
446f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * information needed to layout a conversation header view. Each view model is
456f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * associated with a conversation and is cached to improve the relayout time.
466f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira */
470944e5e69312666b89fda025430b7cf03bca4305Mindy Pereirapublic class ConversationItemViewModel {
486f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    private static final int MAX_CACHE_SIZE = 100;
496f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
506f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    @VisibleForTesting
510944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira    static LruCache<Pair<String, Long>, ConversationItemViewModel> sConversationHeaderMap
520944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira        = new LruCache<Pair<String, Long>, ConversationItemViewModel>(MAX_CACHE_SIZE);
536f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
5412fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    /**
5512fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira     * The Folder associated with the cache of models.
5612fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira     */
5712fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    private static Folder sCachedModelsFolder;
5812fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira
596f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // The hashcode used to detect if the conversation has changed.
606f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    private int mDataHashCode;
616f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    private int mLayoutHashCode;
626f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
638d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank    // Unread
64103319aaed26bce257de55b2fe93d4f78d3c59b9Scott Kennedy    public boolean unread;
656f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
666f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Date
67ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huang    CharSequence dateText;
68be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein    public boolean showDateText = true;
696f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
706f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Personal level
716f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    Bitmap personalLevelBitmap;
726f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
73103319aaed26bce257de55b2fe93d4f78d3c59b9Scott Kennedy    public Bitmap infoIcon;
74103319aaed26bce257de55b2fe93d4f78d3c59b9Scott Kennedy
75be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein    public String badgeText;
76be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein
77be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein    public int insetPadding = 0;
78be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein
796f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Paperclip
806f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    Bitmap paperclip;
816f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
823b2039afbcd8465ab829ecda8a5b207e988e773cScott Kennedy    /** If <code>true</code>, we will not apply any formatting to {@link #sendersText}. */
833b2039afbcd8465ab829ecda8a5b207e988e773cScott Kennedy    public boolean preserveSendersText = false;
843b2039afbcd8465ab829ecda8a5b207e988e773cScott Kennedy
856f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Senders
86103319aaed26bce257de55b2fe93d4f78d3c59b9Scott Kennedy    public String sendersText;
876f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
88d7a4ad903cd77a2cd3ad44c7c456b48c7d47e599Andy Huang    SpannableStringBuilder sendersDisplayText;
89d7a4ad903cd77a2cd3ad44c7c456b48c7d47e599Andy Huang    StaticLayout sendersDisplayLayout;
90d7a4ad903cd77a2cd3ad44c7c456b48c7d47e599Andy Huang
916f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    boolean hasDraftMessage;
926f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
936f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // View Width
946f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    public int viewWidth;
956f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
966f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Standard scaled dimen used to detect if the scale of text has changed.
97fbc519e976de0c0debd810ed8c7f77de44d136a1Andy Huang    @Deprecated
986f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    public int standardScaledDimen;
996f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
1006f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    public long maxMessageId;
1016f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
1024758e980c21027ef1a9cacc9847170290b2ae42eAlice Yang    public int gadgetMode;
1036f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
104732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huang    public Conversation conversation;
105732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huang
106b5080d5335d2aa445a660ad426ab008750be24cbMindy Pereira    public ConversationItemView.ConversationItemFolderDisplayer folderDisplayer;
107b5080d5335d2aa445a660ad426ab008750be24cbMindy Pereira
10854467a21dce72130bb04eb5aa1c9813d88308a80Mindy Pereira    public boolean hasBeenForwarded;
10954467a21dce72130bb04eb5aa1c9813d88308a80Mindy Pereira
11054467a21dce72130bb04eb5aa1c9813d88308a80Mindy Pereira    public boolean hasBeenRepliedTo;
1116f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
1128d83c8a0c9c01ce5ed9381f2765b3ba59948710fMindy Pereira    public boolean isInvite;
1138d83c8a0c9c01ce5ed9381f2765b3ba59948710fMindy Pereira
114b1cbb89f72631bb7e34822b98e8d0842ebd01b83Mindy Pereira    public SpannableStringBuilder messageInfoString;
115b1cbb89f72631bb7e34822b98e8d0842ebd01b83Mindy Pereira
1168a6eb44e688ce082597978def2042ea8376b34b2Mindy Pereira    public int styledMessageInfoStringOffset;
1178a6eb44e688ce082597978def2042ea8376b34b2Mindy Pereira
1180b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira    private String mContentDescription;
1190b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira
1206d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp    /**
1213dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao     * The email address and name of the sender whose avatar will be drawn as a conversation icon.
1223b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     */
1233dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao    public final SenderAvatarModel mSenderAvatarModel = new SenderAvatarModel();
1243b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux
1253b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux    /**
1263b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     * Display names corresponding to the email address for the senders/recipients that will be
1273b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     * displayed on the top line.
1286d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp     */
1293dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao    public final ArrayList<String> displayableNames = new ArrayList<>();
13088acafa03a87f5c84b959697d13b81df8f11a96emindyp
1316f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
1323b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     * A styled version of the {@link #displayableNames} to be displayed on the top line.
1336d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp     */
1343dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao    public final ArrayList<SpannableString> styledNames = new ArrayList<>();
1356d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp
1366d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp    /**
1376f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns the view model for a conversation. If the model doesn't exist for this conversation
1386f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * null is returned. Note: this should only be called from the UI thread.
1396f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     *
1406f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @param account the account contains this conversation
1416f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @param conversationId the Id of this conversation
1426f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @return the view model for this conversation, or null
1436f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
1446f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    @VisibleForTesting
1450e29e769c74e385342fc5dc8e9c85517771aaa34James Lemieux    static ConversationItemViewModel forConversationIdOrNull(String account, long conversationId) {
1466f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        final Pair<String, Long> key = new Pair<String, Long>(account, conversationId);
1476f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        synchronized(sConversationHeaderMap) {
1486f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            return sConversationHeaderMap.get(key);
1496f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        }
1506f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
1516f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
152c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira    static ConversationItemViewModel forConversation(String account, Conversation conv) {
153c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira        ConversationItemViewModel header = ConversationItemViewModel.forConversationId(account,
154c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira                conv.id);
1553b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.conversation = conv;
1563b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.unread = !conv.read;
1573b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.hasBeenForwarded =
1583b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                (conv.convFlags & UIProvider.ConversationFlags.FORWARDED)
1593b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                == UIProvider.ConversationFlags.FORWARDED;
1603b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.hasBeenRepliedTo =
1613b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                (conv.convFlags & UIProvider.ConversationFlags.REPLIED)
1623b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                == UIProvider.ConversationFlags.REPLIED;
1633b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.isInvite =
1643b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                (conv.convFlags & UIProvider.ConversationFlags.CALENDAR_INVITE)
1653b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                == UIProvider.ConversationFlags.CALENDAR_INVITE;
166f9573c5f07bcc05409b5d4c15772884c5313c407Mindy Pereira        return header;
167f9573c5f07bcc05409b5d4c15772884c5313c407Mindy Pereira    }
168f9573c5f07bcc05409b5d4c15772884c5313c407Mindy Pereira
1696f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
1706f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns the view model for a conversation. If this is the first time
1716f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * call, a new view model will be returned. Note: this should only be called
1726f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * from the UI thread.
1736f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     *
1746f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @param account the account contains this conversation
1756f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @param conversationId the Id of this conversation
1766f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @return the view model for this conversation
1776f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
1780944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira    static ConversationItemViewModel forConversationId(String account, long conversationId) {
1796f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        synchronized(sConversationHeaderMap) {
1800944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira            ConversationItemViewModel header =
1816f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira                    forConversationIdOrNull(account, conversationId);
1826f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            if (header == null) {
1836f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira                final Pair<String, Long> key = new Pair<String, Long>(account, conversationId);
1840944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira                header = new ConversationItemViewModel();
1856f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira                sConversationHeaderMap.put(key, header);
1866f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            }
1876f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            return header;
1886f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        }
1896f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
1906f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
1916f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
1926f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns the hashcode to compare if the data in the header is valid.
1936f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
1943b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy    private static int getHashCode(CharSequence dateText, Object convInfo,
195b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huang            List<Folder> rawFolders, boolean starred, boolean read, int priority,
196b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huang            int sendingState) {
1976f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        if (dateText == null) {
1986f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            return -1;
1996f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        }
2007f55c685376659550ed11b047a78cd8d70158ad9mindyp        return Objects.hashCode(convInfo, dateText, rawFolders, starred, read, priority,
2017f55c685376659550ed11b047a78cd8d70158ad9mindyp                sendingState);
2026f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
2036f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2046f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
205c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira     * Returns the layout hashcode to compare to see if the layout state has changed.
2066f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2076f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    private int getLayoutHashCode() {
2084758e980c21027ef1a9cacc9847170290b2ae42eAlice Yang        return Objects.hashCode(mDataHashCode, viewWidth, standardScaledDimen, gadgetMode);
209c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira    }
210c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira
2116f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
2126f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Marks this header as having valid data and layout.
2136f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2143b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy    void validate() {
2153b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        mDataHashCode = getHashCode(dateText,
216edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                conversation.conversationInfo, conversation.getRawFolders(), conversation.starred,
21795f9e1b167f4945adfa4ca62f4b72a102b621794mindyp                conversation.read, conversation.priority, conversation.sendingState);
2186f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        mLayoutHashCode = getLayoutHashCode();
2196f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
2206f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2216f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
2226f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns if the data in this model is valid.
2236f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2243b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy    boolean isDataValid() {
2253b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        return mDataHashCode == getHashCode(dateText,
226edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                conversation.conversationInfo, conversation.getRawFolders(), conversation.starred,
22795f9e1b167f4945adfa4ca62f4b72a102b621794mindyp                conversation.read, conversation.priority, conversation.sendingState);
2286f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
2296f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2306f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
2316f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns if the layout in this model is valid.
2326f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2333b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy    boolean isLayoutValid() {
2343b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        return isDataValid() && mLayoutHashCode == getLayoutHashCode();
2356f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
2366f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2376f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
238dc0617f3478b21dd3324ab10b8c433517ae95460mindyp     * Reset the content description; enough content has changed that we need to
239dc0617f3478b21dd3324ab10b8c433517ae95460mindyp     * regenerate it.
240dc0617f3478b21dd3324ab10b8c433517ae95460mindyp     */
241dc0617f3478b21dd3324ab10b8c433517ae95460mindyp    public void resetContentDescription() {
242dc0617f3478b21dd3324ab10b8c433517ae95460mindyp        mContentDescription = null;
243dc0617f3478b21dd3324ab10b8c433517ae95460mindyp    }
244dc0617f3478b21dd3324ab10b8c433517ae95460mindyp
2456f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
2466f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Get conversation information to use for accessibility.
2476f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2482cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao    public CharSequence getContentDescription(Context context, boolean showToHeader,
2492cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao            String foldersDesc) {
2500b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira        if (mContentDescription == null) {
2510b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            // If any are unread, get the first unread sender.
2520b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            // If all are unread, get the first sender.
2530b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            // If all are read, get the last sender.
2543b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux            String participant = "";
2553b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux            String lastParticipant = "";
2563b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux            int last = conversation.conversationInfo.participantInfos != null ?
2573b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                    conversation.conversationInfo.participantInfos.size() - 1 : -1;
258edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            if (last != -1) {
2593b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                lastParticipant = conversation.conversationInfo.participantInfos.get(last).name;
260edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            }
261edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            if (conversation.read) {
2623b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                participant = TextUtils.isEmpty(lastParticipant) ?
26310ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux                        SendersView.getMe(showToHeader /* useObjectMe */) : lastParticipant;
264edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            } else {
2653b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                ParticipantInfo firstUnread = null;
2663b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                for (ParticipantInfo p : conversation.conversationInfo.participantInfos) {
2673b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                    if (!p.readConversation) {
2683b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                        firstUnread = p;
269edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                        break;
270615fb1ca3f2e9f05a388ea509513d3e996343d80mindyp                    }
271dc0617f3478b21dd3324ab10b8c433517ae95460mindyp                }
272edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                if (firstUnread != null) {
2733b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                    participant = TextUtils.isEmpty(firstUnread.name) ?
27410ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux                            SendersView.getMe(showToHeader /* useObjectMe */) : firstUnread.name;
2750b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira                }
2760b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            }
2773b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux            if (TextUtils.isEmpty(participant)) {
278edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                // Just take the last sender
2793b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                participant = lastParticipant;
280edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            }
28110ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux
28210ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            // the toHeader should read "To: " if requested
28310ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            String toHeader = "";
28410ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            if (showToHeader && !TextUtils.isEmpty(participant)) {
28510ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux                toHeader = SendersView.getFormattedToHeader().toString();
28610ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            }
28710ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux
288dc0617f3478b21dd3324ab10b8c433517ae95460mindyp            boolean isToday = DateUtils.isToday(conversation.dateMs);
2890b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            String date = DateUtils.getRelativeTimeSpanString(context, conversation.dateMs)
2900b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira                    .toString();
291842aa19e551fb6b7e76fb89c8d39f189b9dacc81mindyp            String readString = context.getString(
292842aa19e551fb6b7e76fb89c8d39f189b9dacc81mindyp                    conversation.read ? R.string.read_string : R.string.unread_string);
2932cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao            final int res;
2942cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao            if (foldersDesc == null) {
2952cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao                res = isToday ? R.string.content_description_today : R.string.content_description;
2962cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao            } else {
2972cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao                res = isToday ? R.string.content_description_today_with_folders :
2982cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao                        R.string.content_description_with_folders;
2992cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao            }
30010ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            mContentDescription = context.getString(res, toHeader, participant,
3012cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao                    conversation.subject, conversation.getSnippet(), date, readString,
3022cff0881131d3cd4469d3494a3f7bf0ee3f2f09eJin Cao                    foldersDesc);
3030b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira        }
3040b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira        return mContentDescription;
3056f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
30612fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira
30712fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    /**
308ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp     * Clear cached header model objects when accessibility changes.
309ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp     */
310ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp
311ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp    public static void onAccessibilityUpdated() {
312ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp        sConversationHeaderMap.evictAll();
313ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp    }
314ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp
315ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp    /**
31612fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira     * Clear cached header model objects when the folder changes.
31712fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira     */
31812fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    public static void onFolderUpdated(Folder folder) {
319259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy        final FolderUri old = sCachedModelsFolder != null
320259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                ? sCachedModelsFolder.folderUri : FolderUri.EMPTY;
321259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy        final FolderUri newUri = folder != null ? folder.folderUri : FolderUri.EMPTY;
32212fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira        if (!old.equals(newUri)) {
32312fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira            sCachedModelsFolder = folder;
32412fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira            sConversationHeaderMap.evictAll();
32512fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira        }
32612fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    }
3273dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao
3283dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao    /**
3293dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao     * This mutable model stores the name and email address of the sender for whom an avatar will
3303dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao     * be drawn as the conversation icon.
3313dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao     */
3323dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao    public static final class SenderAvatarModel {
3333dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        private String mEmailAddress;
3343dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        private String mName;
3353dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao
3363dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        public String getEmailAddress() {
3373dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao            return mEmailAddress;
3383dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        }
3393dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao
3403dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        public String getName() {
3413dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao            return mName;
3423dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        }
3433dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao
3443dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        /**
3453dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao         * Removes the name and email address of the participant of this avatar.
3463dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao         */
3473dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        public void clear() {
34849b72f2736de522c8479155d08742ae8e4ba3a91James Lemieux            mName = null;
34949b72f2736de522c8479155d08742ae8e4ba3a91James Lemieux            mEmailAddress = null;
3503dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        }
3513dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao
3523dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        /**
3533dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao         * @param name the name of the participant of this avatar
35449b72f2736de522c8479155d08742ae8e4ba3a91James Lemieux         * @param emailAddress the email address of the participant of this avatar; may not be null
3553dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao         */
3563dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        public void populate(String name, String emailAddress) {
35749b72f2736de522c8479155d08742ae8e4ba3a91James Lemieux            if (TextUtils.isEmpty(emailAddress)) {
35849b72f2736de522c8479155d08742ae8e4ba3a91James Lemieux                throw new IllegalArgumentException("email address may not be null or empty");
35949b72f2736de522c8479155d08742ae8e4ba3a91James Lemieux            }
36049b72f2736de522c8479155d08742ae8e4ba3a91James Lemieux
3613dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao            mName = name;
3623dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao            mEmailAddress = emailAddress;
3633dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        }
3643dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao
3653dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        /**
3663dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao         * @return <tt>true</tt> if this model does not yet contain enough data to produce an
3673dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao         *      avatar image; <tt>false</tt> otherwise
3683dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao         */
3693dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        public boolean isNotPopulated() {
37049b72f2736de522c8479155d08742ae8e4ba3a91James Lemieux            return TextUtils.isEmpty(mEmailAddress);
3713dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao        }
3723dbfbc210a607382ba9c150d7ae373ca0508267cJin Cao    }
373ead0f081264b7b9224a17b193f1bf95c45c048a8Jin Cao}
374