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