Home | History | Annotate | Download | only in browse
      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     public Conversation getConversation() {
     77         return mController != null ? mController.getConversation() : null;
     78     }
     79 
     80     // Is the conversation starred?
     81     public boolean isConversationStarred() {
     82         int pos = -1;
     83         while (moveToPosition(++pos)) {
     84             if (getMessage().starred) {
     85                 return true;
     86             }
     87         }
     88         return false;
     89     }
     90 
     91 
     92     public boolean isConversationRead() {
     93         int pos = -1;
     94         while (moveToPosition(++pos)) {
     95             if (!getMessage().read) {
     96                 return false;
     97             }
     98         }
     99         return true;
    100     }
    101     public void markMessagesRead() {
    102         int pos = -1;
    103         while (moveToPosition(++pos)) {
    104             getMessage().read = true;
    105         }
    106     }
    107 
    108     public ConversationMessage getMessageForId(long id) {
    109         if (isClosed()) {
    110             return null;
    111         }
    112 
    113         int pos = -1;
    114         while (moveToPosition(++pos)) {
    115             final ConversationMessage m = getMessage();
    116             if (id == m.id) {
    117                 return m;
    118             }
    119         }
    120         return null;
    121     }
    122 
    123     public int getStateHashCode() {
    124         return getStateHashCode(0);
    125     }
    126 
    127     /**
    128      * Calculate a hash code that compactly summarizes the state of the messages in this cursor,
    129      * with respect to the way the messages are displayed in conversation view. This is not a
    130      * general-purpose hash code. When the state hash codes of a new cursor differs from the
    131      * existing cursor's hash code, the conversation view will re-render from scratch.
    132      *
    133      * @param exceptLast optional number of messages to exclude iterating through at the end of the
    134      * cursor. pass zero to iterate through all messages (or use {@link #getStateHashCode()}).
    135      * @return state hash code of the selected messages in this cursor
    136      */
    137     public int getStateHashCode(int exceptLast) {
    138         int hashCode = 17;
    139         int pos = -1;
    140         final int stopAt = getCount() - exceptLast;
    141         while (moveToPosition(++pos) && pos < stopAt) {
    142             hashCode = 31 * hashCode + getMessage().getStateHashCode();
    143         }
    144         return hashCode;
    145     }
    146 
    147     public int getStatus() {
    148         if (mStatus != null) {
    149             return mStatus;
    150         }
    151 
    152         mStatus = CursorStatus.LOADED;
    153         final Bundle extras = getExtras();
    154         if (extras != null && extras.containsKey(CursorExtraKeys.EXTRA_STATUS)) {
    155             mStatus = extras.getInt(CursorExtraKeys.EXTRA_STATUS);
    156         }
    157         return mStatus;
    158     }
    159 
    160     /**
    161      * Returns true if the cursor is fully loaded. Returns false if the cursor is expected to get
    162      * new messages.
    163      * @return
    164      */
    165     public boolean isLoaded() {
    166         return !CursorStatus.isWaitingForResults(getStatus());
    167     }
    168 
    169     public String getDebugDump() {
    170         StringBuilder sb = new StringBuilder();
    171         sb.append(String.format("conv='%s' status=%d messages:\n",
    172                 mController.getConversation(), getStatus()));
    173         int pos = -1;
    174         while (moveToPosition(++pos)) {
    175             final ConversationMessage m = getMessage();
    176             final List<Uri> attUris = Lists.newArrayList();
    177             for (Attachment a : m.getAttachments()) {
    178                 attUris.add(a.uri);
    179             }
    180             sb.append(String.format(
    181                     "[Message #%d hash=%s uri=%s id=%s serverId=%s from='%s' draftType=%d" +
    182                     " sendingState=%s read=%s starred=%s attUris=%s]\n",
    183                     pos, m.getStateHashCode(), m.uri, m.id, m.serverId, m.getFrom(), m.draftType,
    184                     m.sendingState, m.read, m.starred, attUris));
    185         }
    186         return sb.toString();
    187     }
    188 
    189 }