MessageCursor.java revision 06c0362f59437f3ea2b5832272fb66158bb4b8c0
17bdc3750454efe59617b7df945eadd7e59bee954Andy Huang/* 27bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * Copyright (C) 2012 Google Inc. 37bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * Licensed to The Android Open Source Project. 47bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * 57bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * Licensed under the Apache License, Version 2.0 (the "License"); 67bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * you may not use this file except in compliance with the License. 77bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * You may obtain a copy of the License at 87bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * 97bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * http://www.apache.org/licenses/LICENSE-2.0 107bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * 117bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * Unless required by applicable law or agreed to in writing, software 127bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * distributed under the License is distributed on an "AS IS" BASIS, 137bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 147bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * See the License for the specific language governing permissions and 157bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * limitations under the License. 167bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 177bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 187bdc3750454efe59617b7df945eadd7e59bee954Andy Huangpackage com.android.mail.browse; 197bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 207bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport android.database.Cursor; 217bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport android.database.CursorWrapper; 2247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport android.os.Bundle; 23839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huangimport android.os.Parcelable; 247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 256766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huangimport com.android.mail.providers.Attachment; 26839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huangimport com.android.mail.providers.Conversation; 277bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.providers.Message; 287bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.providers.UIProvider; 2947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport com.android.mail.providers.UIProvider.CursorExtraKeys; 3047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport com.android.mail.providers.UIProvider.CursorStatus; 31839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huangimport com.android.mail.ui.ConversationUpdater; 32cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huangimport com.google.common.base.Objects; 33bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrookimport com.google.common.collect.Maps; 347bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 357bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport java.util.Map; 367bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 377bdc3750454efe59617b7df945eadd7e59bee954Andy Huang/** 387bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * MessageCursor contains the messages within a conversation; the public methods within should 397bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * only be called by the UI thread, as cursor position isn't guaranteed to be maintained 407bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 417bdc3750454efe59617b7df945eadd7e59bee954Andy Huangpublic class MessageCursor extends CursorWrapper { 427bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 43839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang private final Map<Long, ConversationMessage> mCache = Maps.newHashMap(); 44839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang private final Conversation mConversation; 45cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang private final ConversationController mController; 467bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 4747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private Integer mStatus; 4847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 49cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang public interface ConversationController { 50cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang ConversationUpdater getListController(); 51cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang MessageCursor getMessageCursor(); 52cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 53cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang 54839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang /** 55839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang * A message created as part of a conversation view. Sometimes, like during star/unstar, it's 56cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang * handy to have the owning {@link Conversation} for context. 57cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang * 58cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang * <p>This class must remain separate from the {@link MessageCursor} from whence it came, 59cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang * because cursors can be closed by their Loaders at any time. The 60cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang * {@link ConversationController} intermediate is used to obtain the currently opened cursor. 61839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang * 62839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang * <p>(N.B. This is a {@link Parcelable}, so try not to add non-transient fields here. 63839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang * Parcelable state belongs either in {@link Message} or {@link MessageViewState}. The 64839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang * assumption is that this class never needs the state of its extra context saved.) 65839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang */ 66cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang public static final class ConversationMessage extends Message { 67839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 68839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang public final transient Conversation conversation; 69839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 70cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang private final transient ConversationController mController; 71839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 72839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang public ConversationMessage(MessageCursor cursor, Conversation conv, 73cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang ConversationController controller) { 74839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang super(cursor); 75839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang conversation = conv; 76cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang mController = controller; 77cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 78cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang 79cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang /** 80cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang * Returns a hash code based on this message's identity, contents and current state. 81cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang * This is a separate method from hashCode() to allow for an instance of this class to be 82cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang * a functional key in a hash-based data structure. 83cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang * 84cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang */ 85cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang public int getStateHashCode() { 866766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang return Objects.hashCode(uri, read, starred, getAttachmentsStateHashCode()); 876766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang } 886766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang 896766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang private int getAttachmentsStateHashCode() { 906766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang int hash = 0; 916766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang for (Attachment a : getAttachments()) { 926766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang hash += (a.uri != null ? a.uri.hashCode() : 0); 936766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang } 946766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang return hash; 95839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 96839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 97839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang public boolean isConversationStarred() { 98cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang final MessageCursor c = mController.getMessageCursor(); 99cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang return c != null && c.isConversationStarred(); 100839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 101839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 102839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang public void star(boolean newStarred) { 103cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang final ConversationUpdater listController = mController.getListController(); 104cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang if (listController != null) { 105cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang listController.starMessage(this, newStarred); 106cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 107839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 108839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 109839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 110839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 111cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang public MessageCursor(Cursor inner, Conversation conv, ConversationController controller) { 1127bdc3750454efe59617b7df945eadd7e59bee954Andy Huang super(inner); 113839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang mConversation = conv; 114cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang mController = controller; 1157bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 1167bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 117839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang public ConversationMessage getMessage() { 1187bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final long id = getWrappedCursor().getLong(UIProvider.MESSAGE_ID_COLUMN); 119839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang ConversationMessage m = mCache.get(id); 1207bdc3750454efe59617b7df945eadd7e59bee954Andy Huang if (m == null) { 121cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang m = new ConversationMessage(this, mConversation, mController); 1227bdc3750454efe59617b7df945eadd7e59bee954Andy Huang mCache.put(id, m); 1237bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 1247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang return m; 1257bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 126cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank 127cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank // Is the conversation starred? 128cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank public boolean isConversationStarred() { 129cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank int pos = -1; 130cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank while (moveToPosition(++pos)) { 131423bea25992492efea7d414819729f9eae7ce72eAndy Huang if (getMessage().starred) { 132cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank return true; 133cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank } 134cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank } 135cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank return false; 136cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank } 137bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 138423bea25992492efea7d414819729f9eae7ce72eAndy Huang 139423bea25992492efea7d414819729f9eae7ce72eAndy Huang public boolean isConversationRead() { 140423bea25992492efea7d414819729f9eae7ce72eAndy Huang int pos = -1; 141423bea25992492efea7d414819729f9eae7ce72eAndy Huang while (moveToPosition(++pos)) { 142423bea25992492efea7d414819729f9eae7ce72eAndy Huang if (!getMessage().read) { 143423bea25992492efea7d414819729f9eae7ce72eAndy Huang return false; 144423bea25992492efea7d414819729f9eae7ce72eAndy Huang } 145423bea25992492efea7d414819729f9eae7ce72eAndy Huang } 146423bea25992492efea7d414819729f9eae7ce72eAndy Huang return true; 147423bea25992492efea7d414819729f9eae7ce72eAndy Huang } 148cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang public void markMessagesRead() { 149cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang int pos = -1; 150cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang while (moveToPosition(++pos)) { 151cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang getMessage().read = true; 152cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 153cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 154cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang 1556766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang public int getStateHashCode() { 15606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang return getStateHashCode(0); 15706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 15806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 15906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang /** 16006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * Calculate a hash code that compactly summarizes the state of the messages in this cursor, 16106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * with respect to the way the messages are displayed in conversation view. This is not a 16206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * general-purpose hash code. When the state hash codes of a new cursor differs from the 16306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * existing cursor's hash code, the conversation view will re-render from scratch. 16406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * 16506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * @param exceptLast optional number of messages to exclude iterating through at the end of the 16606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * cursor. pass zero to iterate through all messages (or use {@link #getStateHashCode()}). 16706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * @return state hash code of the selected messages in this cursor 16806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang */ 16906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang public int getStateHashCode(int exceptLast) { 170cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang int hashCode = 17; 171cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang int pos = -1; 17206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang final int stopAt = getCount() - exceptLast; 17306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang while (moveToPosition(++pos) && pos < stopAt) { 174cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang hashCode = 31 * hashCode + getMessage().getStateHashCode(); 175cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 176cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang return hashCode; 177cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 178cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang 17947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public int getStatus() { 18047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (mStatus != null) { 18147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return mStatus; 18247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 18347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 18447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mStatus = CursorStatus.LOADED; 18547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final Bundle extras = getExtras(); 18647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (extras != null && extras.containsKey(CursorExtraKeys.EXTRA_STATUS)) { 18747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mStatus = extras.getInt(CursorExtraKeys.EXTRA_STATUS); 18847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 18947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return mStatus; 19047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 19147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 19247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public boolean isLoaded() { 19347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return getStatus() >= CursorStatus.LOADED || getCount() > 0; // FIXME: remove count hack 19447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 19547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 19647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public String getDebugDump() { 19747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang StringBuilder sb = new StringBuilder(); 19847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang sb.append(String.format("conv subj='%s' status=%d messages:\n", 19947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mConversation.subject, getStatus())); 20047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang int pos = -1; 20147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang while (moveToPosition(++pos)) { 2026766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang final ConversationMessage m = getMessage(); 20347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang sb.append(String.format( 2046766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang "[Message #%d hash=%s uri=%s id=%s serverId=%s from='%s' draftType=%d" + 2056766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang " isSending=%s read=%s starred=%s attJson=%s]\n", 2066766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang pos, m.getStateHashCode(), m.uri, m.id, m.serverId, m.from, m.draftType, 2076766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang m.isSending, m.read, m.starred, m.attachmentsJson)); 20847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 20947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return sb.toString(); 21047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 21147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 2127bdc3750454efe59617b7df945eadd7e59bee954Andy Huang}