1 /* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mail.browse; 19 20 import android.database.Cursor; 21 import android.net.Uri; 22 import android.os.Bundle; 23 24 import com.android.mail.content.ObjectCursor; 25 import com.android.mail.providers.Account; 26 import com.android.mail.providers.Attachment; 27 import com.android.mail.providers.Conversation; 28 import com.android.mail.providers.UIProvider.CursorExtraKeys; 29 import com.android.mail.providers.UIProvider.CursorStatus; 30 import com.android.mail.ui.ConversationUpdater; 31 32 import com.google.common.collect.Lists; 33 34 import java.util.List; 35 36 /** 37 * MessageCursor contains the messages within a conversation; the public methods within should 38 * only be called by the UI thread, as cursor position isn't guaranteed to be maintained 39 */ 40 public class MessageCursor extends ObjectCursor<ConversationMessage> { 41 /** 42 * The current controller that this cursor can use to reference the owning {@link Conversation}, 43 * and a current {@link ConversationUpdater}. Since this cursor will survive a rotation, but 44 * the controller does not, whatever the new controller is MUST update this reference before 45 * using this cursor. 46 */ 47 private ConversationController mController; 48 49 private Integer mStatus; 50 51 public interface ConversationController { 52 Conversation getConversation(); 53 ConversationUpdater getListController(); 54 MessageCursor getMessageCursor(); 55 Account getAccount(); 56 } 57 58 public MessageCursor(Cursor inner) { 59 super(inner, ConversationMessage.FACTORY); 60 } 61 62 public void setController(ConversationController controller) { 63 mController = controller; 64 } 65 66 public ConversationMessage getMessage() { 67 final ConversationMessage m = getModel(); 68 // ALWAYS set up each ConversationMessage with the latest controller. 69 // Rotation invalidates everything except this Cursor, its Loader and the cached Messages, 70 // so if we want to continue using them after rotate, we have to ensure their controller 71 // references always point to the current controller. 72 m.setController(mController); 73 return m; 74 } 75 76 // Is the conversation starred? 77 public boolean isConversationStarred() { 78 int pos = -1; 79 while (moveToPosition(++pos)) { 80 if (getMessage().starred) { 81 return true; 82 } 83 } 84 return false; 85 } 86 87 88 public boolean isConversationRead() { 89 int pos = -1; 90 while (moveToPosition(++pos)) { 91 if (!getMessage().read) { 92 return false; 93 } 94 } 95 return true; 96 } 97 public void markMessagesRead() { 98 int pos = -1; 99 while (moveToPosition(++pos)) { 100 getMessage().read = true; 101 } 102 } 103 104 public int getStateHashCode() { 105 return getStateHashCode(0); 106 } 107 108 /** 109 * Calculate a hash code that compactly summarizes the state of the messages in this cursor, 110 * with respect to the way the messages are displayed in conversation view. This is not a 111 * general-purpose hash code. When the state hash codes of a new cursor differs from the 112 * existing cursor's hash code, the conversation view will re-render from scratch. 113 * 114 * @param exceptLast optional number of messages to exclude iterating through at the end of the 115 * cursor. pass zero to iterate through all messages (or use {@link #getStateHashCode()}). 116 * @return state hash code of the selected messages in this cursor 117 */ 118 public int getStateHashCode(int exceptLast) { 119 int hashCode = 17; 120 int pos = -1; 121 final int stopAt = getCount() - exceptLast; 122 while (moveToPosition(++pos) && pos < stopAt) { 123 hashCode = 31 * hashCode + getMessage().getStateHashCode(); 124 } 125 return hashCode; 126 } 127 128 public int getStatus() { 129 if (mStatus != null) { 130 return mStatus; 131 } 132 133 mStatus = CursorStatus.LOADED; 134 final Bundle extras = getExtras(); 135 if (extras != null && extras.containsKey(CursorExtraKeys.EXTRA_STATUS)) { 136 mStatus = extras.getInt(CursorExtraKeys.EXTRA_STATUS); 137 } 138 return mStatus; 139 } 140 141 /** 142 * Returns true if the cursor is fully loaded. Returns false if the cursor is expected to get 143 * new messages. 144 * @return 145 */ 146 public boolean isLoaded() { 147 return !CursorStatus.isWaitingForResults(getStatus()); 148 } 149 150 public String getDebugDump() { 151 StringBuilder sb = new StringBuilder(); 152 sb.append(String.format("conv='%s' status=%d messages:\n", 153 mController.getConversation(), getStatus())); 154 int pos = -1; 155 while (moveToPosition(++pos)) { 156 final ConversationMessage m = getMessage(); 157 final List<Uri> attUris = Lists.newArrayList(); 158 for (Attachment a : m.getAttachments()) { 159 attUris.add(a.uri); 160 } 161 sb.append(String.format( 162 "[Message #%d hash=%s uri=%s id=%s serverId=%s from='%s' draftType=%d" + 163 " isSending=%s read=%s starred=%s attUris=%s]\n", 164 pos, m.getStateHashCode(), m.uri, m.id, m.serverId, m.getFrom(), m.draftType, 165 m.isSending, m.read, m.starred, attUris)); 166 } 167 return sb.toString(); 168 } 169 170 }