1de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki/*
2de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * Copyright (C) 2010 The Android Open Source Project
3de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *
4de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * Licensed under the Apache License, Version 2.0 (the "License");
5de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * you may not use this file except in compliance with the License.
6de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * You may obtain a copy of the License at
7de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *
8de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *      http://www.apache.org/licenses/LICENSE-2.0
9de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *
10de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * Unless required by applicable law or agreed to in writing, software
11de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * distributed under the License is distributed on an "AS IS" BASIS,
12de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * See the License for the specific language governing permissions and
14de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * limitations under the License.
15de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki */
16de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
17de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onukipackage com.android.email.activity;
18de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
19513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komaloimport android.content.ContentResolver;
20513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komaloimport android.content.Context;
21513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komaloimport android.database.ContentObserver;
22513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komaloimport android.database.Cursor;
23513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komaloimport android.os.Handler;
24513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo
25513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komaloimport com.android.email.MessageListContext;
26513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komaloimport com.android.email.activity.MessageOrderManager.Callback;
27a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent;
284c4e4c3515c3e3300e03f90e02a0c520dc2dff32Makoto Onukiimport com.android.emailcommon.provider.EmailContent.Message;
2922d1a794cd9636634bb31689f53603c0ae64c522Makoto Onukiimport com.android.emailcommon.provider.Mailbox;
302ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onukiimport com.android.emailcommon.utility.DelayedOperations;
314a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onukiimport com.android.emailcommon.utility.EmailAsyncTask;
3231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.utility.Utility;
332ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onukiimport com.google.common.annotations.VisibleForTesting;
3422d1a794cd9636634bb31689f53603c0ae64c522Makoto Onukiimport com.google.common.base.Preconditions;
35de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
36de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki/**
37de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * Used by {@link MessageView} to determine the message-id of the previous/next messages.
38de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *
39de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * All public methods must be called on the main thread.
40de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *
41de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * Call {@link #moveTo} to set the current message id.  As a result,
42de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * either {@link Callback#onMessagesChanged} or {@link Callback#onMessageNotFound} is called.
43de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *
44de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * Use {@link #canMoveToNewer()} and {@link #canMoveToOlder()} to see if there is a newer/older
45de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * message, and {@link #moveToNewer()} and {@link #moveToOlder()} to update the current position.
46de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *
47de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * If the message list changes (e.g. message removed, new message arrived, etc), {@link Callback}
48de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * gets called again.
49de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *
50de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * When an instance is no longer needed, call {@link #close()}, which closes an underlying cursor
51de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * and shuts down an async task.
52de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki *
53de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * TODO: Is there better words than "newer"/"older" that works even if we support other sort orders
54de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki * than timestamp?
55de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki */
56de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onukipublic class MessageOrderManager {
57de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private final Context mContext;
58de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private final ContentResolver mContentResolver;
59de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
60513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo    private final MessageListContext mListContext;
61de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private final ContentObserver mObserver;
62de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private final Callback mCallback;
632ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki    private final DelayedOperations mDelayedOperations;
64de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
65de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private LoadMessageListTask mLoadMessageListTask;
66de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private Cursor mCursor;
67de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
68de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private long mCurrentMessageId = -1;
69de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
70931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki    private int mTotalMessageCount;
71931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki
72931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki    private int mCurrentPosition;
73931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki
74de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private boolean mClosed = false;
75de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
76de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    public interface Callback {
77de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        /**
78de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki         * Called when the message set by {@link MessageOrderManager#moveTo(long)} is found in the
79de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki         * mailbox.  {@link #canMoveToOlder}, {@link #canMoveToNewer}, {@link #moveToOlder} and
80de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki         * {@link #moveToNewer} are ready to be called.
81de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki         */
82de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        public void onMessagesChanged();
83de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        /**
84de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki         * Called when the message set by {@link MessageOrderManager#moveTo(long)} is not found.
85de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki         */
86de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        public void onMessageNotFound();
87de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
88de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
892ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki    /**
902ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki     * Wrapper for {@link Callback}, which uses {@link DelayedOperations#post(Runnable)} to
912ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki     * kick callbacks rather than calling them directly.  This is used to avoid the "nested fragment
922ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki     * transaction" exception.  e.g. {@link #moveTo} is often called during a fragment transaction,
932ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki     * and if the message no longer exists we call {@link #onMessageNotFound}, which most probably
942ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki     * triggers another fragment transaction.
952ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki     */
962ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki    private class PostingCallback implements Callback {
972ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        private final Callback mOriginal;
982ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki
992ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        private PostingCallback(Callback original) {
1002ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki            mOriginal = original;
1012ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        }
1022ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki
1032ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        private final Runnable mOnMessagesChangedRunnable = new Runnable() {
1042ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki            @Override public void run() {
1052ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki                mOriginal.onMessagesChanged();
1062ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki            }
1072ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        };
1082ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki
1092ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        @Override
1102ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        public void onMessagesChanged() {
1112ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki            mDelayedOperations.post(mOnMessagesChangedRunnable);
1122ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        }
1132ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki
1142ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        private final Runnable mOnMessageNotFoundRunnable = new Runnable() {
1152ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki            @Override public void run() {
1162ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki                mOriginal.onMessageNotFound();
1172ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki            }
1182ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        };
1192ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki
1202ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        @Override
1212ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        public void onMessageNotFound() {
1222ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki            mDelayedOperations.post(mOnMessageNotFoundRunnable);
1232ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        }
1242ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki    }
1252ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki
126513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo    public MessageOrderManager(Context context, MessageListContext listContext, Callback callback) {
127513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo        this(context, listContext, callback, new DelayedOperations(Utility.getMainThreadHandler()));
1282ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki    }
1292ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki
1302ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki    @VisibleForTesting
131513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo    MessageOrderManager(Context context, MessageListContext listContext, Callback callback,
1322ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki            DelayedOperations delayedOperations) {
133513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo        Preconditions.checkArgument(listContext.getMailboxId() != Mailbox.NO_MAILBOX);
134de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        mContext = context.getApplicationContext();
135de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        mContentResolver = mContext.getContentResolver();
1362ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        mDelayedOperations = delayedOperations;
137513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo        mListContext = listContext;
1382ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        mCallback = new PostingCallback(callback);
139de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        mObserver = new ContentObserver(getHandlerForContentObserver()) {
140de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki                @Override public void onChange(boolean selfChange) {
141de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki                    if (mClosed) {
142de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki                        return;
143de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki                    }
144de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki                    onContentChanged();
145de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki                }
146de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        };
147de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        startTask();
148de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
149de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
150513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo    public MessageListContext getListContext() {
151513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo        return mListContext;
152513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo    }
153513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo
154d6a2978857e0866d7441f8e140338477545d59a5Makoto Onuki    public long getMailboxId() {
155513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo        return mListContext.getMailboxId();
156d6a2978857e0866d7441f8e140338477545d59a5Makoto Onuki    }
157d6a2978857e0866d7441f8e140338477545d59a5Makoto Onuki
158de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
159931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki     * @return the total number of messages.
160931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki     */
161931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki    public int getTotalMessageCount() {
162931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki        return mTotalMessageCount;
163931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki    }
164931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki
165931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki    /**
166931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki     * @return current cursor position, starting from 0.
167931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki     */
168931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki    public int getCurrentPosition() {
169931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki        return mCurrentPosition;
170931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki    }
171931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki
172931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki    /**
173de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * @return a {@link Handler} for {@link ContentObserver}.
174de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     *
175de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Unit tests override this and return null, so that {@link ContentObserver#onChange} is
176de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * called synchronously.
177de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
178de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /* package */ Handler getHandlerForContentObserver() {
179de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        return new Handler();
180de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
181de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
182de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private boolean isTaskRunning() {
183de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        return mLoadMessageListTask != null;
184de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
185de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
186de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private void startTask() {
187de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        cancelTask();
188de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        startQuery();
189de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
190de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
191de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
192de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Start {@link LoadMessageListTask} to query DB.
193de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Unit tests override this to make tests synchronous and to inject a mock query.
194de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
195de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /* package */ void startQuery() {
196de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        mLoadMessageListTask = new LoadMessageListTask();
1974a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki        mLoadMessageListTask.executeParallel();
198de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
199de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
200de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private void cancelTask() {
201de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        Utility.cancelTaskInterrupt(mLoadMessageListTask);
202de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        mLoadMessageListTask = null;
203de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
204de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
205de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private void closeCursor() {
206de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        if (mCursor != null) {
207de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCursor.close();
208de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCursor = null;
209de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
210de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
211de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
212de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private void setCurrentMessageIdFromCursor() {
213de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        if (mCursor != null) {
214de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCurrentMessageId = mCursor.getLong(EmailContent.ID_PROJECTION_COLUMN);
215de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
216de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
217de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
218de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private void onContentChanged() {
219de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        if (!isTaskRunning()) { // Start only if not running already.
220de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            startTask();
221de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
222de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
223de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
224de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
225de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Shutdown itself and release resources.
226de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
227de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    public void close() {
228de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        mClosed = true;
2292ac164f609faea134b8fa1d6c0ebc90ccda91166Makoto Onuki        mDelayedOperations.removeCallbacks();
230de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        cancelTask();
231de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        closeCursor();
232de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
233de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
234de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    public long getCurrentMessageId() {
235de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        return mCurrentMessageId;
236de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
237de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
238de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
239de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Set the current message id.  As a result, either {@link Callback#onMessagesChanged} or
240de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * {@link Callback#onMessageNotFound} is called.
241de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
242de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    public void moveTo(long messageId) {
243de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        if (mCurrentMessageId != messageId) {
244de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCurrentMessageId = messageId;
245de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            adjustCursorPosition();
246de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
247de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
248de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
249de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private void adjustCursorPosition() {
250931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki        mCurrentPosition = 0;
251de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        if (mCurrentMessageId == -1) {
252de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            return; // Current ID not specified yet.
253de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
254de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        if (mCursor == null) {
255de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            // Task not finished yet.
256de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            // We call adjustCursorPosition() again when we've opened a cursor.
257de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            return;
258de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
259de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        mCursor.moveToPosition(-1);
260de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        while (mCursor.moveToNext()
261de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki                && mCursor.getLong(EmailContent.ID_PROJECTION_COLUMN) != mCurrentMessageId) {
262931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki            mCurrentPosition++;
263de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
264de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        if (mCursor.isAfterLast()) {
265931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki            mCurrentPosition = 0;
266de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCallback.onMessageNotFound(); // Message not found... Already deleted?
267de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        } else {
268de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCallback.onMessagesChanged();
269de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
270de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
271de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
272de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
273de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * @return true if the message set to {@link #moveTo} has an older message in the mailbox.
274de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * false otherwise, or unknown yet.
275de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
276de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    public boolean canMoveToOlder() {
277de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        return (mCursor != null) && !mCursor.isLast();
278de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
279de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
280de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
281de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
282de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * @return true if the message set to {@link #moveTo} has an newer message in the mailbox.
283de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * false otherwise, or unknown yet.
284de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
285de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    public boolean canMoveToNewer() {
286de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        return (mCursor != null) && !mCursor.isFirst();
287de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
288de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
289de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
290de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Move to the older message.
291de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     *
292de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * @return true iif succeed, and {@link Callback#onMessagesChanged} is called.
293de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
294de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    public boolean moveToOlder() {
295de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        if (canMoveToOlder() && mCursor.moveToNext()) {
296931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki            mCurrentPosition++;
297de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            setCurrentMessageIdFromCursor();
298de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCallback.onMessagesChanged();
299de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            return true;
300de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        } else {
301de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            return false;
302de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
303de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
304de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
305de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
306de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Move to the newer message.
307de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     *
308de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * @return true iif succeed, and {@link Callback#onMessagesChanged} is called.
309de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
310de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    public boolean moveToNewer() {
311de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        if (canMoveToNewer() && mCursor.moveToPrevious()) {
312931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki            mCurrentPosition--;
313de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            setCurrentMessageIdFromCursor();
314de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCallback.onMessagesChanged();
315de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            return true;
316de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        } else {
317de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            return false;
318de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
319de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
320de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
321de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
322de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Task to open a Cursor on a worker thread.
323de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
3244a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki    private class LoadMessageListTask extends EmailAsyncTask<Void, Void, Cursor> {
3254a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki        public LoadMessageListTask() {
3264a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki            super(null);
3274a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki        }
3284a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki
329de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        @Override
330de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        protected Cursor doInBackground(Void... params) {
3314a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki            return openNewCursor();
332de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
333de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
334de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        @Override
3354a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki        protected void onCancelled(Cursor cursor) {
3364a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki            if (cursor != null) {
3374a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki                cursor.close();
3384a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki            }
339de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            onCursorOpenDone(null);
340de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
341de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
342de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        @Override
34350d934360d51392ac2aa6f11de4d6e1446cf78c9Makoto Onuki        protected void onSuccess(Cursor cursor) {
3444a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki            onCursorOpenDone(cursor);
345de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
346de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
347de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
348de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
349de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Open a new cursor for a message list.
350de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     *
351de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * This method is called on a worker thread by LoadMessageListTask.
352de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
353de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    private Cursor openNewCursor() {
354de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        final Cursor cursor = mContentResolver.query(EmailContent.Message.CONTENT_URI,
355513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo                EmailContent.ID_PROJECTION,
356513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo                Message.buildMessageListSelection(
357513486cfb5d446f098b1c97d16932e51e316d1a7Ben Komalo                        mContext, mListContext.mAccountId, mListContext.getMailboxId()),
3584c4e4c3515c3e3300e03f90e02a0c520dc2dff32Makoto Onuki                null, EmailContent.MessageColumns.TIMESTAMP + " DESC");
359de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        return cursor;
360de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
361de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki
362de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /**
363de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Called when {@link #openNewCursor()} is finished.
364de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     *
365de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     * Unit tests call this directly to inject a mock cursor.
366de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki     */
367de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    /* package */ void onCursorOpenDone(Cursor cursor) {
368de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        try {
369de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            closeCursor();
370de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            if (cursor == null || cursor.isClosed()) {
371931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki                mTotalMessageCount = 0;
372931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki                mCurrentPosition = 0;
373de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki                return; // Task canceled
374de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            }
375de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCursor = cursor;
376931ca67a1f1baa0c1fd115124e35183c7786fc63Makoto Onuki            mTotalMessageCount = mCursor.getCount();
377de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mCursor.registerContentObserver(mObserver);
378de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            adjustCursorPosition();
379de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        } finally {
380de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki            mLoadMessageListTask = null; // isTaskRunning() becomes false.
381de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki        }
382de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki    }
383de0a1c33c9507e96d554ca645d42d07f1241157eMakoto Onuki}
384