Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 1 | /******************************************************************************* |
| 2 | * Copyright (C) 2012 Google Inc. |
| 3 | * Licensed to The Android Open Source Project. |
| 4 | * |
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | * you may not use this file except in compliance with the License. |
| 7 | * You may obtain a copy of the License at |
| 8 | * |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | * |
| 11 | * Unless required by applicable law or agreed to in writing, software |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | * See the License for the specific language governing permissions and |
| 15 | * limitations under the License. |
| 16 | *******************************************************************************/ |
| 17 | |
| 18 | package com.android.mail.ui; |
| 19 | |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 20 | import com.android.mail.browse.ConversationCursor; |
| 21 | import com.android.mail.providers.Conversation; |
Vikram Aggarwal | c769422 | 2012-04-23 13:37:01 -0700 | [diff] [blame] | 22 | import com.android.mail.providers.Settings; |
| 23 | import com.android.mail.providers.UIProvider.AutoAdvance; |
Paul Westbrook | b334c90 | 2012-06-25 11:42:46 -0700 | [diff] [blame] | 24 | import com.android.mail.utils.LogTag; |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 25 | import com.android.mail.utils.LogUtils; |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 26 | import java.util.Collection; |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 27 | |
| 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 | */ |
| 33 | public class ConversationPositionTracker { |
Paul Westbrook | b334c90 | 2012-06-25 11:42:46 -0700 | [diff] [blame] | 34 | protected static final String LOG_TAG = LogTag.getLogTag(); |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 35 | |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 36 | |
| 37 | public interface Callbacks { |
| 38 | ConversationCursor getConversationListCursor(); |
| 39 | } |
| 40 | |
| 41 | |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 42 | /** Did we recalculate positions after updating the cursor? */ |
| 43 | private boolean mCursorDirty = false; |
Vikram Aggarwal | 135fd02 | 2012-04-11 14:44:15 -0700 | [diff] [blame] | 44 | /** The currently selected conversation */ |
| 45 | private Conversation mConversation; |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 46 | |
| 47 | private final Callbacks mCallbacks; |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 48 | |
| 49 | /** |
| 50 | * Constructs a position tracker that doesn't point to any specific conversation. |
| 51 | */ |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 52 | public ConversationPositionTracker(Callbacks callbacks) { |
| 53 | mCallbacks = callbacks; |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 54 | } |
| 55 | |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 56 | /** Move cursor to a specific position and return the conversation there */ |
| 57 | private Conversation conversationAtPosition(int position){ |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 58 | final ConversationCursor cursor = mCallbacks.getConversationListCursor(); |
| 59 | cursor.moveToPosition(position); |
| 60 | final Conversation conv = new Conversation(cursor); |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 61 | conv.position = position; |
| 62 | return conv; |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | /** |
| 66 | * @return the total number of conversations in the list. |
| 67 | */ |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 68 | private int getCount() { |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 69 | final ConversationCursor cursor = mCallbacks.getConversationListCursor(); |
| 70 | if (isDataLoaded(cursor)) { |
| 71 | return cursor.getCount(); |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 72 | } 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 Aggarwal | 8f23bba | 2012-07-31 08:37:31 -0700 | [diff] [blame] | 81 | private Conversation getNewer(Collection<Conversation> victims) { |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 82 | int pos = calculatePosition(); |
| 83 | if (!isDataLoaded() || pos < 0) { |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 84 | return null; |
| 85 | } |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 86 | // Walk backward from the existing position, trying to find a conversation that is not a |
| 87 | // victim. |
| 88 | pos--; |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 89 | while (pos >= 0) { |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 90 | final Conversation candidate = conversationAtPosition(pos); |
| 91 | if (!Conversation.contains(victims, candidate)) { |
| 92 | return candidate; |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 93 | } |
| 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 Aggarwal | 8f23bba | 2012-07-31 08:37:31 -0700 | [diff] [blame] | 103 | private Conversation getOlder(Collection<Conversation> victims) { |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 104 | int pos = calculatePosition(); |
| 105 | if (!isDataLoaded() || pos < 0) { |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 106 | return null; |
| 107 | } |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 108 | // 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 Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 115 | } |
| 116 | pos++; |
| 117 | } |
| 118 | return null; |
| 119 | } |
| 120 | |
| 121 | /** |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 122 | * 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 Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 125 | * {@link #onCursorUpdated()}. |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 126 | * TODO(viki): Get rid of this method and the mConversation field entirely. |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 127 | */ |
Vikram Aggarwal | 135fd02 | 2012-04-11 14:44:15 -0700 | [diff] [blame] | 128 | public void initialize(Conversation conversation) { |
| 129 | mConversation = conversation; |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 130 | mCursorDirty = true; |
Vikram Aggarwal | a91d00b | 2013-01-18 12:00:37 -0800 | [diff] [blame^] | 131 | calculatePosition(); // Return value discarded. Running for side effects. |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 132 | } |
| 133 | |
| 134 | /** @return whether or not we have a valid cursor to check the position of. */ |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 135 | private static boolean isDataLoaded(ConversationCursor cursor) { |
| 136 | return cursor != null && !cursor.isClosed(); |
| 137 | } |
| 138 | |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 139 | private boolean isDataLoaded() { |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 140 | final ConversationCursor cursor = mCallbacks.getConversationListCursor(); |
| 141 | return isDataLoaded(cursor); |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 142 | } |
| 143 | |
| 144 | /** |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 145 | * Called when the conversation list changes. |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 146 | */ |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 147 | public void onCursorUpdated() { |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 148 | mCursorDirty = true; |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 149 | } |
| 150 | |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 151 | /** |
| 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 Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 164 | * @return the position of the current conversation in the cursor. |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 165 | */ |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 166 | private int calculatePosition() { |
| 167 | final int invalidPosition = -1; |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 168 | final ConversationCursor cursor = mCallbacks.getConversationListCursor(); |
Vikram Aggarwal | baeb383 | 2012-09-20 14:33:43 -0700 | [diff] [blame] | 169 | // 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 Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 175 | return invalidPosition; |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 176 | } |
| 177 | mCursorDirty = false; |
Vikram Aggarwal | a91d00b | 2013-01-18 12:00:37 -0800 | [diff] [blame^] | 178 | final int listSize = cursor.getCount(); |
Paul Westbrook | 937c94f | 2012-08-16 13:01:18 -0700 | [diff] [blame] | 179 | if (!isDataLoaded(cursor) || listSize == 0) { |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 180 | return invalidPosition; |
Vikram Aggarwal | af1ee0c | 2012-04-12 17:13:13 -0700 | [diff] [blame] | 181 | } |
Paul Westbrook | 44c1571 | 2012-09-09 14:13:40 -0700 | [diff] [blame] | 182 | |
Paul Westbrook | c8f2a3c | 2013-01-08 13:57:24 -0800 | [diff] [blame] | 183 | 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 Westbrook | 0a22d44 | 2012-11-08 23:32:43 -0800 | [diff] [blame] | 193 | |
Paul Westbrook | c8f2a3c | 2013-01-08 13:57:24 -0800 | [diff] [blame] | 194 | // 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 Aggarwal | a91d00b | 2013-01-18 12:00:37 -0800 | [diff] [blame^] | 197 | final int newPosition; |
| 198 | if (foundPosition >= listSize) { |
Paul Westbrook | c8f2a3c | 2013-01-08 13:57:24 -0800 | [diff] [blame] | 199 | // Go to the last position since our expected position is past this somewhere. |
Vikram Aggarwal | a91d00b | 2013-01-18 12:00:37 -0800 | [diff] [blame^] | 200 | newPosition = listSize - 1; |
| 201 | } else { |
| 202 | newPosition = foundPosition; |
Paul Westbrook | c8f2a3c | 2013-01-08 13:57:24 -0800 | [diff] [blame] | 203 | } |
Paul Westbrook | 0a22d44 | 2012-11-08 23:32:43 -0800 | [diff] [blame] | 204 | |
Vikram Aggarwal | a91d00b | 2013-01-18 12:00:37 -0800 | [diff] [blame^] | 205 | // 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 Westbrook | c8f2a3c | 2013-01-08 13:57:24 -0800 | [diff] [blame] | 208 | 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 Aggarwal | a91d00b | 2013-01-18 12:00:37 -0800 | [diff] [blame^] | 213 | mConversation.position = newPosition; |
Paul Westbrook | 99e379e | 2012-11-14 09:56:08 -0800 | [diff] [blame] | 214 | } |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 215 | return newPosition; |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 216 | } |
Vikram Aggarwal | c769422 | 2012-04-23 13:37:01 -0700 | [diff] [blame] | 217 | |
| 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 Aggarwal | 025eba8 | 2012-05-08 10:45:30 -0700 | [diff] [blame] | 222 | * @param autoAdvance the auto advance preference for the user as an |
Vikram Aggarwal | a91d00b | 2013-01-18 12:00:37 -0800 | [diff] [blame^] | 223 | * {@link Settings#getAutoAdvanceSetting()} value. |
Vikram Aggarwal | 8f23bba | 2012-07-31 08:37:31 -0700 | [diff] [blame] | 224 | * @param mTarget conversations to overlook while finding the next conversation. (These are |
| 225 | * usually the conversations to be deleted.) |
Vikram Aggarwal | a91d00b | 2013-01-18 12:00:37 -0800 | [diff] [blame^] | 226 | * @return the next conversation to be shown, or null if no next conversation exists. |
Vikram Aggarwal | c769422 | 2012-04-23 13:37:01 -0700 | [diff] [blame] | 227 | */ |
Vikram Aggarwal | 8f23bba | 2012-07-31 08:37:31 -0700 | [diff] [blame] | 228 | public Conversation getNextConversation(int autoAdvance, Collection<Conversation> mTarget) { |
Vikram Aggarwal | 7dd054e | 2012-05-21 14:43:10 -0700 | [diff] [blame] | 229 | final boolean getNewer = autoAdvance == AutoAdvance.NEWER; |
| 230 | final boolean getOlder = autoAdvance == AutoAdvance.OLDER; |
Vikram Aggarwal | 8f23bba | 2012-07-31 08:37:31 -0700 | [diff] [blame] | 231 | final Conversation next = getNewer ? getNewer(mTarget) : |
| 232 | (getOlder ? getOlder(mTarget) : null); |
Vikram Aggarwal | c769422 | 2012-04-23 13:37:01 -0700 | [diff] [blame] | 233 | LogUtils.d(LOG_TAG, "ConversationPositionTracker.getNextConversation: " + |
| 234 | "getNewer = %b, getOlder = %b, Next conversation is %s", |
| 235 | getNewer, getOlder, next); |
| 236 | return next; |
| 237 | } |
Vikram Aggarwal | 025eba8 | 2012-05-08 10:45:30 -0700 | [diff] [blame] | 238 | |
Vikram Aggarwal | 72ef2e7 | 2012-04-04 09:25:59 -0700 | [diff] [blame] | 239 | } |