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