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}