/* * Copyright (C) 2012 Google Inc. * Licensed to The Android Open Source Project. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mail.browse; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import com.android.mail.content.ObjectCursor; import com.android.mail.providers.Account; import com.android.mail.providers.Attachment; import com.android.mail.providers.Conversation; import com.android.mail.providers.UIProvider.CursorExtraKeys; import com.android.mail.providers.UIProvider.CursorStatus; import com.android.mail.ui.ConversationUpdater; import com.google.common.collect.Lists; import java.util.List; /** * MessageCursor contains the messages within a conversation; the public methods within should * only be called by the UI thread, as cursor position isn't guaranteed to be maintained */ public class MessageCursor extends ObjectCursor { /** * The current controller that this cursor can use to reference the owning {@link Conversation}, * and a current {@link ConversationUpdater}. Since this cursor will survive a rotation, but * the controller does not, whatever the new controller is MUST update this reference before * using this cursor. */ private ConversationController mController; private Integer mStatus; public interface ConversationController { Conversation getConversation(); ConversationUpdater getListController(); MessageCursor getMessageCursor(); Account getAccount(); } public MessageCursor(Cursor inner) { super(inner, ConversationMessage.FACTORY); } public void setController(ConversationController controller) { mController = controller; } public ConversationMessage getMessage() { final ConversationMessage m = getModel(); // ALWAYS set up each ConversationMessage with the latest controller. // Rotation invalidates everything except this Cursor, its Loader and the cached Messages, // so if we want to continue using them after rotate, we have to ensure their controller // references always point to the current controller. m.setController(mController); return m; } public Conversation getConversation() { return mController != null ? mController.getConversation() : null; } // Is the conversation starred? public boolean isConversationStarred() { int pos = -1; while (moveToPosition(++pos)) { if (getMessage().starred) { return true; } } return false; } public boolean isConversationRead() { int pos = -1; while (moveToPosition(++pos)) { if (!getMessage().read) { return false; } } return true; } public void markMessagesRead() { int pos = -1; while (moveToPosition(++pos)) { getMessage().read = true; } } public ConversationMessage getMessageForId(long id) { if (isClosed()) { return null; } int pos = -1; while (moveToPosition(++pos)) { final ConversationMessage m = getMessage(); if (id == m.id) { return m; } } return null; } public int getStateHashCode() { return getStateHashCode(0); } /** * Calculate a hash code that compactly summarizes the state of the messages in this cursor, * with respect to the way the messages are displayed in conversation view. This is not a * general-purpose hash code. When the state hash codes of a new cursor differs from the * existing cursor's hash code, the conversation view will re-render from scratch. * * @param exceptLast optional number of messages to exclude iterating through at the end of the * cursor. pass zero to iterate through all messages (or use {@link #getStateHashCode()}). * @return state hash code of the selected messages in this cursor */ public int getStateHashCode(int exceptLast) { int hashCode = 17; int pos = -1; final int stopAt = getCount() - exceptLast; while (moveToPosition(++pos) && pos < stopAt) { hashCode = 31 * hashCode + getMessage().getStateHashCode(); } return hashCode; } public int getStatus() { if (mStatus != null) { return mStatus; } mStatus = CursorStatus.LOADED; final Bundle extras = getExtras(); if (extras != null && extras.containsKey(CursorExtraKeys.EXTRA_STATUS)) { mStatus = extras.getInt(CursorExtraKeys.EXTRA_STATUS); } return mStatus; } /** * Returns true if the cursor is fully loaded. Returns false if the cursor is expected to get * new messages. * @return */ public boolean isLoaded() { return !CursorStatus.isWaitingForResults(getStatus()); } public String getDebugDump() { StringBuilder sb = new StringBuilder(); sb.append(String.format("conv='%s' status=%d messages:\n", mController.getConversation(), getStatus())); int pos = -1; while (moveToPosition(++pos)) { final ConversationMessage m = getMessage(); final List attUris = Lists.newArrayList(); for (Attachment a : m.getAttachments()) { attUris.add(a.uri); } sb.append(String.format( "[Message #%d hash=%s uri=%s id=%s serverId=%s from='%s' draftType=%d" + " sendingState=%s read=%s starred=%s attUris=%s]\n", pos, m.getStateHashCode(), m.uri, m.id, m.serverId, m.getFrom(), m.draftType, m.sendingState, m.read, m.starred, attUris)); } return sb.toString(); } }