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.text.style.CharacterStyle;
286f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport android.util.LruCache;
296f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport android.util.Pair;
306f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
31732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huangimport com.android.mail.R;
32732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huangimport com.android.mail.providers.Conversation;
3353f262e1c93fc20c9c44d46ebb9fc1b5a44cd06bMindy Pereiraimport com.android.mail.providers.Folder;
343b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieuximport com.android.mail.providers.ParticipantInfo;
3554467a21dce72130bb04eb5aa1c9813d88308a80Mindy Pereiraimport com.android.mail.providers.UIProvider;
36259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedyimport com.android.mail.utils.FolderUri;
3707fe8df87bde8732398434e55cce366a8528c181Andy Huangimport com.google.common.annotations.VisibleForTesting;
3807fe8df87bde8732398434e55cce366a8528c181Andy Huangimport com.google.common.base.Objects;
39732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huang
406f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereiraimport java.util.ArrayList;
41b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huangimport java.util.List;
426f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
436f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira/**
446f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * This is the view model for the conversation header. It includes all the
456f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * information needed to layout a conversation header view. Each view model is
466f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira * associated with a conversation and is cached to improve the relayout time.
476f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira */
480944e5e69312666b89fda025430b7cf03bca4305Mindy Pereirapublic class ConversationItemViewModel {
496f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    private static final int MAX_CACHE_SIZE = 100;
506f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
516f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    @VisibleForTesting
520944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira    static LruCache<Pair<String, Long>, ConversationItemViewModel> sConversationHeaderMap
530944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira        = new LruCache<Pair<String, Long>, ConversationItemViewModel>(MAX_CACHE_SIZE);
546f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
5512fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    /**
5612fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira     * The Folder associated with the cache of models.
5712fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira     */
5812fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    private static Folder sCachedModelsFolder;
5912fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira
606f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // The hashcode used to detect if the conversation has changed.
616f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    private int mDataHashCode;
626f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    private int mLayoutHashCode;
636f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
648d69d4e10a9a36ff790babb2f3a098a12d0dc732Marc Blank    // Unread
65103319aaed26bce257de55b2fe93d4f78d3c59b9Scott Kennedy    public boolean unread;
666f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
676f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Date
68ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huang    CharSequence dateText;
69be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein    public boolean showDateText = true;
706f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
716f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Personal level
726f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    Bitmap personalLevelBitmap;
736f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
74103319aaed26bce257de55b2fe93d4f78d3c59b9Scott Kennedy    public Bitmap infoIcon;
75103319aaed26bce257de55b2fe93d4f78d3c59b9Scott Kennedy
76be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein    public String badgeText;
77be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein
78be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein    public int insetPadding = 0;
79be0cb1e421831672f49c30ecb46e6eee765cb661Andrew Sapperstein
806f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Paperclip
816f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    Bitmap paperclip;
826f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
833b2039afbcd8465ab829ecda8a5b207e988e773cScott Kennedy    /** If <code>true</code>, we will not apply any formatting to {@link #sendersText}. */
843b2039afbcd8465ab829ecda8a5b207e988e773cScott Kennedy    public boolean preserveSendersText = false;
853b2039afbcd8465ab829ecda8a5b207e988e773cScott Kennedy
866f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Senders
87103319aaed26bce257de55b2fe93d4f78d3c59b9Scott Kennedy    public String sendersText;
886f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
89d7a4ad903cd77a2cd3ad44c7c456b48c7d47e599Andy Huang    SpannableStringBuilder sendersDisplayText;
90d7a4ad903cd77a2cd3ad44c7c456b48c7d47e599Andy Huang    StaticLayout sendersDisplayLayout;
91d7a4ad903cd77a2cd3ad44c7c456b48c7d47e599Andy Huang
926f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    boolean hasDraftMessage;
936f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
946f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // View Width
956f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    public int viewWidth;
966f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
976f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    // Standard scaled dimen used to detect if the scale of text has changed.
98fbc519e976de0c0debd810ed8c7f77de44d136a1Andy Huang    @Deprecated
996f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    public int standardScaledDimen;
1006f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
1016f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    public long maxMessageId;
1026f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
1034758e980c21027ef1a9cacc9847170290b2ae42eAlice Yang    public int gadgetMode;
1046f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
105732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huang    public Conversation conversation;
106732600e38891db139bae02dc91dd0c5b0987e8e9Andy Huang
107b5080d5335d2aa445a660ad426ab008750be24cbMindy Pereira    public ConversationItemView.ConversationItemFolderDisplayer folderDisplayer;
108b5080d5335d2aa445a660ad426ab008750be24cbMindy Pereira
10954467a21dce72130bb04eb5aa1c9813d88308a80Mindy Pereira    public boolean hasBeenForwarded;
11054467a21dce72130bb04eb5aa1c9813d88308a80Mindy Pereira
11154467a21dce72130bb04eb5aa1c9813d88308a80Mindy Pereira    public boolean hasBeenRepliedTo;
1126f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
1138d83c8a0c9c01ce5ed9381f2765b3ba59948710fMindy Pereira    public boolean isInvite;
1148d83c8a0c9c01ce5ed9381f2765b3ba59948710fMindy Pereira
115b1cbb89f72631bb7e34822b98e8d0842ebd01b83Mindy Pereira    public SpannableStringBuilder messageInfoString;
116b1cbb89f72631bb7e34822b98e8d0842ebd01b83Mindy Pereira
1178a6eb44e688ce082597978def2042ea8376b34b2Mindy Pereira    public int styledMessageInfoStringOffset;
1188a6eb44e688ce082597978def2042ea8376b34b2Mindy Pereira
1190b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira    private String mContentDescription;
1200b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira
1216d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp    /**
1223b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     * Email addresses corresponding to the senders/recipients that will be displayed on the top
1233b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     * line; used to generate the conversation icon.
1243b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     */
1253b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux    public ArrayList<String> displayableEmails;
1263b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux
1273b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux    /**
1283b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     * Display names corresponding to the email address for the senders/recipients that will be
1293b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     * displayed on the top line.
1306d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp     */
1313b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux    public ArrayList<String> displayableNames;
13288acafa03a87f5c84b959697d13b81df8f11a96emindyp
1336f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
1343b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux     * A styled version of the {@link #displayableNames} to be displayed on the top line.
1356d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp     */
1363b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux    public ArrayList<SpannableString> styledNames;
1376d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp
1386d11c8fbca5d54a013d78c85d6eb28f590093e3cmindyp    /**
1396f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns the view model for a conversation. If the model doesn't exist for this conversation
1406f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * null is returned. Note: this should only be called from the UI thread.
1416f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     *
1426f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @param account the account contains this conversation
1436f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @param conversationId the Id of this conversation
1446f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @return the view model for this conversation, or null
1456f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
1466f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    @VisibleForTesting
1470e29e769c74e385342fc5dc8e9c85517771aaa34James Lemieux    static ConversationItemViewModel forConversationIdOrNull(String account, long conversationId) {
1486f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        final Pair<String, Long> key = new Pair<String, Long>(account, conversationId);
1496f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        synchronized(sConversationHeaderMap) {
1506f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            return sConversationHeaderMap.get(key);
1516f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        }
1526f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
1536f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
154c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira    static ConversationItemViewModel forConversation(String account, Conversation conv) {
155c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira        ConversationItemViewModel header = ConversationItemViewModel.forConversationId(account,
156c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira                conv.id);
1573b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.conversation = conv;
1583b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.unread = !conv.read;
1593b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.hasBeenForwarded =
1603b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                (conv.convFlags & UIProvider.ConversationFlags.FORWARDED)
1613b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                == UIProvider.ConversationFlags.FORWARDED;
1623b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.hasBeenRepliedTo =
1633b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                (conv.convFlags & UIProvider.ConversationFlags.REPLIED)
1643b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                == UIProvider.ConversationFlags.REPLIED;
1653b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        header.isInvite =
1663b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                (conv.convFlags & UIProvider.ConversationFlags.CALENDAR_INVITE)
1673b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                == UIProvider.ConversationFlags.CALENDAR_INVITE;
168f9573c5f07bcc05409b5d4c15772884c5313c407Mindy Pereira        return header;
169f9573c5f07bcc05409b5d4c15772884c5313c407Mindy Pereira    }
170f9573c5f07bcc05409b5d4c15772884c5313c407Mindy Pereira
1716f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
1726f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns the view model for a conversation. If this is the first time
1736f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * call, a new view model will be returned. Note: this should only be called
1746f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * from the UI thread.
1756f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     *
1766f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @param account the account contains this conversation
1776f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @param conversationId the Id of this conversation
1786f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * @return the view model for this conversation
1796f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
1800944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira    static ConversationItemViewModel forConversationId(String account, long conversationId) {
1816f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        synchronized(sConversationHeaderMap) {
1820944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira            ConversationItemViewModel header =
1836f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira                    forConversationIdOrNull(account, conversationId);
1846f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            if (header == null) {
1856f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira                final Pair<String, Long> key = new Pair<String, Long>(account, conversationId);
1860944e5e69312666b89fda025430b7cf03bca4305Mindy Pereira                header = new ConversationItemViewModel();
1876f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira                sConversationHeaderMap.put(key, header);
1886f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            }
1896f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            return header;
1906f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        }
1916f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
1926f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
1936f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
1946f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns the hashcode to compare if the data in the header is valid.
1956f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
1963b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy    private static int getHashCode(CharSequence dateText, Object convInfo,
197b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huang            List<Folder> rawFolders, boolean starred, boolean read, int priority,
198b2033d855ab0f13e253e5403ce25989bcbc49488Andy Huang            int sendingState) {
1996f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        if (dateText == null) {
2006f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            return -1;
2016f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        }
2027f55c685376659550ed11b047a78cd8d70158ad9mindyp        return Objects.hashCode(convInfo, dateText, rawFolders, starred, read, priority,
2037f55c685376659550ed11b047a78cd8d70158ad9mindyp                sendingState);
2046f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
2056f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2066f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
207c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira     * Returns the layout hashcode to compare to see if the layout state has changed.
2086f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2096f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    private int getLayoutHashCode() {
2104758e980c21027ef1a9cacc9847170290b2ae42eAlice Yang        return Objects.hashCode(mDataHashCode, viewWidth, standardScaledDimen, gadgetMode);
211c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira    }
212c3efca18f09904f4ce39395169559c5d82bd3d06Mindy Pereira
2136f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
2146f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Marks this header as having valid data and layout.
2156f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2163b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy    void validate() {
2173b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        mDataHashCode = getHashCode(dateText,
218edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                conversation.conversationInfo, conversation.getRawFolders(), conversation.starred,
21995f9e1b167f4945adfa4ca62f4b72a102b621794mindyp                conversation.read, conversation.priority, conversation.sendingState);
2206f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        mLayoutHashCode = getLayoutHashCode();
2216f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
2226f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2236f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
2246f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns if the data in this model is valid.
2256f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2263b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy    boolean isDataValid() {
2273b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        return mDataHashCode == getHashCode(dateText,
228edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                conversation.conversationInfo, conversation.getRawFolders(), conversation.starred,
22995f9e1b167f4945adfa4ca62f4b72a102b621794mindyp                conversation.read, conversation.priority, conversation.sendingState);
2306f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
2316f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2326f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
2336f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Returns if the layout in this model is valid.
2346f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2353b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy    boolean isLayoutValid() {
2363b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy        return isDataValid() && mLayoutHashCode == getLayoutHashCode();
2376f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
2386f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2396f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
2406f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Describes the style of a Senders fragment.
2416f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
2426f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    static class SenderFragment {
2436f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        // Indices that determine which substring of mSendersText we are
2446f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        // displaying.
2456f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        int start;
2466f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        int end;
2476f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2486f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        // The style to apply to the TextPaint object.
2496f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        CharacterStyle style;
2506f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2516f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        // Width of the fragment.
2526f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        int width;
2536f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2546f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        // Ellipsized text.
2556f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        String ellipsizedText;
2566f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2576f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        // Whether the fragment is fixed or not.
2586f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        boolean isFixed;
2596f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2606f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        // Should the fragment be displayed or not.
2616f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        boolean shouldDisplay;
2626f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
2636f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        SenderFragment(int start, int end, CharSequence sendersText, CharacterStyle style,
2646f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira                boolean isFixed) {
2656f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            this.start = start;
2666f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            this.end = end;
2676f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            this.style = style;
2686f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira            this.isFixed = isFixed;
2696f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira        }
2706f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
2716f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira
272dc0617f3478b21dd3324ab10b8c433517ae95460mindyp
273dc0617f3478b21dd3324ab10b8c433517ae95460mindyp    /**
274dc0617f3478b21dd3324ab10b8c433517ae95460mindyp     * Reset the content description; enough content has changed that we need to
275dc0617f3478b21dd3324ab10b8c433517ae95460mindyp     * regenerate it.
276dc0617f3478b21dd3324ab10b8c433517ae95460mindyp     */
277dc0617f3478b21dd3324ab10b8c433517ae95460mindyp    public void resetContentDescription() {
278dc0617f3478b21dd3324ab10b8c433517ae95460mindyp        mContentDescription = null;
279dc0617f3478b21dd3324ab10b8c433517ae95460mindyp    }
280dc0617f3478b21dd3324ab10b8c433517ae95460mindyp
2816f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    /**
2826f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     * Get conversation information to use for accessibility.
2836f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira     */
28410ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux    public CharSequence getContentDescription(Context context, boolean showToHeader) {
2850b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira        if (mContentDescription == null) {
2860b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            // If any are unread, get the first unread sender.
2870b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            // If all are unread, get the first sender.
2880b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            // If all are read, get the last sender.
2893b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux            String participant = "";
2903b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux            String lastParticipant = "";
2913b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux            int last = conversation.conversationInfo.participantInfos != null ?
2923b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                    conversation.conversationInfo.participantInfos.size() - 1 : -1;
293edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            if (last != -1) {
2943b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                lastParticipant = conversation.conversationInfo.participantInfos.get(last).name;
295edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            }
296edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            if (conversation.read) {
2973b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                participant = TextUtils.isEmpty(lastParticipant) ?
29810ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux                        SendersView.getMe(showToHeader /* useObjectMe */) : lastParticipant;
299edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            } else {
3003b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                ParticipantInfo firstUnread = null;
3013b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                for (ParticipantInfo p : conversation.conversationInfo.participantInfos) {
3023b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                    if (!p.readConversation) {
3033b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                        firstUnread = p;
304edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                        break;
305615fb1ca3f2e9f05a388ea509513d3e996343d80mindyp                    }
306dc0617f3478b21dd3324ab10b8c433517ae95460mindyp                }
307edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                if (firstUnread != null) {
3083b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                    participant = TextUtils.isEmpty(firstUnread.name) ?
30910ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux                            SendersView.getMe(showToHeader /* useObjectMe */) : firstUnread.name;
3100b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira                }
3110b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            }
3123b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux            if (TextUtils.isEmpty(participant)) {
313edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler                // Just take the last sender
3143b59b333ad0693f74f9a8cfb24a468a8acbaca8cJames Lemieux                participant = lastParticipant;
315edd6c1a2807d2ade930dfd4622707298dc470d64Tony Mantler            }
31610ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux
31710ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            // the toHeader should read "To: " if requested
31810ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            String toHeader = "";
31910ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            if (showToHeader && !TextUtils.isEmpty(participant)) {
32010ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux                toHeader = SendersView.getFormattedToHeader().toString();
32110ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            }
32210ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux
323dc0617f3478b21dd3324ab10b8c433517ae95460mindyp            boolean isToday = DateUtils.isToday(conversation.dateMs);
3240b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira            String date = DateUtils.getRelativeTimeSpanString(context, conversation.dateMs)
3250b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira                    .toString();
326842aa19e551fb6b7e76fb89c8d39f189b9dacc81mindyp            String readString = context.getString(
327842aa19e551fb6b7e76fb89c8d39f189b9dacc81mindyp                    conversation.read ? R.string.read_string : R.string.unread_string);
328dc0617f3478b21dd3324ab10b8c433517ae95460mindyp            int res = isToday ? R.string.content_description_today : R.string.content_description;
32910ea28ab588d8e922c0f78f0f47fe479739ec2cfJames Lemieux            mContentDescription = context.getString(res, toHeader, participant,
330842aa19e551fb6b7e76fb89c8d39f189b9dacc81mindyp                    conversation.subject, conversation.getSnippet(), date, readString);
3310b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira        }
3320b96043f9f2f740ce4d81dd5036e076e9704aa3bMindy Pereira        return mContentDescription;
3336f92de64bbdf1ea6c4cd9774fc96921a10c266d7Mindy Pereira    }
33412fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira
33512fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    /**
336ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp     * Clear cached header model objects when accessibility changes.
337ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp     */
338ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp
339ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp    public static void onAccessibilityUpdated() {
340ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp        sConversationHeaderMap.evictAll();
341ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp    }
342ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp
343ca87de41285bde382a894b6cb2d13c112c5d7a2fmindyp    /**
34412fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira     * Clear cached header model objects when the folder changes.
34512fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira     */
34612fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    public static void onFolderUpdated(Folder folder) {
347259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy        final FolderUri old = sCachedModelsFolder != null
348259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy                ? sCachedModelsFolder.folderUri : FolderUri.EMPTY;
349259df5b9e11908c8ef7c91483924891dd96b3c27Scott Kennedy        final FolderUri newUri = folder != null ? folder.folderUri : FolderUri.EMPTY;
35012fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira        if (!old.equals(newUri)) {
35112fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira            sCachedModelsFolder = folder;
35212fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira            sConversationHeaderMap.evictAll();
35312fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira        }
35412fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira    }
355b6b174fb3a8f58a2c81e035917ebad8ab45b88aeMindy Pereira}