blob: 57479993d2d44af1656b1b84a654359118dc6676 [file] [log] [blame]
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08001/*******************************************************************************
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 Aggarwal1ddcf0f2012-01-13 11:45:02 -080020import android.app.ActionBar;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080021import android.app.ActionBar.LayoutParams;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080022import android.app.Activity;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070023import android.app.AlertDialog;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080024import android.app.Dialog;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -070025import android.app.Fragment;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -070026import android.app.FragmentManager;
Andy Huangf9a73482012-03-13 15:54:02 -070027import android.app.LoaderManager;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070028import android.app.SearchManager;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080029import android.content.ContentResolver;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080030import android.content.Context;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080031import android.content.CursorLoader;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070032import android.content.DialogInterface;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080033import android.content.Intent;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080034import android.content.Loader;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080035import android.database.Cursor;
Andy Huang632721e2012-04-11 16:57:26 -070036import android.database.DataSetObservable;
37import android.database.DataSetObserver;
Paul Westbrook23b74b92012-02-29 11:36:12 -080038import android.net.Uri;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080039import android.os.Bundle;
Mindy Pereira21ab4902012-03-19 18:48:03 -070040import android.os.Handler;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070041import android.provider.SearchRecentSuggestions;
Mindy Pereiraacf60392012-04-06 09:11:00 -070042import android.view.DragEvent;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080043import android.view.KeyEvent;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080044import android.view.LayoutInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080045import android.view.Menu;
Mindy Pereira28d5f722012-02-15 12:32:40 -080046import android.view.MenuInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080047import android.view.MenuItem;
48import android.view.MotionEvent;
Marc Blankbf128eb2012-04-18 15:58:45 -070049import android.widget.AbsListView;
50import android.widget.AbsListView.OnScrollListener;
Mindy Pereirafd0c2972012-03-27 13:50:39 -070051import android.widget.Toast;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080052
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080053import com.android.mail.ConversationListContext;
Andy Huangf9a73482012-03-13 15:54:02 -070054import com.android.mail.R;
Mindy Pereira967ede62012-03-22 09:29:09 -070055import com.android.mail.browse.ConversationCursor;
Marc Blankcf164d62012-04-20 08:56:17 -070056import com.android.mail.browse.ConversationCursor.ConversationListener;
Paul Westbrookbf232c32012-04-18 03:17:41 -070057import com.android.mail.browse.ConversationPagerController;
Marc Blank7c9f6ac2012-04-02 13:27:19 -070058import com.android.mail.browse.SelectedConversationsActionMenu;
Mindy Pereira9b875682012-02-15 18:10:54 -080059import com.android.mail.compose.ComposeActivity;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080060import com.android.mail.providers.Account;
Mindy Pereira9b875682012-02-15 18:10:54 -080061import com.android.mail.providers.Conversation;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080062import com.android.mail.providers.Folder;
Paul Westbrookc2074c42012-03-22 15:26:58 -070063import com.android.mail.providers.MailAppProvider;
Mindy Pereiradac00542012-03-01 10:50:33 -080064import com.android.mail.providers.Settings;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070065import com.android.mail.providers.SuggestionsProvider;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080066import com.android.mail.providers.UIProvider;
Mindy Pereira3cb28b52012-05-24 15:26:39 -070067import com.android.mail.providers.UIProvider.AccountCapabilities;
Paul Westbrook2388c5d2012-03-25 12:29:11 -070068import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
Mindy Pereirac9d59182012-03-22 16:06:46 -070069import com.android.mail.providers.UIProvider.ConversationColumns;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070070import com.android.mail.providers.UIProvider.FolderCapabilities;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080071import com.android.mail.utils.LogUtils;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -080072import com.android.mail.utils.Utils;
Mindy Pereiraacf60392012-04-06 09:11:00 -070073import com.google.common.collect.Lists;
Marc Blank167faa82012-03-21 13:11:53 -070074import com.google.common.collect.Sets;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080075
Marc Blank167faa82012-03-21 13:11:53 -070076import java.util.ArrayList;
Mindy Pereirafbe40192012-03-20 10:40:45 -070077import java.util.Collection;
Vikram Aggarwald503df42012-05-11 10:13:35 -070078import java.util.Collections;
Paul Westbrook23b74b92012-02-29 11:36:12 -080079import java.util.Set;
Marc Blankbf128eb2012-04-18 15:58:45 -070080import java.util.Timer;
81import java.util.TimerTask;
Paul Westbrook23b74b92012-02-29 11:36:12 -080082
83
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080084/**
Mindy Pereira161f50d2012-02-28 15:47:19 -080085 * This is an abstract implementation of the Activity Controller. This class
86 * knows how to respond to menu items, state changes, layout changes, etc. It
87 * weaves together the views and listeners, dispatching actions to the
88 * respective underlying classes.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080089 * <p>
Mindy Pereira161f50d2012-02-28 15:47:19 -080090 * Even though this class is abstract, it should provide default implementations
91 * for most, if not all the methods in the ActivityController interface. This
92 * makes the task of the subclasses easier: OnePaneActivityController and
93 * TwoPaneActivityController can be concise when the common functionality is in
94 * AbstractActivityController.
95 * </p>
96 * <p>
97 * In the Gmail codebase, this was called BaseActivityController
98 * </p>
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080099 */
Marc Blankbf128eb2012-04-18 15:58:45 -0700100public abstract class AbstractActivityController implements ActivityController,
101 ConversationListener, OnScrollListener {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800102 // Keys for serialization of various information in Bundles.
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700103 /** Tag for {@link #mAccount} */
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800104 private static final String SAVED_ACCOUNT = "saved-account";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700105 /** Tag for {@link #mFolder} */
Mindy Pereira5e478d22012-03-26 18:04:58 -0700106 private static final String SAVED_FOLDER = "saved-folder";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700107 /** Tag for {@link #mCurrentConversation} */
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700108 private static final String SAVED_CONVERSATION = "saved-conversation";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700109 /** Tag for {@link #mSelectedSet} */
110 private static final String SAVED_SELECTED_SET = "saved-selected-set";
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800111
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700112 /** Tag used when loading a wait fragment */
113 protected static final String TAG_WAIT = "wait-fragment";
114 /** Tag used when loading a conversation list fragment. */
Paul Westbrookbf232c32012-04-18 03:17:41 -0700115 public static final String TAG_CONVERSATION_LIST = "tag-conversation-list";
116 /** Tag used when loading a conversation fragment. */
117 public static final String TAG_CONVERSATION = "tag-conversation";
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700118 /** Tag used when loading a folder list fragment. */
119 protected static final String TAG_FOLDER_LIST = "tag-folder-list";
120
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700121 /** Are we on a tablet device or not. */
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800122 public final boolean IS_TABLET_DEVICE;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800123
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800124 protected Account mAccount;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800125 protected Folder mFolder;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800126 protected ActionBarView mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800127 protected final RestrictedActivity mActivity;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800128 protected final Context mContext;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700129 private final FragmentManager mFragmentManager;
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800130 protected final RecentFolderList mRecentFolderList;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800131 protected ConversationListContext mConvListContext;
Mindy Pereira9b875682012-02-15 18:10:54 -0800132 protected Conversation mCurrentConversation;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800133
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700134 /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
135 private SuppressNotificationReceiver mNewEmailReceiver = null;
136
Mindy Pereirafbe40192012-03-20 10:40:45 -0700137 protected Handler mHandler = new Handler();
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800138 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800139 * The current mode of the application. All changes in mode are initiated by
140 * the activity controller. View mode changes are propagated to classes that
141 * attach themselves as listeners of view mode changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800142 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800143 protected final ViewMode mViewMode;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800144 protected ContentResolver mResolver;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800145 protected boolean isLoaderInitialized = false;
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800146 private AsyncRefreshTask mAsyncRefreshTask;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800147
Paul Westbrook23b74b92012-02-29 11:36:12 -0800148 private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
Mindy Pereira967ede62012-03-22 09:29:09 -0700149 protected ConversationCursor mConversationListCursor;
Andy Huang632721e2012-04-11 16:57:26 -0700150 private final DataSetObservable mConversationListObservable = new DataSetObservable() {
151 @Override
152 public void registerObserver(DataSetObserver observer) {
153 final int count = mObservers.size();
154 super.registerObserver(observer);
155 LogUtils.d(LOG_TAG, "IN AAC.registerListObserver: %s before=%d after=%d", observer,
156 count, mObservers.size());
157 }
158 @Override
159 public void unregisterObserver(DataSetObserver observer) {
160 final int count = mObservers.size();
161 super.unregisterObserver(observer);
162 LogUtils.d(LOG_TAG, "IN AAC.unregisterListObserver: %s before=%d after=%d", observer,
163 count, mObservers.size());
164 }
165 };
Marc Blankbf128eb2012-04-18 15:58:45 -0700166 protected boolean mConversationListenerAdded = false;
167
168 private boolean mIsConversationListScrolling = false;
169 private long mConversationListRefreshTime = 0;
Marc Blankbf128eb2012-04-18 15:58:45 -0700170 private RefreshTimerTask mConversationListRefreshTask;
Marc Blanke1d1b072012-04-13 17:29:16 -0700171
Vikram Aggarwalc7694222012-04-23 13:37:01 -0700172 /** Listeners that are interested in changes to current account settings. */
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700173 private final ArrayList<Settings.ChangeListener> mSettingsListeners = Lists.newArrayList();
174
Mindy Pereira967ede62012-03-22 09:29:09 -0700175 /**
176 * Selected conversations, if any.
177 */
Andy Huang4556a442012-03-30 16:42:05 -0700178 private final ConversationSelectionSet mSelectedSet = new ConversationSelectionSet();
Mindy Pereiradac00542012-03-01 10:50:33 -0800179
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700180 private final int mFolderItemUpdateDelayMs;
181
Vikram Aggarwal135fd022012-04-11 14:44:15 -0700182 /** Keeps track of selected and unselected conversations */
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700183 final protected ConversationPositionTracker mTracker = new ConversationPositionTracker();
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700184
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700185 /**
186 * Action menu associated with the selected set.
187 */
188 SelectedConversationsActionMenu mCabActionMenu;
Mindy Pereira0963ef82012-04-10 11:43:01 -0700189 protected UndoBarView mUndoBarView;
Andy Huang632721e2012-04-11 16:57:26 -0700190 protected ConversationPagerController mPagerController;
Mindy Pereiraab486362012-03-21 18:18:53 -0700191
Andy Huangb1c34dc2012-04-17 16:36:19 -0700192 // this is split out from the general loader dispatcher because its loader doesn't return a
193 // basic Cursor
194 private final ConversationListLoaderCallbacks mListCursorCallbacks =
195 new ConversationListLoaderCallbacks();
196
Vikram Aggarwal04ff99c2012-02-28 15:29:13 -0800197 protected static final String LOG_TAG = new LogUtils().getLogTag();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800198 /** Constants used to differentiate between the types of loaders. */
199 private static final int LOADER_ACCOUNT_CURSOR = 0;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800200 private static final int LOADER_FOLDER_CURSOR = 2;
201 private static final int LOADER_RECENT_FOLDERS = 3;
Mindy Pereira967ede62012-03-22 09:29:09 -0700202 private static final int LOADER_CONVERSATION_LIST = 4;
Mindy Pereiraab486362012-03-21 18:18:53 -0700203 private static final int LOADER_ACCOUNT_INBOX = 5;
204 private static final int LOADER_SEARCH = 6;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -0700205 private static final int LOADER_ACCOUNT_UPDATE_CURSOR = 7;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800206
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700207 private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
208
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700209 /** The pending destructive action to be carried out before swapping the conversation cursor.*/
210 private DestructiveAction mPendingDestruction;
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700211 /** Indicates if a conversation view is visible. */
212 private boolean mIsConversationVisible;
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700213
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800214 public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
215 mActivity = activity;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700216 mFragmentManager = mActivity.getFragmentManager();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800217 mViewMode = viewMode;
218 mContext = activity.getApplicationContext();
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800219 IS_TABLET_DEVICE = Utils.useTabletUI(mContext);
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700220 mRecentFolderList = new RecentFolderList(mContext);
Mindy Pereira967ede62012-03-22 09:29:09 -0700221 // Allow the fragment to observe changes to its own selection set. No other object is
222 // aware of the selected set.
223 mSelectedSet.addObserver(this);
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700224
225 mFolderItemUpdateDelayMs =
226 mContext.getResources().getInteger(R.integer.folder_item_refresh_delay_ms);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800227 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800228
229 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800230 public void clearSubject() {
231 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800232 }
233
234 @Override
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800235 public Account getCurrentAccount() {
236 return mAccount;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800237 }
238
239 @Override
240 public ConversationListContext getCurrentListContext() {
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800241 return mConvListContext;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800242 }
243
244 @Override
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800245 public String getHelpContext() {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800246 return "Mail";
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800247 }
248
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800249 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800250 public int getMode() {
251 return mViewMode.getMode();
252 }
253
254 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800255 public String getUnshownSubject(String subject) {
256 // Calculate how much of the subject is shown, and return the remaining.
257 return null;
258 }
259
260 @Override
261 public void handleConversationLoadError() {
262 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800263 }
264
Mindy Pereira967ede62012-03-22 09:29:09 -0700265 @Override
Vikram Aggarwalef7c9922012-04-23 12:35:04 -0700266 public final ConversationCursor getConversationListCursor() {
Mindy Pereira967ede62012-03-22 09:29:09 -0700267 return mConversationListCursor;
268 }
269
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700270 /**
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700271 * Check if the fragment is attached to an activity and has a root view.
272 * @param in
273 * @return true if the fragment is valid, false otherwise
274 */
275 private static final boolean isValidFragment(Fragment in) {
276 if (in == null || in.getActivity() == null || in.getView() == null) {
277 return false;
278 }
279 return true;
280 }
281
282 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700283 * Get the conversation list fragment for this activity. If the conversation list fragment
284 * is not attached, this method returns null
285 * @return
286 */
287 protected ConversationListFragment getConversationListFragment() {
288 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700289 if (isValidFragment(fragment)) {
290 return (ConversationListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700291 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700292 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700293 }
294
295 /**
Vikram Aggarwald503df42012-05-11 10:13:35 -0700296 * Get the conversation view fragment for this activity. If the conversation view fragment
297 * is not attached, this method returns null
298 * @return
299 */
300 protected ConversationViewFragment getConversationViewFragment() {
301 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION);
302 if (isValidFragment(fragment)) {
303 return (ConversationViewFragment) fragment;
304 }
305 return null;
306 }
307
308 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700309 * Returns the folder list fragment attached with this activity. If no such fragment is attached
310 * this method returns null.
311 * @return
312 */
313 protected FolderListFragment getFolderListFragment() {
314 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_FOLDER_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700315 if (isValidFragment(fragment)) {
316 return (FolderListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700317 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700318 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700319 }
320
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800321 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800322 * Initialize the action bar. This is not visible to OnePaneController and
323 * TwoPaneController so they cannot override this behavior.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800324 */
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700325 private void initializeActionBar() {
326 final ActionBar actionBar = mActivity.getActionBar();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800327 mActionBarView = (ActionBarView) LayoutInflater.from(mContext).inflate(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800328 R.layout.actionbar_view, null);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800329 if (actionBar != null && mActionBarView != null) {
Mindy Pereira161f50d2012-02-28 15:47:19 -0800330 // Why have a different variable for the same thing? We should apply
331 // the same actions
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800332 // on mActionBarView instead.
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800333 mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700334 }
335 }
336
337 /**
338 * Attach the action bar to the activity.
339 */
340 private void attachActionBar() {
341 final ActionBar actionBar = mActivity.getActionBar();
342 if (actionBar != null && mActionBarView != null) {
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800343 actionBar.setCustomView(mActionBarView, new ActionBar.LayoutParams(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800344 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
345 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
Vikram Aggarwal2a25d0c2012-02-21 16:43:10 -0800346 ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700347 mActionBarView.attach();
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800348 }
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700349 mViewMode.addListener(mActionBarView);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800350 }
351
352 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800353 * Returns whether the conversation list fragment is visible or not.
354 * Different layouts will have their own notion on the visibility of
355 * fragments, so this method needs to be overriden.
356 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800357 * @return
358 */
359 protected abstract boolean isConversationListVisible();
360
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700361 /**
362 * Switch the current account to the one provided as an argument to the method.
363 * @param account
364 */
365 private void switchAccount(Account account){
366 // Current account is different from the new account, restart loaders and show
367 // the account Inbox.
368 mAccount = account;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -0700369 LogUtils.d(LOG_TAG, "AbstractActivityController.switchAccount(): mAccount = %s",
370 mAccount.uri);
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700371 cancelRefreshTask();
372 onSettingsChanged(mAccount.settings);
373 mActionBarView.setAccount(mAccount);
374 loadAccountInbox();
375
376 mRecentFolderList.setCurrentAccount(account);
377 restartOptionalLoader(LOADER_RECENT_FOLDERS);
378 mActivity.invalidateOptionsMenu();
379 disableNotificationsOnAccountChange(mAccount);
380 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
381 MailAppProvider.getInstance().setLastViewedAccount(mAccount.uri.toString());
382 }
383
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800384 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800385 public void onAccountChanged(Account account) {
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700386 LogUtils.d(LOG_TAG, "onAccountChanged (%s) called.", account.uri);
387 final boolean accountChanged = (mAccount == null) || !account.uri.equals(mAccount.uri);
388 if (accountChanged) {
389 switchAccount(account);
390 return;
391 }
392 // Current account is the same as the new account, but the settings might be different.
393 if (!account.settings.equals(mAccount.settings)){
394 onSettingsChanged(account.settings);
395 return;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800396 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800397 }
398
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700399 /**
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700400 * Changes the settings for the current account. The new settings are provided as a parameter.
401 * @param settings
402 */
Mindy Pereiradac00542012-03-01 10:50:33 -0800403 public void onSettingsChanged(Settings settings) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700404 dispatchSettingsChange(settings);
Mindy Pereira12a676a2012-03-23 13:00:22 -0700405 resetActionBarIcon();
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700406 mActivity.invalidateOptionsMenu();
407 // If the user was viewing the default Inbox here, and the new setting contains a different
408 // default Inbox, we don't want to load a different folder here.
Mindy Pereiradac00542012-03-01 10:50:33 -0800409 }
410
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800411 @Override
412 public Settings getSettings() {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700413 return mAccount.settings;
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800414 }
415
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700416 /**
417 * Adds a listener interested in change in settings. If a class is storing a reference to
418 * Settings, it should listen on changes, so it can receive updates to settings.
419 * Must happen in the UI thread.
420 */
421 public void addSettingsListener(Settings.ChangeListener listener) {
422 mSettingsListeners.add(listener);
423 }
424
425 /**
426 * Removes a listener from receiving settings changes.
427 * Must happen in the UI thread.
428 */
429 public void removeSettingsListener(Settings.ChangeListener listener) {
430 mSettingsListeners.remove(listener);
431 }
432
433 /**
434 * Method that lets the settings listeners know when the settings got changed.
435 */
436 private void dispatchSettingsChange(Settings updatedSettings) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700437 // Copy the list of current listeners so that
438 final ArrayList<Settings.ChangeListener> allListeners =
439 new ArrayList<Settings.ChangeListener>(mSettingsListeners);
440 for (Settings.ChangeListener listener : allListeners) {
441 if (listener != null) {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700442 listener.onSettingsChanged(updatedSettings);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700443 }
444 }
445 // And we know that the ConversationListFragment is interested in changes to settings,
446 // though it hasn't registered itself with us.
447 final ConversationListFragment convList = getConversationListFragment();
448 if (convList != null) {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700449 convList.onSettingsChanged(updatedSettings);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700450 }
451 }
452
Mindy Pereirae0828392012-03-08 10:38:40 -0800453 private void fetchSearchFolder(Intent intent) {
Mindy Pereiraab486362012-03-21 18:18:53 -0700454 Bundle args = new Bundle();
455 args.putString(ConversationListContext.EXTRA_SEARCH_QUERY, intent
Mindy Pereirae0828392012-03-08 10:38:40 -0800456 .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -0700457 mActivity.getLoaderManager().restartLoader(LOADER_SEARCH, args, this);
Mindy Pereirae0828392012-03-08 10:38:40 -0800458 }
459
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800460 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800461 public void onFolderChanged(Folder folder) {
Mindy Pereira7a3471f2012-03-06 12:23:41 -0800462 if (folder != null && !folder.equals(mFolder)) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800463 setFolder(folder);
Mindy Pereira161f50d2012-02-28 15:47:19 -0800464 mConvListContext = ConversationListContext.forFolder(mContext, mAccount, mFolder);
Mindy Pereira28e0c342012-02-17 15:05:13 -0800465 showConversationList(mConvListContext);
Paul Westbrook9024b6d2012-03-19 13:57:55 -0700466
467 // Add the folder that we were viewing to the recent folders list.
468 // TODO: this may need to be fine tuned. If this is the signal that is indicating that
469 // the list is shown to the user, this could fire in one pane if the user goes directly
470 // to a conversation
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700471 updateRecentFolderList();
Marc Blankbf128eb2012-04-18 15:58:45 -0700472 cancelRefreshTask();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800473 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800474 }
475
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700476 /**
477 * Update the recent folders. This only needs to be done once when accessing a new folder.
478 */
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700479 private void updateRecentFolderList() {
Mindy Pereiraab486362012-03-21 18:18:53 -0700480 if (mFolder != null) {
Marc Blank2675dbc2012-04-03 10:17:13 -0700481 mRecentFolderList.touchFolder(mFolder, mAccount);
Mindy Pereiraab486362012-03-21 18:18:53 -0700482 }
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700483 }
484
Mindy Pereiraab486362012-03-21 18:18:53 -0700485 // TODO(mindyp): set this up to store a copy of the folder as a transient
486 // field in the account.
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700487 @Override
488 public void loadAccountInbox() {
Vikram Aggarwal94c94de2012-04-04 15:38:28 -0700489 restartOptionalLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700490 }
491
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800492 /** Set the current folder */
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800493 private void setFolder(Folder folder) {
494 // Start watching folder for sync status.
Mindy Pereirae0458e82012-03-06 11:54:55 -0800495 if (folder != null && !folder.equals(mFolder)) {
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700496 LogUtils.d(LOG_TAG, "AbstractActivityController.setFolder(%s)", folder.name);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700497 final boolean folderWasNull = (mFolder == null);
498 final LoaderManager lm = mActivity.getLoaderManager();
Mindy Pereira9b623802012-03-07 17:15:49 -0800499 mActionBarView.setRefreshInProgress(false);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800500 mFolder = folder;
Mindy Pereiraf9323cd2012-02-29 13:47:09 -0800501 mActionBarView.setFolder(mFolder);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700502
503 // Only when we switch from one folder to another do we want to restart the
504 // folder and conversation list loaders (to trigger onCreateLoader).
505 // The first time this runs when the activity is [re-]initialized, we want to re-use the
506 // previous loader's instance and data upon configuration change (e.g. rotation).
507 if (folderWasNull) {
508 lm.initLoader(LOADER_FOLDER_CURSOR, null, this);
509 lm.initLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
510 } else {
511 lm.restartLoader(LOADER_FOLDER_CURSOR, null, this);
512 lm.restartLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
513 }
Mindy Pereirae0458e82012-03-06 11:54:55 -0800514 } else if (folder == null) {
515 LogUtils.wtf(LOG_TAG, "Folder in setFolder is null");
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800516 }
517 }
518
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800519 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800520 public void onActivityResult(int requestCode, int resultCode, Intent data) {
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700521 if (requestCode == ADD_ACCOUNT_REQUEST_CODE) {
522 // We were waiting for the user to create an account
523 if (resultCode == Activity.RESULT_OK) {
524 // restart the loader to get the updated list of accounts
525 mActivity.getLoaderManager().initLoader(
526 LOADER_ACCOUNT_CURSOR, null, this);
527 } else {
528 // The user failed to create an account, just exit the app
529 mActivity.finish();
530 }
531 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800532 }
533
534 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800535 public void onConversationListVisibilityChanged(boolean visible) {
Paul Westbrook9f119c72012-04-24 16:10:59 -0700536 if (mConversationListCursor != null) {
537 // The conversation list is visible.
538 Utils.setConversationCursorVisibility(mConversationListCursor, visible);
539 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800540 }
541
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800542 /**
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700543 * Called when a conversation is visible. Child classes must call the super class implementation
544 * before performing local computation.
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800545 */
546 @Override
547 public void onConversationVisibilityChanged(boolean visible) {
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700548 mIsConversationVisible = visible;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800549 return;
550 }
551
552 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800553 public boolean onCreate(Bundle savedState) {
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700554 initializeActionBar();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800555 // Allow shortcut keys to function for the ActionBar and menus.
556 mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800557 mResolver = mActivity.getContentResolver();
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700558 mNewEmailReceiver = new SuppressNotificationReceiver();
559
Mindy Pereira161f50d2012-02-28 15:47:19 -0800560 // All the individual UI components listen for ViewMode changes. This
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800561 // simplifies the amount of logic in the AbstractActivityController, but increases the
562 // possibility of timing-related bugs.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800563 mViewMode.addListener(this);
Andy Huang632721e2012-04-11 16:57:26 -0700564 mPagerController = new ConversationPagerController(mActivity, this);
Andy Huang632721e2012-04-11 16:57:26 -0700565 mUndoBarView = (UndoBarView) mActivity.findViewById(R.id.undo_view);
Vikram Aggarwalada51782012-04-26 14:18:31 -0700566 attachActionBar();
Andy Huang632721e2012-04-11 16:57:26 -0700567
568 final Intent intent = mActivity.getIntent();
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700569 // Immediately handle a clean launch with intent, and any state restoration
Andy Huang632721e2012-04-11 16:57:26 -0700570 // that does not rely on restored fragments or loader data
571 // any state restoration that relies on those can be done later in
572 // onRestoreInstanceState, once fragments are up and loader data is re-delivered
573 if (savedState != null) {
574 if (savedState.containsKey(SAVED_ACCOUNT)) {
575 setAccount((Account) savedState.getParcelable(SAVED_ACCOUNT));
576 mActivity.invalidateOptionsMenu();
577 }
578 if (savedState.containsKey(SAVED_FOLDER)) {
579 // Open the folder.
580 onFolderChanged((Folder) savedState.getParcelable(SAVED_FOLDER));
581 }
582 } else if (intent != null) {
583 handleIntent(intent);
584 }
Andy Huang632721e2012-04-11 16:57:26 -0700585 // Create the accounts loader; this loads the account switch spinner.
586 mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
Andy Huang632721e2012-04-11 16:57:26 -0700587 return true;
Andy Huangb1c34dc2012-04-17 16:36:19 -0700588 }
589
590 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800591 public Dialog onCreateDialog(int id, Bundle bundle) {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800592 return null;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800593 }
594
595 @Override
596 public boolean onCreateOptionsMenu(Menu menu) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800597 MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800598 inflater.inflate(mActionBarView.getOptionsMenuId(), menu);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800599 mActionBarView.onCreateOptionsMenu(menu);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800600 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800601 }
602
603 @Override
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800604 public boolean onKeyDown(int keyCode, KeyEvent event) {
605 // TODO(viki): Auto-generated method stub
606 return false;
607 }
608
609 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800610 public boolean onOptionsItemSelected(MenuItem item) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700611 final int id = item.getItemId();
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700612 LogUtils.d(LOG_TAG, "AbstractController.onOptionsItemSelected(%d) called.", id);
Mindy Pereira9b875682012-02-15 18:10:54 -0800613 boolean handled = true;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800614 switch (id) {
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800615 case android.R.id.home:
616 onUpPressed();
617 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800618 case R.id.compose:
619 ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
620 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800621 case R.id.show_all_folders:
622 showFolderList();
623 break;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800624 case R.id.refresh:
625 requestFolderRefresh();
626 break;
Mindy Pereira1f936682012-03-02 11:30:33 -0800627 case R.id.settings:
628 Utils.showSettings(mActivity.getActivityContext(), mAccount);
Paul Westbrook2861b6a2012-02-15 15:25:34 -0800629 break;
Paul Westbrooke5503552012-03-28 00:35:57 -0700630 case R.id.folder_options:
631 Utils.showFolderSettings(mActivity.getActivityContext(), mAccount, mFolder);
632 break;
Paul Westbrook94e440d2012-02-24 11:03:47 -0800633 case R.id.help_info_menu_item:
634 // TODO: enable context sensitive help
Paul Westbrook498e76d2012-04-12 16:33:02 -0700635 Utils.showHelp(mActivity.getActivityContext(), mAccount, null);
Paul Westbrook94e440d2012-02-24 11:03:47 -0800636 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700637 case R.id.feedback_menu_item:
Mindy Pereirafbe40192012-03-20 10:40:45 -0700638 Utils.sendFeedback(mActivity.getActivityContext(), mAccount);
639 break;
Paul Westbrook18babd22012-04-09 22:17:08 -0700640 case R.id.manage_folders_item:
641 Utils.showManageFolder(mActivity.getActivityContext(), mAccount);
642 break;
Vikram Aggarwald503df42012-05-11 10:13:35 -0700643 case R.id.change_folder:
644 new FoldersSelectionDialog(mActivity.getActivityContext(), mAccount, this,
645 Conversation.listOf(mCurrentConversation)).show();
646 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800647 default:
648 handled = false;
649 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800650 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800651 return handled;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800652 }
653
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700654 /**
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700655 * Update the specified column name in conversation for a boolean value.
656 * @param columnName
657 * @param value
658 */
Mindy Pereirafbe40192012-03-20 10:40:45 -0700659 protected void updateCurrentConversation(String columnName, boolean value) {
Vikram Aggarwal440fe792012-05-10 17:23:15 -0700660 mConversationListCursor.updateBoolean(mContext, Conversation.listOf(mCurrentConversation),
Paul Westbrookbf232c32012-04-18 03:17:41 -0700661 columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700662 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700663 }
664
665 /**
666 * Update the specified column name in conversation for an integer value.
667 * @param columnName
668 * @param value
669 */
Mindy Pereirafbe40192012-03-20 10:40:45 -0700670 protected void updateCurrentConversation(String columnName, int value) {
Vikram Aggarwal440fe792012-05-10 17:23:15 -0700671 mConversationListCursor.updateInt(mContext, Conversation.listOf(mCurrentConversation),
Paul Westbrookbf232c32012-04-18 03:17:41 -0700672 columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700673 refreshConversationList();
Mindy Pereirac9d59182012-03-22 16:06:46 -0700674 }
675
676 protected void updateCurrentConversation(String columnName, String value) {
Vikram Aggarwal440fe792012-05-10 17:23:15 -0700677 mConversationListCursor.updateString(mContext, Conversation.listOf(mCurrentConversation),
Paul Westbrookbf232c32012-04-18 03:17:41 -0700678 columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700679 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700680 }
681
Mindy Pereira28e0c342012-02-17 15:05:13 -0800682 private void requestFolderRefresh() {
683 if (mFolder != null) {
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800684 if (mAsyncRefreshTask != null) {
685 mAsyncRefreshTask.cancel(true);
686 }
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800687 mAsyncRefreshTask = new AsyncRefreshTask(mContext, mFolder);
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800688 mAsyncRefreshTask.execute();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800689 }
690 }
691
Mindy Pereirafbe40192012-03-20 10:40:45 -0700692 /**
693 * Confirm (based on user's settings) and delete a conversation from the conversation list and
694 * from the database.
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700695 * @param target the conversations to act upon
696 * @param showDialog true if a confirmation dialog is to be shown, false otherwise.
697 * @param confirmResource the resource ID of the string that is shown in the confirmation dialog
698 * @param action the action to perform after animating the deletion of the conversations.
Mindy Pereirafbe40192012-03-20 10:40:45 -0700699 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700700 protected void confirmAndDelete(final Collection<Conversation> target, boolean showDialog,
701 int confirmResource, final DestructiveAction action) {
Mindy Pereirafbe40192012-03-20 10:40:45 -0700702 if (showDialog) {
703 final AlertDialog.OnClickListener onClick = new AlertDialog.OnClickListener() {
704 @Override
705 public void onClick(DialogInterface dialog, int which) {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700706 requestDelete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -0700707 }
708 };
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700709 final CharSequence message = Utils.formatPlural(mContext, confirmResource,
710 target.size());
Mindy Pereirafbe40192012-03-20 10:40:45 -0700711 new AlertDialog.Builder(mActivity.getActivityContext()).setMessage(message)
712 .setPositiveButton(R.string.ok, onClick)
713 .setNegativeButton(R.string.cancel, null)
714 .create().show();
715 } else {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700716 requestDelete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -0700717 }
718 }
719
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700720 /**
Vikram Aggarwald503df42012-05-11 10:13:35 -0700721 * Requests the removal of the current conversation with the specified destructive action.
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700722 * @param action
723 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700724 protected void requestDelete(final Collection<Conversation> target,
725 final DestructiveAction action) {
Vikram Aggarwald503df42012-05-11 10:13:35 -0700726 // The conversation list handles deletion if it exists.
727 final ConversationListFragment convList = getConversationListFragment();
728 if (convList != null) {
Vikram Aggarwalc30fe412012-05-18 11:58:58 -0700729 LogUtils.d(LOG_TAG, "AAC.requestDelete: ListFragment is handling delete.");
Vikram Aggarwald503df42012-05-11 10:13:35 -0700730 convList.requestDelete(target, action);
731 return;
732 }
733 // Update the conversation fragment if the current conversation is deleted.
734 if (getConversationViewFragment() != null &&
735 !Conversation.contains(target, mCurrentConversation)) {
736 final Conversation next = mTracker.getNextConversation(
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700737 Settings.getAutoAdvanceSetting(mAccount.settings), target,
738 mCurrentConversation);
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700739 LogUtils.d(LOG_TAG, "requestDelete: showing %s next.", next);
740 showConversation(next);
Vikram Aggarwald503df42012-05-11 10:13:35 -0700741 }
742 // No visible UI element handled it on our behalf. Perform the action ourself.
743 action.performAction();
744 }
745
746 /**
747 * Requests that the action be performed and the UI state is updated to reflect the new change.
748 * @param target
749 * @param action
750 */
751 protected void requestUpdate(final Collection<Conversation> target,
752 final DestructiveAction action) {
753 action.performAction();
754 refreshConversationList();
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700755 }
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700756
757 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800758 public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
759 // TODO(viki): Auto-generated method stub
760
761 }
762
763 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800764 public boolean onPrepareOptionsMenu(Menu menu) {
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800765 mActionBarView.onPrepareOptionsMenu(menu);
Mindy Pereira363451a2012-02-22 14:14:46 -0800766 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800767 }
768
Mindy Pereira68f2e222012-03-07 10:36:54 -0800769 @Override
770 public void onPause() {
771 isLoaderInitialized = false;
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700772
773 enableNotifications();
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700774 commitLeaveBehindItems();
Paul Westbrook94e440d2012-02-24 11:03:47 -0800775 }
776
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800777 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800778 public void onResume() {
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700779 // Register the receiver that will prevent the status receiver from
780 // displaying its notification icon as long as we're running.
781 // The SupressNotificationReceiver will block the broadcast if we're looking at the folder
782 // that the notification was received for.
783 disableNotifications();
784
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800785 if (mActionBarView != null) {
786 mActionBarView.onResume();
787 }
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700788
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800789 }
790
791 @Override
792 public void onSaveInstanceState(Bundle outState) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800793 if (mAccount != null) {
794 LogUtils.d(LOG_TAG, "Saving the account now");
795 outState.putParcelable(SAVED_ACCOUNT, mAccount);
796 }
Mindy Pereira5e478d22012-03-26 18:04:58 -0700797 if (mFolder != null) {
798 outState.putParcelable(SAVED_FOLDER, mFolder);
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800799 }
Mindy Pereira9fa43ca2012-05-17 14:18:01 -0700800 if (mCurrentConversation != null
801 && (mViewMode.getMode() == ViewMode.CONVERSATION ||
802 mViewMode.getMode() == ViewMode.SEARCH_RESULTS_CONVERSATION)) {
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700803 outState.putParcelable(SAVED_CONVERSATION, mCurrentConversation);
804 }
Andy Huang4556a442012-03-30 16:42:05 -0700805 if (!mSelectedSet.isEmpty()) {
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700806 outState.putParcelable(SAVED_SELECTED_SET, mSelectedSet);
Andy Huang4556a442012-03-30 16:42:05 -0700807 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800808 }
809
810 @Override
Mindy Pereira68f2e222012-03-07 10:36:54 -0800811 public void onSearchRequested(String query) {
812 Intent intent = new Intent();
813 intent.setAction(Intent.ACTION_SEARCH);
814 intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query);
815 intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount);
816 intent.setComponent(mActivity.getComponentName());
Vikram Aggarwalb17cbc02012-04-06 15:41:46 -0700817 mActionBarView.collapseSearch();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800818 mActivity.startActivity(intent);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800819 }
820
821 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800822 public void onStartDragMode() {
823 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800824 }
825
826 @Override
827 public void onStop() {
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700828 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800829 }
830
831 @Override
832 public void onStopDragMode() {
833 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800834 }
835
Andy Huang632721e2012-04-11 16:57:26 -0700836 @Override
837 public void onDestroy() {
838 // unregister the ViewPager's observer on the conversation cursor
839 mPagerController.onDestroy();
840 }
841
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800842 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800843 * {@inheritDoc} Subclasses must override this to listen to mode changes
844 * from the ViewMode. Subclasses <b>must</b> call the parent's
845 * onViewModeChanged since the parent will handle common state changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800846 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800847 @Override
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800848 public void onViewModeChanged(int newMode) {
849 // Perform any mode specific work here.
Mindy Pereira161f50d2012-02-28 15:47:19 -0800850 // reset the action bar icon based on the mode. Why don't the individual
851 // controllers do
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800852 // this themselves?
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800853
Mindy Pereira161f50d2012-02-28 15:47:19 -0800854 // We don't want to invalidate the options menu when switching to
855 // conversation
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800856 // mode, as it will happen when the conversation finishes loading.
857 if (newMode != ViewMode.CONVERSATION) {
858 mActivity.invalidateOptionsMenu();
859 }
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800860 }
861
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700862 protected void commitLeaveBehindItems() {
863 ConversationListFragment fragment = getConversationListFragment();
864 if (fragment != null) {
865 fragment.commitLeaveBehindItems();
866 }
867 }
868
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800869 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800870 public void onWindowFocusChanged(boolean hasFocus) {
Paul Westbrook9f119c72012-04-24 16:10:59 -0700871 ConversationListFragment convList = getConversationListFragment();
872 if (hasFocus && convList != null && convList.isVisible()) {
873 // The conversation list is visible.
874 Utils.setConversationCursorVisibility(mConversationListCursor, true);
875 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800876 }
877
Mindy Pereira75181e82012-04-18 08:17:13 -0700878 private void setAccount(Account account) {
Andy Huangb9ca9792012-05-18 15:31:49 -0700879 if (account == null) {
880 LogUtils.w(LOG_TAG, new Error(),
881 "AAC ignoring null (presumably invalid) account restoration");
882 return;
883 }
Andy Huangb1148412012-05-19 00:16:30 -0700884 LogUtils.d(LOG_TAG, "AbstractActivityController.setAccount(): account = %s", account.uri);
Vikram Aggarwal91e87372012-05-18 15:36:04 -0700885 mAccount = account;
Mindy Pereira75181e82012-04-18 08:17:13 -0700886 mActionBarView.setAccount(mAccount);
Vikram Aggarwal91e87372012-05-18 15:36:04 -0700887 if (account.settings == null) {
888 LogUtils.w(LOG_TAG, new Error(), "AAC ignoring account with null settings.");
889 return;
890 }
891 dispatchSettingsChange(mAccount.settings);
Mindy Pereira75181e82012-04-18 08:17:13 -0700892 }
893
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800894 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800895 * Restore the state from the previous bundle. Subclasses should call this
896 * method from the parent class, since it performs important UI
897 * initialization.
898 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800899 * @param savedState
900 */
Andy Huang632721e2012-04-11 16:57:26 -0700901 @Override
902 public void onRestoreInstanceState(Bundle savedState) {
903 LogUtils.d(LOG_TAG, "IN AAC.onRestoreInstanceState");
904 if (savedState.containsKey(SAVED_CONVERSATION)) {
905 // Open the conversation.
Paul Westbrook534e4a22012-04-25 03:46:29 -0700906 final Conversation conversation =
907 (Conversation)savedState.getParcelable(SAVED_CONVERSATION);
908 if (conversation != null && conversation.position < 0) {
909 // Set the position to 0 on this conversation, as we don't know where it is
910 // in the list
911 conversation.position = 0;
912 }
913 setCurrentConversation(conversation);
Andy Huang632721e2012-04-11 16:57:26 -0700914 showConversation(mCurrentConversation);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800915 }
Mindy Pereira967ede62012-03-22 09:29:09 -0700916
917 /**
918 * Restore the state of selected conversations. This needs to be done after the correct mode
919 * is set and the action bar is fully initialized. If not, several key pieces of state
920 * information will be missing, and the split views may not be initialized correctly.
921 * @param savedState
922 */
Andy Huang4556a442012-03-30 16:42:05 -0700923 restoreSelectedConversations(savedState);
Andy Huang632721e2012-04-11 16:57:26 -0700924 }
925
926 private void handleIntent(Intent intent) {
927 boolean handled = false;
928 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
929 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
930 setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
931 } else if (intent.hasExtra(Utils.EXTRA_ACCOUNT_STRING)) {
932 setAccount(Account.newinstance(intent
933 .getStringExtra(Utils.EXTRA_ACCOUNT_STRING)));
934 }
Andy Huangb9ca9792012-05-18 15:31:49 -0700935 if (mAccount == null) {
936 return;
Andy Huang632721e2012-04-11 16:57:26 -0700937 }
Andy Huangb9ca9792012-05-18 15:31:49 -0700938 mActivity.invalidateOptionsMenu();
Andy Huang632721e2012-04-11 16:57:26 -0700939
940 Folder folder = null;
941 if (intent.hasExtra(Utils.EXTRA_FOLDER)) {
942 // Open the folder.
943 LogUtils.d(LOG_TAG, "SHOW THE FOLDER at %s",
944 intent.getParcelableExtra(Utils.EXTRA_FOLDER));
945 folder = (Folder) intent.getParcelableExtra(Utils.EXTRA_FOLDER);
946
947 } else if (intent.hasExtra(Utils.EXTRA_FOLDER_STRING)) {
948 // Open the folder.
949 folder = new Folder(intent.getStringExtra(Utils.EXTRA_FOLDER_STRING));
950 }
951 if (folder != null) {
952 onFolderChanged(folder);
953 handled = true;
954 }
955
956 if (intent.hasExtra(Utils.EXTRA_CONVERSATION)) {
957 // Open the conversation.
958 LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
959 intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
Paul Westbrooka9161912012-04-24 10:10:04 -0700960 final Conversation conversation =
961 (Conversation)intent.getParcelableExtra(Utils.EXTRA_CONVERSATION);
962 if (conversation != null && conversation.position < 0) {
963 // Set the position to 0 on this conversation, as we don't know where it is
964 // in the list
965 conversation.position = 0;
966 }
967 setCurrentConversation(conversation);
Andy Huang632721e2012-04-11 16:57:26 -0700968 showConversation(mCurrentConversation);
969 handled = true;
970 }
971
972 if (!handled) {
973 // Nothing was saved; just load the account inbox.
974 loadAccountInbox();
975 }
976 } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
977 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
978 // Save this search query for future suggestions.
979 final String query = intent.getStringExtra(SearchManager.QUERY);
980 final String authority = mContext.getString(R.string.suggestions_authority);
981 SearchRecentSuggestions suggestions = new SearchRecentSuggestions(
982 mContext, authority, SuggestionsProvider.MODE);
983 suggestions.saveRecentQuery(query, null);
984
985 mViewMode.enterSearchResultsListMode();
986 setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
987 mActivity.invalidateOptionsMenu();
988 restartOptionalLoader(LOADER_RECENT_FOLDERS);
989 mRecentFolderList.setCurrentAccount(mAccount);
990 fetchSearchFolder(intent);
991 } else {
992 LogUtils.e(LOG_TAG, "Missing account extra from search intent. Finishing");
993 mActivity.finish();
994 }
995 }
996 if (mAccount != null) {
997 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
998 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800999 }
1000
Andy Huang4556a442012-03-30 16:42:05 -07001001 /**
1002 * Copy any selected conversations stored in the saved bundle into our selection set,
1003 * triggering {@link ConversationSetObserver} callbacks as our selection set changes.
1004 *
1005 */
Mindy Pereira967ede62012-03-22 09:29:09 -07001006 private void restoreSelectedConversations(Bundle savedState) {
1007 if (savedState == null) {
Andy Huang4556a442012-03-30 16:42:05 -07001008 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001009 return;
1010 }
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001011 final ConversationSelectionSet selectedSet = savedState.getParcelable(SAVED_SELECTED_SET);
Andy Huang4556a442012-03-30 16:42:05 -07001012 if (selectedSet == null || selectedSet.isEmpty()) {
1013 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001014 return;
1015 }
Andy Huang632721e2012-04-11 16:57:26 -07001016
1017 // putAll will take care of calling our registered onSetPopulated method
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001018 mSelectedSet.putAll(selectedSet);
Mindy Pereira967ede62012-03-22 09:29:09 -07001019 }
1020
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001021 @Override
1022 public void setSubject(String subject) {
1023 // Do something useful with the subject. This requires changing the
1024 // conversation view's subject text.
1025 }
1026
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001027 /**
1028 * Children can override this method, but they must call super.showConversation().
1029 * {@inheritDoc}
1030 */
1031 @Override
1032 public void showConversation(Conversation conversation) {
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001033 // Set the current conversation just in case it wasn't already set.
1034 setCurrentConversation(conversation);
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001035 }
1036
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001037 /**
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001038 * Children can override this method, but they must call super.showWaitForInitialization().
1039 * {@inheritDoc}
1040 */
1041 @Override
1042 public void showWaitForInitialization() {
1043 mViewMode.enterWaitingForInitializationMode();
1044 }
1045
1046 @Override
1047 public void hideWaitForInitialization() {
1048 }
1049
1050 @Override
1051 public void updateWaitMode() {
1052 final FragmentManager manager = mActivity.getFragmentManager();
1053 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001054 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001055 if (waitFragment != null) {
1056 waitFragment.updateAccount(mAccount);
1057 }
1058 }
1059
1060 @Override
1061 public boolean inWaitMode() {
1062 final FragmentManager manager = mActivity.getFragmentManager();
1063 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001064 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001065 if (waitFragment != null) {
1066 final Account fragmentAccount = waitFragment.getAccount();
1067 return fragmentAccount.uri.equals(mAccount.uri) &&
1068 mViewMode.getMode() == ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION;
1069 }
1070 return false;
1071 }
1072
1073 /**
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001074 * Children can override this method, but they must call super.showConversationList().
1075 * {@inheritDoc}
1076 */
1077 @Override
1078 public void showConversationList(ConversationListContext listContext) {
1079 }
1080
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001081 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -08001082 public void onConversationSelected(Conversation conversation) {
Vikram Aggarwal5b7b3ab2012-04-03 15:43:55 -07001083 showConversation(conversation);
Mindy Pereira9fa43ca2012-05-17 14:18:01 -07001084 if (Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())) {
Mindy Pereira68f2e222012-03-07 10:36:54 -08001085 mViewMode.enterSearchResultsConversationMode();
1086 } else {
1087 mViewMode.enterConversationMode();
1088 }
Vikram Aggarwal7d602882012-02-07 15:01:20 -08001089 }
Mindy Pereira555140c2012-02-15 14:55:29 -08001090
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001091 /**
1092 * Set the current conversation. This is the conversation on which all actions are performed.
1093 * Do not modify mCurrentConversation except through this method, which makes it easy to
1094 * perform common actions associated with changing the current conversation.
1095 * @param conversation
1096 */
Andy Huang632721e2012-04-11 16:57:26 -07001097 @Override
1098 public void setCurrentConversation(Conversation conversation) {
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001099 mCurrentConversation = conversation;
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001100 mTracker.initialize(mCurrentConversation);
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001101 }
1102
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001103 /**
1104 * {@inheritDoc}
1105 */
Mindy Pereira555140c2012-02-15 14:55:29 -08001106 @Override
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001107 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
1108 // Create a loader to listen in on account changes.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001109 switch (id) {
1110 case LOADER_ACCOUNT_CURSOR:
Paul Westbrookc2074c42012-03-22 15:26:58 -07001111 return new CursorLoader(mContext, MailAppProvider.getAccountsUri(),
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001112 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
1113 case LOADER_FOLDER_CURSOR:
Paul Westbrookc7a070f2012-04-12 01:46:41 -07001114 final CursorLoader loader = new CursorLoader(mContext, mFolder.uri,
1115 UIProvider.FOLDERS_PROJECTION, null, null, null);
1116 loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
1117 return loader;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001118 case LOADER_RECENT_FOLDERS:
Paul Westbrook91d10502012-04-13 12:01:39 -07001119 if (mAccount != null && mAccount.recentFolderListUri != null) {
Paul Westbrookea4ee202012-03-12 14:12:33 -07001120 return new CursorLoader(mContext, mAccount.recentFolderListUri,
1121 UIProvider.FOLDERS_PROJECTION, null, null, null);
1122 }
1123 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001124 case LOADER_ACCOUNT_INBOX:
Vikram Aggarwal025eba82012-05-08 10:45:30 -07001125 final Uri defaultInbox = Settings.getDefaultInboxUri(mAccount.settings);
Vikram Aggarwal1e57e672012-05-07 14:48:24 -07001126 final Uri inboxUri = defaultInbox.equals(Uri.EMPTY) ?
1127 mAccount.folderListUri : defaultInbox;
Paul Westbrook7496e822012-04-24 09:50:54 -07001128 LogUtils.d(LOG_TAG, "Loading the default inbox: %s", inboxUri);
Paul Westbrook1220b6d2012-04-10 00:48:00 -07001129 if (inboxUri != null) {
1130 return new CursorLoader(mContext, inboxUri, UIProvider.FOLDERS_PROJECTION, null,
1131 null, null);
1132 }
1133 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001134 case LOADER_SEARCH:
1135 return Folder.forSearchResults(mAccount,
1136 args.getString(ConversationListContext.EXTRA_SEARCH_QUERY),
1137 mActivity.getActivityContext());
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001138 case LOADER_ACCOUNT_UPDATE_CURSOR:
1139 return new CursorLoader(mContext, mAccount.uri, UIProvider.ACCOUNTS_PROJECTION,
1140 null, null, null);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001141 default:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001142 LogUtils.wtf(LOG_TAG, "Loader returned unexpected id: %d", id);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001143 }
1144 return null;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001145 }
1146
Paul Westbrookb1f573c2012-04-06 11:38:28 -07001147 @Override
1148 public void onLoaderReset(Loader<Cursor> loader) {
1149
1150 }
1151
Andy Huangf9a73482012-03-13 15:54:02 -07001152 /**
1153 * {@link LoaderManager} currently has a bug in
1154 * {@link LoaderManager#restartLoader(int, Bundle, android.app.LoaderManager.LoaderCallbacks)}
1155 * where, if a previous onCreateLoader returned a null loader, this method will NPE. Work around
1156 * this bug by destroying any loaders that may have been created as null (essentially because
1157 * they are optional loads, and may not apply to a particular account).
1158 * <p>
1159 * A simple null check before restarting a loader will not work, because that would not
1160 * give the controller a chance to invalidate UI corresponding the prior loader result.
1161 *
1162 * @param id loader ID to safely restart
Andy Huangf9a73482012-03-13 15:54:02 -07001163 */
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001164 private void restartOptionalLoader(int id) {
Andy Huangf9a73482012-03-13 15:54:02 -07001165 final LoaderManager lm = mActivity.getLoaderManager();
1166 lm.destroyLoader(id);
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001167 lm.restartLoader(id, Bundle.EMPTY, this);
1168 }
1169
1170 /**
1171 * Start a loader with the given id. This should be called when we know that the previous
1172 * state of the application matches this state, and we are happy if we get the previously
1173 * created loader with this id. If that is not true, consider calling
1174 * {@link #restartOptionalLoader(int)} instead.
1175 * @param id
1176 */
1177 private void startLoader(int id) {
1178 final LoaderManager lm = mActivity.getLoaderManager();
1179 lm.initLoader(id, Bundle.EMPTY, this);
Andy Huangf9a73482012-03-13 15:54:02 -07001180 }
1181
Andy Huang632721e2012-04-11 16:57:26 -07001182 @Override
1183 public void registerConversationListObserver(DataSetObserver observer) {
1184 mConversationListObservable.registerObserver(observer);
1185 }
1186
1187 @Override
1188 public void unregisterConversationListObserver(DataSetObserver observer) {
1189 mConversationListObservable.unregisterObserver(observer);
1190 }
1191
Paul Westbrook23b74b92012-02-29 11:36:12 -08001192 private boolean accountsUpdated(Cursor accountCursor) {
1193 // Check to see if the current account hasn't been set, or the account cursor is empty
1194 if (mAccount == null || !accountCursor.moveToFirst()) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001195 return true;
Paul Westbrook23b74b92012-02-29 11:36:12 -08001196 }
1197
1198 // Check to see if the number of accounts are different, from the number we saw on the last
1199 // updated
1200 if (mCurrentAccountUris.size() != accountCursor.getCount()) {
1201 return true;
1202 }
1203
1204 // Check to see if the account list is different or if the current account is not found in
1205 // the cursor.
1206 boolean foundCurrentAccount = false;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001207 do {
Paul Westbrook23b74b92012-02-29 11:36:12 -08001208 final Uri accountUri =
1209 Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
1210 if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
1211 foundCurrentAccount = true;
1212 }
1213
1214 if (!mCurrentAccountUris.contains(accountUri)) {
1215 return true;
1216 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001217 } while (accountCursor.moveToNext());
Paul Westbrook23b74b92012-02-29 11:36:12 -08001218
1219 // As long as we found the current account, the list hasn't been updated
1220 return !foundCurrentAccount;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001221 }
1222
1223 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -08001224 * Update the accounts on the device. This currently loads the first account
1225 * in the list.
1226 *
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001227 * @param loader
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001228 * @param accounts cursor into the AccountCache
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001229 * @return true if the update was successful, false otherwise
1230 */
Paul Westbrook23b74b92012-02-29 11:36:12 -08001231 private boolean updateAccounts(Loader<Cursor> loader, Cursor accounts) {
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001232 if (accounts == null || !accounts.moveToFirst()) {
1233 return false;
1234 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001235
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001236 final Account[] allAccounts = Account.getAllAccounts(accounts);
Paul Westbrook23b74b92012-02-29 11:36:12 -08001237
1238 // Save the uris for the accounts
1239 mCurrentAccountUris.clear();
1240 for (Account account : allAccounts) {
1241 mCurrentAccountUris.add(account.uri);
1242 }
1243
Andy Huang0d647352012-03-21 21:48:16 -07001244 // 1. current account is already set and is in allAccounts -> no-op
1245 // 2. current account is set and is not in allAccounts -> pick first (acct was deleted?)
1246 // 3. saved pref has an account -> pick that one
1247 // 4. otherwise just pick first
1248
1249 Account newAccount = null;
1250
1251 if (mAccount != null) {
1252 if (!mCurrentAccountUris.contains(mAccount.uri)) {
1253 newAccount = allAccounts[0];
1254 } else {
1255 newAccount = mAccount;
1256 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001257 } else {
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001258 final String lastAccountUri = MailAppProvider.getInstance().getLastViewedAccount();
Andy Huang0d647352012-03-21 21:48:16 -07001259 if (lastAccountUri != null) {
1260 for (int i = 0; i < allAccounts.length; i++) {
1261 final Account acct = allAccounts[i];
1262 if (lastAccountUri.equals(acct.uri.toString())) {
1263 newAccount = acct;
1264 break;
1265 }
1266 }
1267 }
1268 if (newAccount == null) {
1269 newAccount = allAccounts[0];
1270 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001271 }
Andy Huang0d647352012-03-21 21:48:16 -07001272
Paul Westbrook23b74b92012-02-29 11:36:12 -08001273 onAccountChanged(newAccount);
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001274 mActionBarView.setAccounts(allAccounts);
1275 return (allAccounts.length > 0);
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001276 }
1277
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001278 private void disableNotifications() {
1279 mNewEmailReceiver.activate(mContext, this);
1280 }
1281
1282 private void enableNotifications() {
1283 mNewEmailReceiver.deactivate();
1284 }
1285
1286 private void disableNotificationsOnAccountChange(Account account) {
1287 // If the new mail suppression receiver is activated for a different account, we want to
1288 // activate it for the new account.
1289 if (mNewEmailReceiver.activated() &&
1290 !mNewEmailReceiver.notificationsDisabledForAccount(account)) {
1291 // Deactivate the current receiver, otherwise multiple receivers may be registered.
1292 mNewEmailReceiver.deactivate();
1293 mNewEmailReceiver.activate(mContext, this);
1294 }
1295 }
1296
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001297 /**
1298 * {@inheritDoc}
1299 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001300 @Override
1301 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001302 // We want to reinitialize only if we haven't ever been initialized, or
1303 // if the current account has vanished.
Paul Westbrooke3e84292012-03-05 16:19:30 -08001304 if (data == null) {
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001305 LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
Paul Westbrooke3e84292012-03-05 16:19:30 -08001306 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001307 switch (loader.getId()) {
1308 case LOADER_ACCOUNT_CURSOR:
Paul Westbrook2388c5d2012-03-25 12:29:11 -07001309 // If the account list is not not null, and the account list cursor is empty,
1310 // we need to start the specified activity.
1311 if (data != null && data.getCount() == 0) {
1312 // If an empty cursor is returned, the MailAppProvider is indicating that
1313 // no accounts have been specified. We want to navigate to the "add account"
1314 // activity that will handle the intent returned by the MailAppProvider
1315
1316 // If the MailAppProvider believes that all accounts have been loaded, and the
1317 // account list is still empty, we want to prompt the user to add an account
1318 final Bundle extras = data.getExtras();
1319 final boolean accountsLoaded =
1320 extras.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
1321
1322 if (accountsLoaded) {
1323 final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(mContext);
1324 if (noAccountIntent != null) {
1325 mActivity.startActivityForResult(noAccountIntent,
1326 ADD_ACCOUNT_REQUEST_CODE);
1327 }
1328 }
1329 } else {
1330 final boolean accountListUpdated = accountsUpdated(data);
1331 if (!isLoaderInitialized || accountListUpdated) {
1332 isLoaderInitialized = updateAccounts(loader, data);
1333 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001334 }
1335 break;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001336 case LOADER_ACCOUNT_UPDATE_CURSOR:
1337 // We have gotten an update for current account.
1338
1339 // Make sure that this is an update for what is the current account
1340 if (data != null && data.moveToFirst()) {
1341 final Account updatedAccount = new Account(data);
1342
1343 if (updatedAccount.uri.equals(mAccount.uri)) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001344 // Update the controller's reference to the current account
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001345 mAccount = updatedAccount;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -07001346 LogUtils.d(LOG_TAG, "AbstractActivityController.onLoadFinished(): "
1347 + "mAccount = %s", mAccount.uri);
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001348 dispatchSettingsChange(mAccount.settings);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001349
1350 // Got an update for the current account
1351 final boolean inWaitingMode = inWaitMode();
1352 if (!updatedAccount.isAccountIntialized() && !inWaitingMode) {
1353 // Transition to waiting mode
1354 showWaitForInitialization();
1355 } else if (updatedAccount.isAccountIntialized() && inWaitingMode) {
1356 // Dismiss waiting mode
1357 hideWaitForInitialization();
1358 } else if (!updatedAccount.isAccountIntialized() && inWaitingMode) {
1359 // Update the WaitFragment's account object
1360 updateWaitMode();
1361 }
1362 } else {
1363 LogUtils.e(LOG_TAG, "Got update for account: %s with current account: %s",
1364 updatedAccount.uri, mAccount.uri);
1365 // We need to restart the loader, so the correct account information will
1366 // be returned
1367 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1368 }
1369 }
1370 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001371 case LOADER_FOLDER_CURSOR:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001372 // Check status of the cursor.
Marc Blankfd9d0b82012-04-23 16:01:51 -07001373 if (data != null && data.moveToFirst()) {
1374 Folder folder = new Folder(data);
1375 if (folder.isSyncInProgress()) {
1376 mActionBarView.onRefreshStarted();
1377 } else {
1378 // Stop the spinner here.
1379 mActionBarView.onRefreshStopped(folder.lastSyncResult);
1380 }
1381 mActionBarView.onFolderUpdated(folder);
1382 final ConversationListFragment convList = getConversationListFragment();
1383 if (convList != null) {
1384 convList.onFolderUpdated(folder);
1385 }
1386 LogUtils.d(LOG_TAG, "FOLDER STATUS = %d", folder.syncStatus);
Paul Westbrookc808fac2012-02-22 16:42:18 -08001387 } else {
Marc Blankfd9d0b82012-04-23 16:01:51 -07001388 LogUtils.d(LOG_TAG, "Unable to get the folder %s",
1389 mFolder != null ? mAccount.name : "");
Mindy Pereira11dd5ef2012-03-10 15:10:18 -08001390 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001391 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001392 case LOADER_RECENT_FOLDERS:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001393 mRecentFolderList.loadFromUiProvider(data);
Vikram Aggarwal37263972012-04-17 15:51:14 -07001394 mActionBarView.requestRecentFoldersAndRedraw();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001395 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001396 case LOADER_ACCOUNT_INBOX:
Marc Blankfd9d0b82012-04-23 16:01:51 -07001397 if (data != null && !data.isClosed() && data.moveToFirst()) {
Mindy Pereira0e88e9f2012-03-25 13:47:41 -07001398 Folder inbox = new Folder(data);
1399 onFolderChanged(inbox);
Mindy Pereirab4a43282012-03-23 16:20:03 -07001400 // Just want to get the inbox, don't care about updates to it
1401 // as this will be tracked by the folder change listener.
1402 mActivity.getLoaderManager().destroyLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereira5ba33802012-03-26 16:30:11 -07001403 } else {
1404 LogUtils.d(LOG_TAG, "Unable to get the account inbox for account %s",
1405 mAccount != null ? mAccount.name : "");
Mindy Pereirab4a43282012-03-23 16:20:03 -07001406 }
Mindy Pereiraab486362012-03-21 18:18:53 -07001407 break;
1408 case LOADER_SEARCH:
1409 data.moveToFirst();
1410 Folder search = new Folder(data);
1411 setFolder(search);
1412 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder,
Mindy Pereirad660d252012-03-26 11:48:43 -07001413 mActivity.getIntent()
1414 .getStringExtra(UIProvider.SearchQueryParameters.QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -07001415 showConversationList(mConvListContext);
1416 mActivity.invalidateOptionsMenu();
Mindy Pereira3b399222012-03-28 15:19:47 -07001417 mActivity.getLoaderManager().destroyLoader(LOADER_SEARCH);
Mindy Pereiraab486362012-03-21 18:18:53 -07001418 break;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001419 }
1420 }
1421
Vikram Aggarwalc7694222012-04-23 13:37:01 -07001422 /**
1423 * Destructive actions on Conversations. This class should only be created by controllers, and
1424 * clients should only require {@link DestructiveAction}s, not specific implementations of the.
1425 * Only the controllers should know what kind of destructive actions are being created.
1426 */
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001427 protected class ConversationAction implements DestructiveAction {
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001428 /**
1429 * The action to be performed. This is specified as the resource ID of the menu item
1430 * corresponding to this action: R.id.delete, R.id.report_spam, etc.
1431 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001432 private final int mAction;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001433 /** The action will act upon these conversations */
1434 private final Collection<Conversation> mTarget = new ArrayList<Conversation>();
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001435 /** Whether this destructive action has already been performed */
1436 private boolean mCompleted;
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001437 /** Whether this is an action on the currently selected set. */
1438 private final boolean mIsSelectedSet;
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001439
Mindy Pereirafbe40192012-03-20 10:40:45 -07001440 /**
1441 * Create a listener object. action is one of four constants: R.id.y_button (archive),
1442 * R.id.delete , R.id.mute, and R.id.report_spam.
1443 * @param action
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001444 * @param target Conversation that we want to apply the action to.
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001445 * @param isBatch whether the conversations are in the currently selected batch set.
1446 */
1447 public ConversationAction(int action, Collection<Conversation> target, boolean isBatch) {
Mindy Pereirafbe40192012-03-20 10:40:45 -07001448 mAction = action;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001449 mTarget.addAll(target);
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001450 mIsSelectedSet = isBatch;
Mindy Pereirafbe40192012-03-20 10:40:45 -07001451 }
1452
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001453 /**
1454 * The action common to child classes. This performs the action specified in the constructor
1455 * on the conversations given here.
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001456 */
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001457 @Override
1458 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001459 if (isPerformed()) {
1460 return;
1461 }
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001462 // Certain actions force a return to list.
1463 boolean forceReturnToList = false;
Mindy Pereira3cb28b52012-05-24 15:26:39 -07001464 boolean undoEnabled = mAccount.supportsCapability(AccountCapabilities.UNDO);
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001465
1466 // Are we destroying the currently shown conversation? Show the next one.
1467 if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)){
1468 LogUtils.d(LOG_TAG, "ConversationAction.performAction(): mIsConversationVisible=%b"
1469 + "\nmTarget=%s\nCurrent=%s", mIsConversationVisible,
1470 Conversation.toString(mTarget), mCurrentConversation);
1471 }
1472 if (mIsConversationVisible && Conversation.contains(mTarget, mCurrentConversation)) {
1473 final Conversation next = forceReturnToList ? null :
1474 mTracker.getNextConversation(Settings.getAutoAdvanceSetting(mAccount.settings),
1475 mTarget, mCurrentConversation);
1476 LogUtils.d(LOG_TAG, "Next conversation is: %s", next);
1477 showConversation(next);
1478 }
1479
Mindy Pereirafbe40192012-03-20 10:40:45 -07001480 switch (mAction) {
Mindy Pereira0692baf2012-03-23 17:34:31 -07001481 case R.id.archive:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001482 LogUtils.d(LOG_TAG, "Archiving");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001483 mConversationListCursor.archive(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001484 break;
1485 case R.id.delete:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001486 LogUtils.d(LOG_TAG, "Deleting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001487 mConversationListCursor.delete(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001488 break;
1489 case R.id.mute:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001490 LogUtils.d(LOG_TAG, "Muting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001491 if (mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE)) {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001492 for (Conversation c : mTarget) {
1493 c.localDeleteOnUpdate = true;
1494 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001495 }
1496 mConversationListCursor.mute(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001497 break;
1498 case R.id.report_spam:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001499 LogUtils.d(LOG_TAG, "Reporting spam");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001500 mConversationListCursor.reportSpam(mContext, mTarget);
1501 break;
1502 case R.id.remove_star:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001503 LogUtils.d(LOG_TAG, "Removing star");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001504 // Star removal is destructive in the Starred folder.
1505 mConversationListCursor.updateBoolean(mContext, mTarget,
1506 ConversationColumns.STARRED, false);
1507 break;
1508 case R.id.mark_not_important:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001509 LogUtils.d(LOG_TAG, "Marking not-important");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001510 // Marking not important is destructive in a mailbox containing only important
1511 // messages
1512 mConversationListCursor.updateInt(mContext, mTarget,
1513 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001514 break;
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001515 case R.id.inside_conversation_unread:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001516 LogUtils.d(LOG_TAG, "Marking conversation unread");
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001517 mConversationListCursor.updateBoolean(mContext, mTarget,
1518 ConversationColumns.READ, false);
1519 forceReturnToList = true;
1520 undoEnabled = false;
1521 }
1522 if (undoEnabled) {
1523 onUndoAvailable(new UndoOperation(mTarget.size(), mAction));
1524 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001525 refreshConversationList();
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001526 if (mIsSelectedSet) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001527 mSelectedSet.clear();
1528 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001529 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001530
1531 /**
1532 * Returns true if this action has been performed, false otherwise.
1533 * @return
1534 */
1535 private synchronized boolean isPerformed() {
1536 if (mCompleted) {
1537 return true;
1538 }
1539 mCompleted = true;
1540 return false;
1541 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001542 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001543
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001544 /**
1545 * Get a destructive action for a menu action.
1546 * This is a temporary method, to control the profusion of {@link DestructiveAction} classes
1547 * that are created. Please do not copy this paradigm.
1548 * @param action the resource ID of the menu action: R.id.delete, for example
1549 * @param target the conversations to act upon.
1550 * @return a {@link DestructiveAction} that performs the specified action.
1551 */
1552 protected final DestructiveAction getAction(int action, Collection<Conversation> target) {
1553 final DestructiveAction da = new ConversationAction(action, target, false);
1554 registerDestructiveAction(da);
1555 return da;
1556 }
1557
Vikram Aggarwald503df42012-05-11 10:13:35 -07001558 // Called from the FolderSelectionDialog after a user is done selecting folders to assign the
1559 // conversations to.
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001560 @Override
Vikram Aggarwald503df42012-05-11 10:13:35 -07001561 public final void onFolderChangesCommit(
1562 Collection<Folder> folders, Collection<Conversation> target) {
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001563 final boolean isDestructive = !Folder.containerIncludes(folders, mFolder);
Vikram Aggarwald503df42012-05-11 10:13:35 -07001564 LogUtils.d(LOG_TAG, "onFolderChangesCommit: isDestructive = %b", isDestructive);
1565 if (isDestructive) {
1566 for (final Conversation c : target) {
1567 c.localDeleteOnUpdate = true;
Mindy Pereira6778f462012-03-23 18:01:55 -07001568 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001569 }
Vikram Aggarwald503df42012-05-11 10:13:35 -07001570 final DestructiveAction folderChange = getFolderChange(target, folders, isDestructive);
1571 // Update the UI elements depending no their visibility and availability
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001572 // TODO(viki): Consolidate this into a single method requestDelete.
Vikram Aggarwald503df42012-05-11 10:13:35 -07001573 if (isDestructive) {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001574 requestDelete(target, folderChange);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001575 } else {
Vikram Aggarwald503df42012-05-11 10:13:35 -07001576 requestUpdate(target, folderChange);
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001577 }
1578 }
1579
Mindy Pereira967ede62012-03-22 09:29:09 -07001580 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001581 public final void onRefreshRequired() {
Marc Blankbf128eb2012-04-18 15:58:45 -07001582 if (mIsConversationListScrolling) {
1583 LogUtils.d(LOG_TAG, "onRefreshRequired: delay until scrolling done");
1584 return;
1585 }
1586 // Refresh the query in the background
Vikram Aggarwalb9fb2c12012-05-11 14:25:25 -07001587 final long now = System.currentTimeMillis();
1588 final long sinceLastRefresh = now - mConversationListRefreshTime;
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001589 if (mConversationListCursor.isRefreshRequired()) {
1590 mConversationListCursor.refresh();
Marc Blankbf128eb2012-04-18 15:58:45 -07001591 mTracker.updateCursor(mConversationListCursor);
1592 mConversationListRefreshTime = now;
1593 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001594 }
1595
1596 /**
1597 * Called when the {@link ConversationCursor} is changed or has new data in it.
1598 * <p>
1599 * {@inheritDoc}
1600 */
1601 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001602 public final void onRefreshReady() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001603 if (!mIsConversationListScrolling) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001604 // Swap cursors
1605 mConversationListCursor.sync();
Marc Blankbf128eb2012-04-18 15:58:45 -07001606 }
1607 mTracker.updateCursor(mConversationListCursor);
1608 }
1609
1610 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001611 public final void onDataSetChanged() {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001612 updateConversationListFragment();
Andy Huang632721e2012-04-11 16:57:26 -07001613 mConversationListObservable.notifyChanged();
Marc Blankbf128eb2012-04-18 15:58:45 -07001614 }
1615
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001616 /**
1617 * If the Conversation List Fragment is visible, updates the fragment.
1618 */
1619 private final void updateConversationListFragment() {
Marc Blankbf128eb2012-04-18 15:58:45 -07001620 final ConversationListFragment convList = getConversationListFragment();
1621 if (convList != null) {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07001622 refreshConversationList();
Paul Westbrook9f119c72012-04-24 16:10:59 -07001623 if (convList.isVisible()) {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001624 Utils.setConversationCursorVisibility(mConversationListCursor, true);
1625 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001626 }
1627 }
1628
1629 /**
1630 * This class handles throttled refresh of the conversation list
1631 */
1632 static class RefreshTimerTask extends TimerTask {
1633 final Handler mHandler;
1634 final AbstractActivityController mController;
1635
1636 RefreshTimerTask(AbstractActivityController controller, Handler handler) {
1637 mHandler = handler;
1638 mController = controller;
1639 }
1640
1641 @Override
1642 public void run() {
1643 mHandler.post(new Runnable() {
1644 @Override
1645 public void run() {
1646 LogUtils.d(LOG_TAG, "Delay done... calling onRefreshRequired");
1647 mController.onRefreshRequired();
1648 }});
1649 }
1650 }
1651
1652 /**
1653 * Cancel the refresh task, if it's running
1654 */
1655 private void cancelRefreshTask () {
1656 if (mConversationListRefreshTask != null) {
1657 mConversationListRefreshTask.cancel();
1658 mConversationListRefreshTask = null;
1659 }
1660 }
1661
1662 @Override
1663 public void onScrollStateChanged(AbsListView view, int scrollState) {
1664 boolean isScrolling = (scrollState != OnScrollListener.SCROLL_STATE_IDLE);
1665 if (!isScrolling) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001666 if (mConversationListCursor.isRefreshRequired()) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001667 LogUtils.d(LOG_TAG, "Stop scrolling: refresh");
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001668 mConversationListCursor.refresh();
1669 } else if (mConversationListCursor.isRefreshReady()) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001670 LogUtils.d(LOG_TAG, "Stop scrolling: try sync");
1671 onRefreshReady();
1672 }
1673 }
1674 mIsConversationListScrolling = isScrolling;
1675 }
1676
1677 @Override
1678 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1679 int totalItemCount) {
1680 }
1681
1682 @Override
Mindy Pereira967ede62012-03-22 09:29:09 -07001683 public void onSetEmpty() {
Mindy Pereira967ede62012-03-22 09:29:09 -07001684 }
1685
1686 @Override
1687 public void onSetPopulated(ConversationSelectionSet set) {
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001688 final ConversationListFragment convList = getConversationListFragment();
1689 if (convList == null) {
1690 return;
1691 }
Mindy Pereira07118a02012-04-02 16:35:01 -07001692 mCabActionMenu = new SelectedConversationsActionMenu(mActivity, set,
Vikram Aggarwald503df42012-05-11 10:13:35 -07001693 convList.getAnimatedAdapter(), this,
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001694 mAccount, mFolder, (SwipeableListView) convList.getListView());
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001695 enableCabMode();
Mindy Pereira967ede62012-03-22 09:29:09 -07001696 }
1697
Mindy Pereira967ede62012-03-22 09:29:09 -07001698 @Override
1699 public void onSetChanged(ConversationSelectionSet set) {
1700 // Do nothing. We don't care about changes to the set.
1701 }
1702
1703 @Override
1704 public ConversationSelectionSet getSelectedSet() {
1705 return mSelectedSet;
1706 }
1707
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001708 /**
1709 * Disable the Contextual Action Bar (CAB). The selected set is not changed.
1710 */
1711 protected void disableCabMode() {
1712 if (mCabActionMenu != null) {
1713 mCabActionMenu.deactivate();
1714 }
1715 }
1716
1717 /**
1718 * Re-enable the CAB menu if required. The selection set is not changed.
1719 */
1720 protected void enableCabMode() {
1721 if (mCabActionMenu != null) {
1722 mCabActionMenu.activate();
1723 }
1724 }
1725
Mindy Pereira967ede62012-03-22 09:29:09 -07001726 @Override
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001727 public void startSearch() {
Vikram Aggarwal35f19d72012-04-24 13:24:48 -07001728 if (mAccount == null) {
1729 // We cannot search if there is no account. Drop the request to the floor.
1730 LogUtils.d(LOG_TAG, "AbstractActivityController.startSearch(): null account");
1731 return;
1732 }
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001733 if (mAccount.supportsCapability(UIProvider.AccountCapabilities.LOCAL_SEARCH)
1734 | mAccount.supportsCapability(UIProvider.AccountCapabilities.SERVER_SEARCH)) {
Vikram Aggarwal70f130e2012-04-03 12:32:14 -07001735 onSearchRequested(mActionBarView.getQuery());
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001736 } else {
1737 Toast.makeText(mActivity.getActivityContext(), mActivity.getActivityContext()
Mindy Pereiraa46c2992012-03-27 14:12:39 -07001738 .getString(R.string.search_unsupported), Toast.LENGTH_SHORT).show();
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001739 }
1740 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07001741
Vikram Aggarwal0dda5732012-04-06 11:20:16 -07001742 @Override
1743 public void exitSearchMode() {
1744 if (mViewMode.getMode() == ViewMode.SEARCH_RESULTS_LIST) {
1745 mActivity.finish();
1746 }
1747 }
1748
Mindy Pereiraacf60392012-04-06 09:11:00 -07001749 /**
1750 * Supports dragging conversations to a folder.
1751 */
1752 @Override
1753 public boolean supportsDrag(DragEvent event, Folder folder) {
1754 return (folder != null
1755 && event != null
1756 && event.getClipDescription() != null
1757 && folder.supportsCapability
1758 (UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
1759 && folder.supportsCapability
1760 (UIProvider.FolderCapabilities.CAN_HOLD_MAIL)
1761 && !mFolder.uri.equals(folder.uri));
1762 }
1763
1764 /**
1765 * Handles dropping conversations to a label.
1766 */
1767 @Override
1768 public void handleDrop(DragEvent event, final Folder folder) {
Mindy Pereiraacf60392012-04-06 09:11:00 -07001769 if (!supportsDrag(event, folder)) {
1770 return;
1771 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07001772 final Collection<Conversation> conversations = mSelectedSet.values();
Vikram Aggarwal440fe792012-05-10 17:23:15 -07001773 final Collection<Folder> dropTarget = Folder.listOf(folder);
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001774 // Drag and drop is destructive: we remove conversations from the current folder.
1775 final DestructiveAction action = getFolderChange(conversations, dropTarget, true);
Vikram Aggarwald503df42012-05-11 10:13:35 -07001776 requestDelete(conversations, action);
Mindy Pereiraacf60392012-04-06 09:11:00 -07001777 }
Mindy Pereira0963ef82012-04-10 11:43:01 -07001778
1779 @Override
1780 public void onUndoCancel() {
1781 mUndoBarView.hide(false);
1782 }
1783
Paul Westbrookbf232c32012-04-18 03:17:41 -07001784
Mindy Pereira0963ef82012-04-10 11:43:01 -07001785 @Override
1786 public void onTouchEvent(MotionEvent event) {
1787 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1788 if (mUndoBarView != null && !mUndoBarView.isEventInUndo(event)) {
1789 mUndoBarView.hide(true);
1790 }
1791 }
1792 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001793
Andy Huang632721e2012-04-11 16:57:26 -07001794 @Override
1795 public void onConversationSeen(Conversation conv) {
1796 mPagerController.onConversationSeen(conv);
1797 }
1798
Andy Huangb1c34dc2012-04-17 16:36:19 -07001799 private class ConversationListLoaderCallbacks implements
1800 LoaderManager.LoaderCallbacks<ConversationCursor> {
1801
1802 @Override
1803 public Loader<ConversationCursor> onCreateLoader(int id, Bundle args) {
1804 Loader<ConversationCursor> result = new ConversationCursorLoader((Activity) mActivity,
Paul Westbrookbf232c32012-04-18 03:17:41 -07001805 mAccount, mFolder.conversationListUri, mFolder.name);
Andy Huangb1c34dc2012-04-17 16:36:19 -07001806 return result;
1807 }
1808
1809 @Override
1810 public void onLoadFinished(Loader<ConversationCursor> loader, ConversationCursor data) {
Andy Huang632721e2012-04-11 16:57:26 -07001811 LogUtils.d(LOG_TAG, "IN AAC.ConversationCursor.onLoadFinished, data=%s loader=%s",
1812 data, loader);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001813 // Clear our all pending destructive actions before swapping the conversation cursor
1814 destroyPending(null);
Andy Huangb1c34dc2012-04-17 16:36:19 -07001815 mConversationListCursor = data;
Paul Westbrookbf232c32012-04-18 03:17:41 -07001816 mConversationListCursor.addListener(AbstractActivityController.this);
Andy Huangb1c34dc2012-04-17 16:36:19 -07001817
Andy Huange3df1ad2012-04-24 17:15:23 -07001818 mConversationListObservable.notifyChanged();
1819
Andy Huangb1c34dc2012-04-17 16:36:19 -07001820 // Register the AbstractActivityController as a listener to changes in
1821 // data in the cursor.
1822 final ConversationListFragment convList = getConversationListFragment();
1823 if (convList != null) {
1824 convList.onCursorUpdated();
Paul Westbrookbf232c32012-04-18 03:17:41 -07001825 convList.getListView().setOnScrollListener(AbstractActivityController.this);
Paul Westbrook9f119c72012-04-24 16:10:59 -07001826
1827 if (convList.isVisible()) {
1828 // The conversation list is visible.
1829 Utils.setConversationCursorVisibility(mConversationListCursor, true);
1830 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001831 }
1832 // Shown for search results in two-pane mode only.
1833 if (shouldShowFirstConversation()) {
1834 if (mConversationListCursor.getCount() > 0) {
1835 mConversationListCursor.moveToPosition(0);
1836 if (convList != null) {
1837 convList.getListView().setItemChecked(0, true);
1838 }
1839 final Conversation conv = new Conversation(mConversationListCursor);
1840 conv.position = 0;
1841 onConversationSelected(conv);
1842 }
1843 }
1844 }
1845
1846 @Override
1847 public void onLoaderReset(Loader<ConversationCursor> loader) {
1848 final ConversationListFragment convList = getConversationListFragment();
1849 if (convList == null) {
1850 return;
1851 }
1852 convList.onCursorUpdated();
1853 }
Paul Westbrookbf232c32012-04-18 03:17:41 -07001854 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001855
Paul Westbrookbf232c32012-04-18 03:17:41 -07001856 @Override
1857 public void sendConversationRead(String toFragment, Conversation conversation, boolean state,
1858 boolean local) {
1859 if (toFragment.equals(TAG_CONVERSATION_LIST)) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001860 if (mConversationListCursor != null) {
Paul Westbrookbf232c32012-04-18 03:17:41 -07001861 if (local) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001862 mConversationListCursor.setConversationColumn(conversation.uri.toString(), ConversationColumns.READ,
Paul Westbrookbf232c32012-04-18 03:17:41 -07001863 state);
1864 } else {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001865 mConversationListCursor.markRead(mContext, state, conversation);
Paul Westbrookbf232c32012-04-18 03:17:41 -07001866 }
1867 }
1868 } else if (toFragment.equals(TAG_CONVERSATION)) {
1869 // TODO Handle setting read in conversation view
1870 }
1871 }
1872
1873 @Override
1874 public void sendConversationUriStarred(String toFragment, String conversationUri,
1875 boolean state, boolean local) {
1876 if (toFragment.equals(TAG_CONVERSATION_LIST)) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001877 if (mConversationListCursor != null) {
Paul Westbrookbf232c32012-04-18 03:17:41 -07001878 if (local) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001879 mConversationListCursor.setConversationColumn(conversationUri, ConversationColumns.STARRED, state);
Paul Westbrookbf232c32012-04-18 03:17:41 -07001880 } else {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001881 mConversationListCursor.updateBoolean(mContext, conversationUri, ConversationColumns.STARRED, state);
Paul Westbrookbf232c32012-04-18 03:17:41 -07001882 }
1883 }
1884 } else if (toFragment.equals(TAG_CONVERSATION)) {
1885 // TODO Handle setting starred in conversation view
1886 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001887 }
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001888
1889 /**
1890 * Destroy the pending {@link DestructiveAction} till now and assign the given action as the
1891 * next destructive action..
1892 * @param nextAction the next destructive action to be performed. This can be null.
1893 */
1894 private final void destroyPending(DestructiveAction nextAction) {
1895 // If there is a pending action, perform that first.
1896 if (mPendingDestruction != null) {
1897 mPendingDestruction.performAction();
1898 }
1899 mPendingDestruction = nextAction;
1900 }
1901
1902 /**
1903 * Register a destructive action with the controller. This performs the previous destructive
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001904 * action as a side effect. This method is final because we don't want the child classes to
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001905 * embellish this method any more.
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001906 * @param action
1907 */
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001908 private final void registerDestructiveAction(DestructiveAction action) {
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001909 // TODO(viki): This is not a good idea. The best solution is for clients to request a
1910 // destructive action from the controller and for the controller to own the action. This is
1911 // a half-way solution while refactoring DestructiveAction.
1912 destroyPending(action);
1913 return;
1914 }
1915
1916 /**
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001917 * Get a destructive action for selected conversations.
1918 * @param action
1919 * @return
1920 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001921 public final DestructiveAction getBatchDestruction(int action) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001922 final DestructiveAction da = new ConversationAction(action, mSelectedSet.values(), true);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001923 registerDestructiveAction(da);
1924 return da;
1925 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001926
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001927 /**
1928 * Class to change the folders that are assigned to a set of conversations. This is destructive
1929 * because the user can remove the current folder from the conversation, in which case it has
1930 * to be animated away from the current folder.
1931 */
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001932 private class FolderDestruction implements DestructiveAction {
1933 private final Collection<Conversation> mTarget = new ArrayList<Conversation>();
1934 private final ArrayList<Folder> mFolderList = new ArrayList<Folder>();
1935 private final boolean mIsDestructive;
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001936 /** Whether this destructive action has already been performed */
1937 private boolean mCompleted;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001938
1939 /**
1940 * Create a new folder destruction object to act on the given conversations.
1941 * @param target
1942 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001943 private FolderDestruction(final Collection<Conversation> target,
1944 final Collection<Folder> folders, boolean isDestructive) {
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001945 mTarget.addAll(target);
1946 mFolderList.addAll(folders);
1947 mIsDestructive = isDestructive;
1948 }
1949
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001950 @Override
1951 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001952 if (isPerformed()) {
1953 return;
1954 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001955 if (mIsDestructive) {
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001956 UndoOperation undoOp = new UndoOperation(mTarget.size(), R.id.change_folder);
1957 onUndoAvailable(undoOp);
1958 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001959 mConversationListCursor.updateString(mContext, mTarget,
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001960 ConversationColumns.FOLDER_LIST, Folder.getUriString(mFolderList));
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001961 mConversationListCursor.updateString(mContext, mTarget,
1962 ConversationColumns.RAW_FOLDERS,
1963 Folder.getSerializedFolderString(mFolder, mFolderList));
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001964 refreshConversationList();
1965 }
1966 /**
1967 * Returns true if this action has been performed, false otherwise.
1968 * @return
1969 */
1970 private synchronized boolean isPerformed() {
1971 if (mCompleted) {
1972 return true;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001973 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001974 mCompleted = true;
1975 return false;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001976 }
1977 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001978
Vikram Aggarwald503df42012-05-11 10:13:35 -07001979 private final DestructiveAction getFolderChange(Collection<Conversation> target,
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001980 Collection<Folder> folders, boolean isDestructive){
1981 final DestructiveAction da = new FolderDestruction(target, folders, isDestructive);
1982 registerDestructiveAction(da);
1983 return da;
1984 }
Vikram Aggarwal75daee52012-04-30 11:13:09 -07001985
1986 /**
1987 * Safely refresh the conversation list if it exists.
1988 */
Vikram Aggarwald503df42012-05-11 10:13:35 -07001989 protected final void refreshConversationList() {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07001990 final ConversationListFragment convList = getConversationListFragment();
1991 if (convList == null) {
1992 return;
1993 }
1994 convList.requestListRefresh();
1995 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08001996}