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; 21fc0eab16cbb294dd388011dfb2b6b3a846c4731fAndy Huangimport android.net.Uri; 2247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport android.os.Bundle; 237bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 24c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursor; 25bbe74aee04f669990e888095b0d6858dc1e17ce1Mark Weiimport com.android.mail.providers.Account; 266766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huangimport com.android.mail.providers.Attachment; 27839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huangimport com.android.mail.providers.Conversation; 2847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport com.android.mail.providers.UIProvider.CursorExtraKeys; 2947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport com.android.mail.providers.UIProvider.CursorStatus; 30839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huangimport com.android.mail.ui.ConversationUpdater; 31bbe74aee04f669990e888095b0d6858dc1e17ce1Mark Wei 32fc0eab16cbb294dd388011dfb2b6b3a846c4731fAndy Huangimport com.google.common.collect.Lists; 337bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 34fc0eab16cbb294dd388011dfb2b6b3a846c4731fAndy Huangimport java.util.List; 357bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 367bdc3750454efe59617b7df945eadd7e59bee954Andy Huang/** 377bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * MessageCursor contains the messages within a conversation; the public methods within should 387bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * only be called by the UI thread, as cursor position isn't guaranteed to be maintained 397bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 408812d3c50e35c4f2a02d29c35c76082c4ebec0cdAndrew Sappersteinpublic class MessageCursor extends ObjectCursor<ConversationMessage> { 4102133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang /** 4202133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang * The current controller that this cursor can use to reference the owning {@link Conversation}, 4302133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang * and a current {@link ConversationUpdater}. Since this cursor will survive a rotation, but 4402133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang * the controller does not, whatever the new controller is MUST update this reference before 4502133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang * using this cursor. 4602133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang */ 4702133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang private ConversationController mController; 487bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 4947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private Integer mStatus; 5047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 51cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang public interface ConversationController { 5202133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang Conversation getConversation(); 53cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang ConversationUpdater getListController(); 54cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang MessageCursor getMessageCursor(); 55bbe74aee04f669990e888095b0d6858dc1e17ce1Mark Wei Account getAccount(); 56cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 57cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang 5802133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang public MessageCursor(Cursor inner) { 59c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook super(inner, ConversationMessage.FACTORY); 6002133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang } 6102133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang 6202133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang public void setController(ConversationController controller) { 63cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang mController = controller; 647bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 657bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 66839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang public ConversationMessage getMessage() { 67c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook final ConversationMessage m = getModel(); 6802133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang // ALWAYS set up each ConversationMessage with the latest controller. 6902133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang // Rotation invalidates everything except this Cursor, its Loader and the cached Messages, 7002133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang // so if we want to continue using them after rotate, we have to ensure their controller 7102133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang // references always point to the current controller. 7202133aa80d4ff68739a8b8c6f4cc00150a3cfc80Andy Huang m.setController(mController); 737bdc3750454efe59617b7df945eadd7e59bee954Andy Huang return m; 747bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 75cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank 765c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein public Conversation getConversation() { 775c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein return mController != null ? mController.getConversation() : null; 785c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein } 795c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein 80cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank // Is the conversation starred? 81cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank public boolean isConversationStarred() { 82cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank int pos = -1; 83cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank while (moveToPosition(++pos)) { 84423bea25992492efea7d414819729f9eae7ce72eAndy Huang if (getMessage().starred) { 85cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank return true; 86cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank } 87cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank } 88cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank return false; 89cf164d64bcb1da92b427bda99b97f7ec310ef704Marc Blank } 90bf232c3735f65b1a4746943e4a134e59e36f0bdePaul Westbrook 91423bea25992492efea7d414819729f9eae7ce72eAndy Huang 92423bea25992492efea7d414819729f9eae7ce72eAndy Huang public boolean isConversationRead() { 93423bea25992492efea7d414819729f9eae7ce72eAndy Huang int pos = -1; 94423bea25992492efea7d414819729f9eae7ce72eAndy Huang while (moveToPosition(++pos)) { 95423bea25992492efea7d414819729f9eae7ce72eAndy Huang if (!getMessage().read) { 96423bea25992492efea7d414819729f9eae7ce72eAndy Huang return false; 97423bea25992492efea7d414819729f9eae7ce72eAndy Huang } 98423bea25992492efea7d414819729f9eae7ce72eAndy Huang } 99423bea25992492efea7d414819729f9eae7ce72eAndy Huang return true; 100423bea25992492efea7d414819729f9eae7ce72eAndy Huang } 101cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang public void markMessagesRead() { 102cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang int pos = -1; 103cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang while (moveToPosition(++pos)) { 104cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang getMessage().read = true; 105cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 106cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 107cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang 1083c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang public ConversationMessage getMessageForId(long id) { 1093c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang if (isClosed()) { 1103c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang return null; 1113c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang } 1123c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang 1133c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang int pos = -1; 1143c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang while (moveToPosition(++pos)) { 1153c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang final ConversationMessage m = getMessage(); 1163c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang if (id == m.id) { 1173c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang return m; 1183c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang } 1193c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang } 1203c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang return null; 1213c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang } 1223c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang 1236766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang public int getStateHashCode() { 12406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang return getStateHashCode(0); 12506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 12606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 12706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang /** 12806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * Calculate a hash code that compactly summarizes the state of the messages in this cursor, 12906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * with respect to the way the messages are displayed in conversation view. This is not a 13006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * general-purpose hash code. When the state hash codes of a new cursor differs from the 13106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * existing cursor's hash code, the conversation view will re-render from scratch. 13206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * 13306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * @param exceptLast optional number of messages to exclude iterating through at the end of the 13406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * cursor. pass zero to iterate through all messages (or use {@link #getStateHashCode()}). 13506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang * @return state hash code of the selected messages in this cursor 13606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang */ 13706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang public int getStateHashCode(int exceptLast) { 138cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang int hashCode = 17; 139cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang int pos = -1; 14006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang final int stopAt = getCount() - exceptLast; 14106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang while (moveToPosition(++pos) && pos < stopAt) { 142cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang hashCode = 31 * hashCode + getMessage().getStateHashCode(); 143cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 144cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang return hashCode; 145cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang } 146cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang 14747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public int getStatus() { 14847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (mStatus != null) { 14947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return mStatus; 15047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 15147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 15247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mStatus = CursorStatus.LOADED; 15347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final Bundle extras = getExtras(); 15447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (extras != null && extras.containsKey(CursorExtraKeys.EXTRA_STATUS)) { 15547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mStatus = extras.getInt(CursorExtraKeys.EXTRA_STATUS); 15647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 15747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return mStatus; 15847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 15947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 160838c8c9b385d5907d3dc3e52aaeec87bdde2ae22Vikram Aggarwal /** 161838c8c9b385d5907d3dc3e52aaeec87bdde2ae22Vikram Aggarwal * Returns true if the cursor is fully loaded. Returns false if the cursor is expected to get 162838c8c9b385d5907d3dc3e52aaeec87bdde2ae22Vikram Aggarwal * new messages. 163838c8c9b385d5907d3dc3e52aaeec87bdde2ae22Vikram Aggarwal * @return 164838c8c9b385d5907d3dc3e52aaeec87bdde2ae22Vikram Aggarwal */ 16547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public boolean isLoaded() { 166838c8c9b385d5907d3dc3e52aaeec87bdde2ae22Vikram Aggarwal return !CursorStatus.isWaitingForResults(getStatus()); 16747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 16847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 16947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public String getDebugDump() { 17047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang StringBuilder sb = new StringBuilder(); 1719e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang sb.append(String.format("conv='%s' status=%d messages:\n", 1729e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang mController.getConversation(), getStatus())); 17347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang int pos = -1; 17447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang while (moveToPosition(++pos)) { 1756766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang final ConversationMessage m = getMessage(); 176fc0eab16cbb294dd388011dfb2b6b3a846c4731fAndy Huang final List<Uri> attUris = Lists.newArrayList(); 177fc0eab16cbb294dd388011dfb2b6b3a846c4731fAndy Huang for (Attachment a : m.getAttachments()) { 178fc0eab16cbb294dd388011dfb2b6b3a846c4731fAndy Huang attUris.add(a.uri); 179fc0eab16cbb294dd388011dfb2b6b3a846c4731fAndy Huang } 18047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang sb.append(String.format( 1816766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang "[Message #%d hash=%s uri=%s id=%s serverId=%s from='%s' draftType=%d" + 1826a2df258316b267151296556dbbdba20200ecb1fJin Cao " sendingState=%s read=%s starred=%s attUris=%s]\n", 1838960f0af431bc164003e09b3c8981aab808d9ec1Scott Kennedy pos, m.getStateHashCode(), m.uri, m.id, m.serverId, m.getFrom(), m.draftType, 1846a2df258316b267151296556dbbdba20200ecb1fJin Cao m.sendingState, m.read, m.starred, attUris)); 18547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 18647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang return sb.toString(); 18747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 18847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 1897bdc3750454efe59617b7df945eadd7e59bee954Andy Huang}