blob: 46ca0a0f6634c73cf241c31994396ef7433538b3 [file] [log] [blame]
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -07001/*******************************************************************************
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
18package com.android.mail.ui;
19
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070020import com.android.mail.browse.ConversationCursor;
21import com.android.mail.providers.Conversation;
Vikram Aggarwalc7694222012-04-23 13:37:01 -070022import com.android.mail.providers.Settings;
23import com.android.mail.providers.UIProvider.AutoAdvance;
Paul Westbrookb334c902012-06-25 11:42:46 -070024import com.android.mail.utils.LogTag;
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -070025import com.android.mail.utils.LogUtils;
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -070026import java.util.Collection;
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070027
28/**
29 * An iterator over a conversation list that keeps track of the position of a conversation, and
30 * updates the position accordingly when the underlying list data changes and the conversation
31 * is in a different position.
32 */
33public class ConversationPositionTracker {
Paul Westbrookb334c902012-06-25 11:42:46 -070034 protected static final String LOG_TAG = LogTag.getLogTag();
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070035
Paul Westbrook937c94f2012-08-16 13:01:18 -070036
37 public interface Callbacks {
38 ConversationCursor getConversationListCursor();
39 }
40
41
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -070042 /** Did we recalculate positions after updating the cursor? */
43 private boolean mCursorDirty = false;
Vikram Aggarwal135fd022012-04-11 14:44:15 -070044 /** The currently selected conversation */
45 private Conversation mConversation;
Paul Westbrook937c94f2012-08-16 13:01:18 -070046
47 private final Callbacks mCallbacks;
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070048
49 /**
50 * Constructs a position tracker that doesn't point to any specific conversation.
51 */
Paul Westbrook937c94f2012-08-16 13:01:18 -070052 public ConversationPositionTracker(Callbacks callbacks) {
53 mCallbacks = callbacks;
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070054 }
55
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -070056 /** Move cursor to a specific position and return the conversation there */
57 private Conversation conversationAtPosition(int position){
Paul Westbrook937c94f2012-08-16 13:01:18 -070058 final ConversationCursor cursor = mCallbacks.getConversationListCursor();
59 cursor.moveToPosition(position);
60 final Conversation conv = new Conversation(cursor);
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -070061 conv.position = position;
62 return conv;
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070063 }
64
65 /**
66 * @return the total number of conversations in the list.
67 */
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -070068 private int getCount() {
Paul Westbrook937c94f2012-08-16 13:01:18 -070069 final ConversationCursor cursor = mCallbacks.getConversationListCursor();
70 if (isDataLoaded(cursor)) {
71 return cursor.getCount();
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070072 } else {
73 return 0;
74 }
75 }
76
77 /**
78 * @return the {@link Conversation} of the newer conversation by one position. If no such
79 * conversation exists, this method returns null.
80 */
Vikram Aggarwal8f23bba2012-07-31 08:37:31 -070081 private Conversation getNewer(Collection<Conversation> victims) {
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -070082 int pos = calculatePosition();
83 if (!isDataLoaded() || pos < 0) {
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070084 return null;
85 }
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -070086 // Walk backward from the existing position, trying to find a conversation that is not a
87 // victim.
88 pos--;
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070089 while (pos >= 0) {
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -070090 final Conversation candidate = conversationAtPosition(pos);
91 if (!Conversation.contains(victims, candidate)) {
92 return candidate;
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -070093 }
94 pos--;
95 }
96 return null;
97 }
98
99 /**
100 * @return the {@link Conversation} of the older conversation by one spot. If no such
101 * conversation exists, this method returns null.
102 */
Vikram Aggarwal8f23bba2012-07-31 08:37:31 -0700103 private Conversation getOlder(Collection<Conversation> victims) {
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700104 int pos = calculatePosition();
105 if (!isDataLoaded() || pos < 0) {
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700106 return null;
107 }
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700108 // Walk forward from the existing position, trying to find a conversation that is not a
109 // victim.
110 pos++;
111 while (pos < getCount()) {
112 final Conversation candidate = conversationAtPosition(pos);
113 if (!Conversation.contains(victims, candidate)) {
114 return candidate;
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700115 }
116 pos++;
117 }
118 return null;
119 }
120
121 /**
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700122 * Initializes the tracker with initial conversation id and initial position. This invalidates
123 * the positions in the tracker. We need a valid cursor before we can bless the position as
124 * valid. This requires a call to
Paul Westbrook937c94f2012-08-16 13:01:18 -0700125 * {@link #onCursorUpdated()}.
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700126 * TODO(viki): Get rid of this method and the mConversation field entirely.
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700127 */
Vikram Aggarwal135fd022012-04-11 14:44:15 -0700128 public void initialize(Conversation conversation) {
129 mConversation = conversation;
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700130 mCursorDirty = true;
Vikram Aggarwala91d00b2013-01-18 12:00:37 -0800131 calculatePosition(); // Return value discarded. Running for side effects.
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700132 }
133
134 /** @return whether or not we have a valid cursor to check the position of. */
Paul Westbrook937c94f2012-08-16 13:01:18 -0700135 private static boolean isDataLoaded(ConversationCursor cursor) {
136 return cursor != null && !cursor.isClosed();
137 }
138
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700139 private boolean isDataLoaded() {
Paul Westbrook937c94f2012-08-16 13:01:18 -0700140 final ConversationCursor cursor = mCallbacks.getConversationListCursor();
141 return isDataLoaded(cursor);
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700142 }
143
144 /**
Paul Westbrook937c94f2012-08-16 13:01:18 -0700145 * Called when the conversation list changes.
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700146 */
Paul Westbrook937c94f2012-08-16 13:01:18 -0700147 public void onCursorUpdated() {
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700148 mCursorDirty = true;
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700149 }
150
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700151 /**
152 * Recalculate the current position based on the cursor. This needs to be done once for
153 * each (Conversation, Cursor) pair. We could do this on every change of conversation or
154 * cursor, but that would be wasteful, since the recalculation of position is only required
155 * when transitioning to the next conversation. Transitions don't happen frequently, but
156 * changes in conversation and cursor do. So we defer this till it is actually needed.
157 *
158 * This method could change the current conversation if it cannot find the current conversation
159 * in the cursor. When this happens, this method sets the current conversation to some safe
160 * value and logs the reasons why it couldn't find the conversation.
161 *
162 * Calling this method repeatedly is safe: it returns early if it detects it has already been
163 * called.
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700164 * @return the position of the current conversation in the cursor.
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700165 */
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700166 private int calculatePosition() {
167 final int invalidPosition = -1;
Paul Westbrook937c94f2012-08-16 13:01:18 -0700168 final ConversationCursor cursor = mCallbacks.getConversationListCursor();
Vikram Aggarwalbaeb3832012-09-20 14:33:43 -0700169 // If we have a valid position and nothing has changed, return that right away
170 if (!mCursorDirty) {
171 return mConversation.position;
172 }
173 // Ensure valid input data
174 if (cursor == null || mConversation == null) {
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700175 return invalidPosition;
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700176 }
177 mCursorDirty = false;
Vikram Aggarwala91d00b2013-01-18 12:00:37 -0800178 final int listSize = cursor.getCount();
Paul Westbrook937c94f2012-08-16 13:01:18 -0700179 if (!isDataLoaded(cursor) || listSize == 0) {
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700180 return invalidPosition;
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700181 }
Paul Westbrook44c15712012-09-09 14:13:40 -0700182
Paul Westbrookc8f2a3c2013-01-08 13:57:24 -0800183 final int foundPosition = cursor.getConversationPosition(mConversation.id);
184 if (foundPosition >= 0) {
185 mConversation.position = foundPosition;
186 // Pre-emptively try to load the next cursor position so that the cursor window
187 // can be filled. The odd behavior of the ConversationCursor requires us to do
188 // this to ensure the adjacent conversation information is loaded for calls to
189 // hasNext.
190 cursor.moveToPosition(foundPosition + 1);
191 return foundPosition;
192 }
Paul Westbrook0a22d442012-11-08 23:32:43 -0800193
Paul Westbrookc8f2a3c2013-01-08 13:57:24 -0800194 // If the conversation is no longer found in the list, try to save the same position if
195 // it is still a valid position. Otherwise, go back to a valid position until we can
196 // find a valid one.
Vikram Aggarwala91d00b2013-01-18 12:00:37 -0800197 final int newPosition;
198 if (foundPosition >= listSize) {
Paul Westbrookc8f2a3c2013-01-08 13:57:24 -0800199 // Go to the last position since our expected position is past this somewhere.
Vikram Aggarwala91d00b2013-01-18 12:00:37 -0800200 newPosition = listSize - 1;
201 } else {
202 newPosition = foundPosition;
Paul Westbrookc8f2a3c2013-01-08 13:57:24 -0800203 }
Paul Westbrook0a22d442012-11-08 23:32:43 -0800204
Vikram Aggarwala91d00b2013-01-18 12:00:37 -0800205 // Did not keep the current conversation, so let's try to load the conversation from the
206 // new position.
207 if (isDataLoaded(cursor) && newPosition >= 0){
Paul Westbrookc8f2a3c2013-01-08 13:57:24 -0800208 LogUtils.d(LOG_TAG, "ConversationPositionTracker: Could not find conversation %s" +
209 " in the cursor. Moving to position %d ", mConversation.toString(),
210 newPosition);
211 cursor.moveToPosition(newPosition);
212 mConversation = new Conversation(cursor);
Vikram Aggarwala91d00b2013-01-18 12:00:37 -0800213 mConversation.position = newPosition;
Paul Westbrook99e379e2012-11-14 09:56:08 -0800214 }
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700215 return newPosition;
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700216 }
Vikram Aggarwalc7694222012-04-23 13:37:01 -0700217
218 /**
219 * Get the next conversation according to the AutoAdvance settings and the list of
220 * conversations available in the folder. If no next conversation can be found, this method
221 * returns null.
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700222 * @param autoAdvance the auto advance preference for the user as an
Vikram Aggarwala91d00b2013-01-18 12:00:37 -0800223 * {@link Settings#getAutoAdvanceSetting()} value.
Vikram Aggarwal8f23bba2012-07-31 08:37:31 -0700224 * @param mTarget conversations to overlook while finding the next conversation. (These are
225 * usually the conversations to be deleted.)
Vikram Aggarwala91d00b2013-01-18 12:00:37 -0800226 * @return the next conversation to be shown, or null if no next conversation exists.
Vikram Aggarwalc7694222012-04-23 13:37:01 -0700227 */
Vikram Aggarwal8f23bba2012-07-31 08:37:31 -0700228 public Conversation getNextConversation(int autoAdvance, Collection<Conversation> mTarget) {
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700229 final boolean getNewer = autoAdvance == AutoAdvance.NEWER;
230 final boolean getOlder = autoAdvance == AutoAdvance.OLDER;
Vikram Aggarwal8f23bba2012-07-31 08:37:31 -0700231 final Conversation next = getNewer ? getNewer(mTarget) :
232 (getOlder ? getOlder(mTarget) : null);
Vikram Aggarwalc7694222012-04-23 13:37:01 -0700233 LogUtils.d(LOG_TAG, "ConversationPositionTracker.getNextConversation: " +
234 "getNewer = %b, getOlder = %b, Next conversation is %s",
235 getNewer, getOlder, next);
236 return next;
237 }
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700238
Vikram Aggarwal72ef2e72012-04-04 09:25:59 -0700239}