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}