blob: 6be142af7c0dfda1cddc4286a9e9c11855bfb447 [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 Aggarwal27d89ad2012-06-12 13:38:40 -070039import android.os.AsyncTask;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080040import android.os.Bundle;
Mindy Pereira21ab4902012-03-19 18:48:03 -070041import android.os.Handler;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070042import android.provider.SearchRecentSuggestions;
Mindy Pereiraacf60392012-04-06 09:11:00 -070043import android.view.DragEvent;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080044import android.view.KeyEvent;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080045import android.view.LayoutInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080046import android.view.Menu;
Mindy Pereira28d5f722012-02-15 12:32:40 -080047import android.view.MenuInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080048import android.view.MenuItem;
49import android.view.MotionEvent;
Marc Blankbf128eb2012-04-18 15:58:45 -070050import android.widget.AbsListView;
51import android.widget.AbsListView.OnScrollListener;
Mindy Pereirafd0c2972012-03-27 13:50:39 -070052import android.widget.Toast;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080053
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080054import com.android.mail.ConversationListContext;
Andy Huangf9a73482012-03-13 15:54:02 -070055import com.android.mail.R;
Mindy Pereira967ede62012-03-22 09:29:09 -070056import com.android.mail.browse.ConversationCursor;
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 Pereiraa0c24442012-05-24 15:39:17 -070069import com.android.mail.providers.UIProvider.AutoAdvance;
Mindy Pereirac9d59182012-03-22 16:06:46 -070070import com.android.mail.providers.UIProvider.ConversationColumns;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070071import com.android.mail.providers.UIProvider.FolderCapabilities;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -070072import com.android.mail.ui.ActionableToastBar.ActionClickedListener;
Paul Westbrookb334c902012-06-25 11:42:46 -070073import com.android.mail.utils.LogTag;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080074import com.android.mail.utils.LogUtils;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -080075import com.android.mail.utils.Utils;
Mindy Pereiraacf60392012-04-06 09:11:00 -070076import com.google.common.collect.Lists;
Marc Blank167faa82012-03-21 13:11:53 -070077import com.google.common.collect.Sets;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080078
Mindy Pereiraef0bef62012-07-03 15:04:05 -070079import org.json.JSONException;
80
Marc Blank167faa82012-03-21 13:11:53 -070081import java.util.ArrayList;
Mindy Pereirafbe40192012-03-20 10:40:45 -070082import java.util.Collection;
Paul Westbrook23b74b92012-02-29 11:36:12 -080083import java.util.Set;
Marc Blankbf128eb2012-04-18 15:58:45 -070084import java.util.TimerTask;
Paul Westbrook23b74b92012-02-29 11:36:12 -080085
86
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080087/**
Mindy Pereira161f50d2012-02-28 15:47:19 -080088 * This is an abstract implementation of the Activity Controller. This class
89 * knows how to respond to menu items, state changes, layout changes, etc. It
90 * weaves together the views and listeners, dispatching actions to the
91 * respective underlying classes.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080092 * <p>
Mindy Pereira161f50d2012-02-28 15:47:19 -080093 * Even though this class is abstract, it should provide default implementations
94 * for most, if not all the methods in the ActivityController interface. This
95 * makes the task of the subclasses easier: OnePaneActivityController and
96 * TwoPaneActivityController can be concise when the common functionality is in
97 * AbstractActivityController.
98 * </p>
99 * <p>
100 * In the Gmail codebase, this was called BaseActivityController
101 * </p>
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800102 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700103public abstract class AbstractActivityController implements ActivityController {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800104 // Keys for serialization of various information in Bundles.
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700105 /** Tag for {@link #mAccount} */
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800106 private static final String SAVED_ACCOUNT = "saved-account";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700107 /** Tag for {@link #mFolder} */
Mindy Pereira5e478d22012-03-26 18:04:58 -0700108 private static final String SAVED_FOLDER = "saved-folder";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700109 /** Tag for {@link #mCurrentConversation} */
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700110 private static final String SAVED_CONVERSATION = "saved-conversation";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700111 /** Tag for {@link #mSelectedSet} */
112 private static final String SAVED_SELECTED_SET = "saved-selected-set";
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800113
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700114 /** Tag used when loading a wait fragment */
115 protected static final String TAG_WAIT = "wait-fragment";
116 /** Tag used when loading a conversation list fragment. */
Paul Westbrookbf232c32012-04-18 03:17:41 -0700117 public static final String TAG_CONVERSATION_LIST = "tag-conversation-list";
118 /** Tag used when loading a conversation fragment. */
119 public static final String TAG_CONVERSATION = "tag-conversation";
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700120 /** Tag used when loading a folder list fragment. */
121 protected static final String TAG_FOLDER_LIST = "tag-folder-list";
122
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800123 protected Account mAccount;
Mindy Pereira12a4d802012-06-22 09:24:43 -0700124 protected Folder mFolder;
Andy Huang6681e542012-06-14 14:36:45 -0700125 protected MailActionBarView mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800126 protected final RestrictedActivity mActivity;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800127 protected final Context mContext;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700128 private final FragmentManager mFragmentManager;
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800129 protected final RecentFolderList mRecentFolderList;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800130 protected ConversationListContext mConvListContext;
Mindy Pereira9b875682012-02-15 18:10:54 -0800131 protected Conversation mCurrentConversation;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800132
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700133 /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
134 private SuppressNotificationReceiver mNewEmailReceiver = null;
135
Mindy Pereirafbe40192012-03-20 10:40:45 -0700136 protected Handler mHandler = new Handler();
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800137 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800138 * The current mode of the application. All changes in mode are initiated by
139 * the activity controller. View mode changes are propagated to classes that
140 * attach themselves as listeners of view mode changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800141 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800142 protected final ViewMode mViewMode;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800143 protected ContentResolver mResolver;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800144 protected boolean isLoaderInitialized = false;
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800145 private AsyncRefreshTask mAsyncRefreshTask;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800146
Paul Westbrook23b74b92012-02-29 11:36:12 -0800147 private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
Mindy Pereira967ede62012-03-22 09:29:09 -0700148 protected ConversationCursor mConversationListCursor;
Andy Huang632721e2012-04-11 16:57:26 -0700149 private final DataSetObservable mConversationListObservable = new DataSetObservable() {
150 @Override
151 public void registerObserver(DataSetObserver observer) {
152 final int count = mObservers.size();
153 super.registerObserver(observer);
154 LogUtils.d(LOG_TAG, "IN AAC.registerListObserver: %s before=%d after=%d", observer,
155 count, mObservers.size());
156 }
157 @Override
158 public void unregisterObserver(DataSetObserver observer) {
159 final int count = mObservers.size();
160 super.unregisterObserver(observer);
161 LogUtils.d(LOG_TAG, "IN AAC.unregisterListObserver: %s before=%d after=%d", observer,
162 count, mObservers.size());
163 }
164 };
Marc Blankbf128eb2012-04-18 15:58:45 -0700165 protected boolean mConversationListenerAdded = false;
166
167 private boolean mIsConversationListScrolling = false;
168 private long mConversationListRefreshTime = 0;
Marc Blankbf128eb2012-04-18 15:58:45 -0700169 private RefreshTimerTask mConversationListRefreshTask;
Marc Blanke1d1b072012-04-13 17:29:16 -0700170
Vikram Aggarwalc7694222012-04-23 13:37:01 -0700171 /** Listeners that are interested in changes to current account settings. */
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700172 private final ArrayList<Settings.ChangeListener> mSettingsListeners = Lists.newArrayList();
173
Mindy Pereira967ede62012-03-22 09:29:09 -0700174 /**
175 * Selected conversations, if any.
176 */
Andy Huang4556a442012-03-30 16:42:05 -0700177 private final ConversationSelectionSet mSelectedSet = new ConversationSelectionSet();
Mindy Pereiradac00542012-03-01 10:50:33 -0800178
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700179 private final int mFolderItemUpdateDelayMs;
180
Vikram Aggarwal135fd022012-04-11 14:44:15 -0700181 /** Keeps track of selected and unselected conversations */
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700182 final protected ConversationPositionTracker mTracker = new ConversationPositionTracker();
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700183
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700184 /**
185 * Action menu associated with the selected set.
186 */
187 SelectedConversationsActionMenu mCabActionMenu;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700188 protected ActionableToastBar mToastBar;
Andy Huang632721e2012-04-11 16:57:26 -0700189 protected ConversationPagerController mPagerController;
Mindy Pereiraab486362012-03-21 18:18:53 -0700190
Andy Huangb1c34dc2012-04-17 16:36:19 -0700191 // this is split out from the general loader dispatcher because its loader doesn't return a
192 // basic Cursor
193 private final ConversationListLoaderCallbacks mListCursorCallbacks =
194 new ConversationListLoaderCallbacks();
195
Paul Westbrookb334c902012-06-25 11:42:46 -0700196 protected static final String LOG_TAG = LogTag.getLogTag();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800197 /** Constants used to differentiate between the types of loaders. */
198 private static final int LOADER_ACCOUNT_CURSOR = 0;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800199 private static final int LOADER_FOLDER_CURSOR = 2;
200 private static final int LOADER_RECENT_FOLDERS = 3;
Mindy Pereira967ede62012-03-22 09:29:09 -0700201 private static final int LOADER_CONVERSATION_LIST = 4;
Mindy Pereiraab486362012-03-21 18:18:53 -0700202 private static final int LOADER_ACCOUNT_INBOX = 5;
203 private static final int LOADER_SEARCH = 6;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -0700204 private static final int LOADER_ACCOUNT_UPDATE_CURSOR = 7;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800205
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700206 private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
207
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700208 /** The pending destructive action to be carried out before swapping the conversation cursor.*/
209 private DestructiveAction mPendingDestruction;
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700210 /** Indicates if a conversation view is visible. */
211 private boolean mIsConversationVisible;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700212 protected AsyncRefreshTask mFolderSyncTask;
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 Aggarwal025eba82012-05-08 10:45:30 -0700219 mRecentFolderList = new RecentFolderList(mContext);
Mindy Pereira967ede62012-03-22 09:29:09 -0700220 // Allow the fragment to observe changes to its own selection set. No other object is
221 // aware of the selected set.
222 mSelectedSet.addObserver(this);
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700223
224 mFolderItemUpdateDelayMs =
225 mContext.getResources().getInteger(R.integer.folder_item_refresh_delay_ms);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800226 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800227
228 @Override
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800229 public Account getCurrentAccount() {
230 return mAccount;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800231 }
232
233 @Override
234 public ConversationListContext getCurrentListContext() {
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800235 return mConvListContext;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800236 }
237
238 @Override
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800239 public String getHelpContext() {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800240 return "Mail";
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800241 }
242
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800243 @Override
Vikram Aggarwalef7c9922012-04-23 12:35:04 -0700244 public final ConversationCursor getConversationListCursor() {
Mindy Pereira967ede62012-03-22 09:29:09 -0700245 return mConversationListCursor;
246 }
247
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700248 /**
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700249 * Check if the fragment is attached to an activity and has a root view.
250 * @param in
251 * @return true if the fragment is valid, false otherwise
252 */
253 private static final boolean isValidFragment(Fragment in) {
254 if (in == null || in.getActivity() == null || in.getView() == null) {
255 return false;
256 }
257 return true;
258 }
259
260 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700261 * Get the conversation list fragment for this activity. If the conversation list fragment
262 * is not attached, this method returns null
263 * @return
264 */
265 protected ConversationListFragment getConversationListFragment() {
266 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700267 if (isValidFragment(fragment)) {
268 return (ConversationListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700269 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700270 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700271 }
272
273 /**
Vikram Aggarwald503df42012-05-11 10:13:35 -0700274 * Get the conversation view fragment for this activity. If the conversation view fragment
275 * is not attached, this method returns null
276 * @return
277 */
278 protected ConversationViewFragment getConversationViewFragment() {
279 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION);
280 if (isValidFragment(fragment)) {
281 return (ConversationViewFragment) fragment;
282 }
283 return null;
284 }
285
286 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700287 * Returns the folder list fragment attached with this activity. If no such fragment is attached
288 * this method returns null.
289 * @return
290 */
291 protected FolderListFragment getFolderListFragment() {
292 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_FOLDER_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700293 if (isValidFragment(fragment)) {
294 return (FolderListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700295 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700296 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700297 }
298
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800299 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800300 * Initialize the action bar. This is not visible to OnePaneController and
301 * TwoPaneController so they cannot override this behavior.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800302 */
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700303 private void initializeActionBar() {
304 final ActionBar actionBar = mActivity.getActionBar();
Andy Huang5895f7b2012-06-01 17:07:20 -0700305 if (actionBar == null) {
306 return;
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700307 }
Andy Huang5895f7b2012-06-01 17:07:20 -0700308
309 // be sure to inherit from the ActionBar theme when inflating
310 final LayoutInflater inflater = LayoutInflater.from(actionBar.getThemedContext());
Mindy Pereira82faec72012-06-14 17:21:50 -0700311 final boolean isSearch = mActivity.getIntent() != null
312 && Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction());
313 mActionBarView = (MailActionBarView) inflater.inflate(
314 isSearch ? R.layout.search_actionbar_view : R.layout.actionbar_view, null);
Andy Huang5895f7b2012-06-01 17:07:20 -0700315 // Why have a different variable for the same thing? We should apply
316 // the same actions
317 // on mActionBarView instead.
318 mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700319 }
320
321 /**
322 * Attach the action bar to the activity.
323 */
324 private void attachActionBar() {
325 final ActionBar actionBar = mActivity.getActionBar();
326 if (actionBar != null && mActionBarView != null) {
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800327 actionBar.setCustomView(mActionBarView, new ActionBar.LayoutParams(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800328 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
329 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
Vikram Aggarwal2a25d0c2012-02-21 16:43:10 -0800330 ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700331 mActionBarView.attach();
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800332 }
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700333 mViewMode.addListener(mActionBarView);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800334 }
335
336 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800337 * Returns whether the conversation list fragment is visible or not.
338 * Different layouts will have their own notion on the visibility of
339 * fragments, so this method needs to be overriden.
340 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800341 * @return
342 */
343 protected abstract boolean isConversationListVisible();
344
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700345 /**
346 * Switch the current account to the one provided as an argument to the method.
347 * @param account
348 */
349 private void switchAccount(Account account){
350 // Current account is different from the new account, restart loaders and show
351 // the account Inbox.
352 mAccount = account;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -0700353 LogUtils.d(LOG_TAG, "AbstractActivityController.switchAccount(): mAccount = %s",
354 mAccount.uri);
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700355 cancelRefreshTask();
356 onSettingsChanged(mAccount.settings);
357 mActionBarView.setAccount(mAccount);
358 loadAccountInbox();
359
360 mRecentFolderList.setCurrentAccount(account);
361 restartOptionalLoader(LOADER_RECENT_FOLDERS);
362 mActivity.invalidateOptionsMenu();
363 disableNotificationsOnAccountChange(mAccount);
364 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
365 MailAppProvider.getInstance().setLastViewedAccount(mAccount.uri.toString());
366 }
367
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800368 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800369 public void onAccountChanged(Account account) {
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700370 LogUtils.d(LOG_TAG, "onAccountChanged (%s) called.", account.uri);
371 final boolean accountChanged = (mAccount == null) || !account.uri.equals(mAccount.uri);
372 if (accountChanged) {
373 switchAccount(account);
374 return;
375 }
376 // Current account is the same as the new account, but the settings might be different.
377 if (!account.settings.equals(mAccount.settings)){
378 onSettingsChanged(account.settings);
379 return;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800380 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800381 }
382
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700383 /**
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700384 * Changes the settings for the current account. The new settings are provided as a parameter.
385 * @param settings
386 */
Mindy Pereiradac00542012-03-01 10:50:33 -0800387 public void onSettingsChanged(Settings settings) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700388 dispatchSettingsChange(settings);
Mindy Pereira12a676a2012-03-23 13:00:22 -0700389 resetActionBarIcon();
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700390 mActivity.invalidateOptionsMenu();
391 // If the user was viewing the default Inbox here, and the new setting contains a different
392 // default Inbox, we don't want to load a different folder here.
Mindy Pereiradac00542012-03-01 10:50:33 -0800393 }
394
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800395 @Override
396 public Settings getSettings() {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700397 return mAccount.settings;
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800398 }
399
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700400 /**
401 * Adds a listener interested in change in settings. If a class is storing a reference to
402 * Settings, it should listen on changes, so it can receive updates to settings.
403 * Must happen in the UI thread.
404 */
405 public void addSettingsListener(Settings.ChangeListener listener) {
406 mSettingsListeners.add(listener);
407 }
408
409 /**
410 * Removes a listener from receiving settings changes.
411 * Must happen in the UI thread.
412 */
413 public void removeSettingsListener(Settings.ChangeListener listener) {
414 mSettingsListeners.remove(listener);
415 }
416
417 /**
418 * Method that lets the settings listeners know when the settings got changed.
419 */
420 private void dispatchSettingsChange(Settings updatedSettings) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700421 // Copy the list of current listeners so that
422 final ArrayList<Settings.ChangeListener> allListeners =
423 new ArrayList<Settings.ChangeListener>(mSettingsListeners);
424 for (Settings.ChangeListener listener : allListeners) {
425 if (listener != null) {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700426 listener.onSettingsChanged(updatedSettings);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700427 }
428 }
429 // And we know that the ConversationListFragment is interested in changes to settings,
430 // though it hasn't registered itself with us.
431 final ConversationListFragment convList = getConversationListFragment();
432 if (convList != null) {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700433 convList.onSettingsChanged(updatedSettings);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700434 }
435 }
436
Mindy Pereirae0828392012-03-08 10:38:40 -0800437 private void fetchSearchFolder(Intent intent) {
Mindy Pereiraab486362012-03-21 18:18:53 -0700438 Bundle args = new Bundle();
439 args.putString(ConversationListContext.EXTRA_SEARCH_QUERY, intent
Mindy Pereirae0828392012-03-08 10:38:40 -0800440 .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -0700441 mActivity.getLoaderManager().restartLoader(LOADER_SEARCH, args, this);
Mindy Pereirae0828392012-03-08 10:38:40 -0800442 }
443
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800444 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800445 public void onFolderChanged(Folder folder) {
Mindy Pereira7a3471f2012-03-06 12:23:41 -0800446 if (folder != null && !folder.equals(mFolder)) {
Mindy Pereira11e35962012-06-01 14:49:46 -0700447 updateFolder(folder);
Mindy Pereira161f50d2012-02-28 15:47:19 -0800448 mConvListContext = ConversationListContext.forFolder(mContext, mAccount, mFolder);
Mindy Pereira28e0c342012-02-17 15:05:13 -0800449 showConversationList(mConvListContext);
Paul Westbrook9024b6d2012-03-19 13:57:55 -0700450
451 // Add the folder that we were viewing to the recent folders list.
452 // TODO: this may need to be fine tuned. If this is the signal that is indicating that
453 // the list is shown to the user, this could fire in one pane if the user goes directly
454 // to a conversation
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700455 updateRecentFolderList();
Marc Blankbf128eb2012-04-18 15:58:45 -0700456 cancelRefreshTask();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800457 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800458 }
459
Mindy Pereira13c12a62012-05-31 15:41:08 -0700460 @Override
Mindy Pereira505df5f2012-06-19 17:57:17 -0700461 public void onFolderSelected(Folder folder) {
Mindy Pereira13c12a62012-05-31 15:41:08 -0700462 onFolderChanged(folder);
463 }
464
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700465 /**
466 * Update the recent folders. This only needs to be done once when accessing a new folder.
467 */
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700468 private void updateRecentFolderList() {
Mindy Pereiraab486362012-03-21 18:18:53 -0700469 if (mFolder != null) {
Marc Blank2675dbc2012-04-03 10:17:13 -0700470 mRecentFolderList.touchFolder(mFolder, mAccount);
Mindy Pereiraab486362012-03-21 18:18:53 -0700471 }
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700472 }
473
Mindy Pereiraab486362012-03-21 18:18:53 -0700474 // TODO(mindyp): set this up to store a copy of the folder as a transient
475 // field in the account.
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700476 @Override
477 public void loadAccountInbox() {
Vikram Aggarwal94c94de2012-04-04 15:38:28 -0700478 restartOptionalLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700479 }
480
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800481 /** Set the current folder */
Mindy Pereira11e35962012-06-01 14:49:46 -0700482 private void updateFolder(Folder folder) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800483 // Start watching folder for sync status.
Mindy Pereirae0458e82012-03-06 11:54:55 -0800484 if (folder != null && !folder.equals(mFolder)) {
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700485 LogUtils.d(LOG_TAG, "AbstractActivityController.setFolder(%s)", folder.name);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700486 final LoaderManager lm = mActivity.getLoaderManager();
Mindy Pereira9b623802012-03-07 17:15:49 -0800487 mActionBarView.setRefreshInProgress(false);
Mindy Pereira11e35962012-06-01 14:49:46 -0700488 setFolder(folder);
Mindy Pereiraf9323cd2012-02-29 13:47:09 -0800489 mActionBarView.setFolder(mFolder);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700490
491 // Only when we switch from one folder to another do we want to restart the
492 // folder and conversation list loaders (to trigger onCreateLoader).
493 // The first time this runs when the activity is [re-]initialized, we want to re-use the
494 // previous loader's instance and data upon configuration change (e.g. rotation).
Mindy Pereira11e35962012-06-01 14:49:46 -0700495 // If there was not already an instance of the loader, init it.
496 if (lm.getLoader(LOADER_FOLDER_CURSOR) == null) {
Andy Huangb1c34dc2012-04-17 16:36:19 -0700497 lm.initLoader(LOADER_FOLDER_CURSOR, null, this);
498 lm.initLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
499 } else {
500 lm.restartLoader(LOADER_FOLDER_CURSOR, null, this);
501 lm.restartLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
502 }
Mindy Pereirae0458e82012-03-06 11:54:55 -0800503 } else if (folder == null) {
504 LogUtils.wtf(LOG_TAG, "Folder in setFolder is null");
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800505 }
506 }
507
Mindy Pereira11e35962012-06-01 14:49:46 -0700508 /**
509 * Set the folder that is used for all current operations, including what
510 * conversation list to show (if applicable), what item to select in the
511 * FolderListFragment.
512 *
513 * @param folder
514 */
515 public void setFolder(Folder folder) {
516 mFolder = folder;
517 }
518
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800519 @Override
Mindy Pereira23aadfd2012-05-25 11:24:33 -0700520 public Folder getFolder() {
521 return mFolder;
522 }
523
524 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800525 public void onActivityResult(int requestCode, int resultCode, Intent data) {
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700526 if (requestCode == ADD_ACCOUNT_REQUEST_CODE) {
527 // We were waiting for the user to create an account
528 if (resultCode == Activity.RESULT_OK) {
529 // restart the loader to get the updated list of accounts
530 mActivity.getLoaderManager().initLoader(
531 LOADER_ACCOUNT_CURSOR, null, this);
532 } else {
533 // The user failed to create an account, just exit the app
534 mActivity.finish();
535 }
536 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800537 }
538
539 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800540 public void onConversationListVisibilityChanged(boolean visible) {
Paul Westbrook9f119c72012-04-24 16:10:59 -0700541 if (mConversationListCursor != null) {
542 // The conversation list is visible.
543 Utils.setConversationCursorVisibility(mConversationListCursor, visible);
544 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800545 }
546
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800547 /**
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700548 * Called when a conversation is visible. Child classes must call the super class implementation
549 * before performing local computation.
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800550 */
551 @Override
552 public void onConversationVisibilityChanged(boolean visible) {
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700553 mIsConversationVisible = visible;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800554 return;
555 }
556
557 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800558 public boolean onCreate(Bundle savedState) {
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700559 initializeActionBar();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800560 // Allow shortcut keys to function for the ActionBar and menus.
561 mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800562 mResolver = mActivity.getContentResolver();
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700563 mNewEmailReceiver = new SuppressNotificationReceiver();
564
Mindy Pereira161f50d2012-02-28 15:47:19 -0800565 // All the individual UI components listen for ViewMode changes. This
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800566 // simplifies the amount of logic in the AbstractActivityController, but increases the
567 // possibility of timing-related bugs.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800568 mViewMode.addListener(this);
Andy Huang632721e2012-04-11 16:57:26 -0700569 mPagerController = new ConversationPagerController(mActivity, this);
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700570 mToastBar = (ActionableToastBar) mActivity.findViewById(R.id.toast_bar);
Vikram Aggarwalada51782012-04-26 14:18:31 -0700571 attachActionBar();
Andy Huang632721e2012-04-11 16:57:26 -0700572
573 final Intent intent = mActivity.getIntent();
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700574 // Immediately handle a clean launch with intent, and any state restoration
Andy Huang632721e2012-04-11 16:57:26 -0700575 // that does not rely on restored fragments or loader data
576 // any state restoration that relies on those can be done later in
577 // onRestoreInstanceState, once fragments are up and loader data is re-delivered
578 if (savedState != null) {
579 if (savedState.containsKey(SAVED_ACCOUNT)) {
580 setAccount((Account) savedState.getParcelable(SAVED_ACCOUNT));
581 mActivity.invalidateOptionsMenu();
582 }
583 if (savedState.containsKey(SAVED_FOLDER)) {
584 // Open the folder.
585 onFolderChanged((Folder) savedState.getParcelable(SAVED_FOLDER));
586 }
587 } else if (intent != null) {
588 handleIntent(intent);
589 }
Andy Huang632721e2012-04-11 16:57:26 -0700590 // Create the accounts loader; this loads the account switch spinner.
591 mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
Andy Huang632721e2012-04-11 16:57:26 -0700592 return true;
Andy Huangb1c34dc2012-04-17 16:36:19 -0700593 }
594
595 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800596 public Dialog onCreateDialog(int id, Bundle bundle) {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800597 return null;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800598 }
599
600 @Override
Vikram Aggarwal4eb52712012-06-19 16:24:50 -0700601 public final boolean onCreateOptionsMenu(Menu menu) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800602 MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800603 inflater.inflate(mActionBarView.getOptionsMenuId(), menu);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800604 mActionBarView.onCreateOptionsMenu(menu);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800605 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800606 }
607
608 @Override
Vikram Aggarwal4eb52712012-06-19 16:24:50 -0700609 public final boolean onKeyDown(int keyCode, KeyEvent event) {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800610 // TODO(viki): Auto-generated method stub
611 return false;
612 }
613
614 @Override
Vikram Aggarwal4eb52712012-06-19 16:24:50 -0700615 public final boolean onOptionsItemSelected(MenuItem item) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700616 final int id = item.getItemId();
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700617 LogUtils.d(LOG_TAG, "AbstractController.onOptionsItemSelected(%d) called.", id);
Mindy Pereira9b875682012-02-15 18:10:54 -0800618 boolean handled = true;
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700619 final Collection<Conversation> target = Conversation.listOf(mCurrentConversation);
Vikram Aggarwal112cd162012-06-18 10:51:13 -0700620 final Settings settings = (mAccount == null) ? null : mAccount.settings;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800621 switch (id) {
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700622 case R.id.archive: {
623 final boolean showDialog = (settings != null && settings.confirmArchive);
624 confirmAndDelete(target, showDialog, R.plurals.confirm_archive_conversation,
625 getAction(R.id.archive, target));
626 break;
627 }
628 case R.id.delete: {
629 final boolean showDialog = (settings != null && settings.confirmDelete);
630 confirmAndDelete(target, showDialog, R.plurals.confirm_delete_conversation,
631 getAction(R.id.delete, target));
632 break;
633 }
634 case R.id.mark_important:
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700635 updateConversation(Conversation.listOf(mCurrentConversation),
636 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.HIGH);
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700637 break;
638 case R.id.mark_not_important:
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700639 updateConversation(Conversation.listOf(mCurrentConversation),
640 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700641 break;
642 case R.id.mute:
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700643 delete(target, getAction(R.id.mute, target));
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700644 break;
645 case R.id.report_spam:
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700646 delete(target, getAction(R.id.report_spam, target));
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700647 break;
648 case R.id.inside_conversation_unread:
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700649 // TODO(viki): This is strange, and potentially incorrect. READ is an int column
650 // in the provider.
651 updateConversation(Conversation.listOf(mCurrentConversation),
652 ConversationColumns.READ, false);
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700653 mViewMode.enterConversationListMode();
654 break;
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800655 case android.R.id.home:
656 onUpPressed();
657 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800658 case R.id.compose:
659 ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
660 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800661 case R.id.show_all_folders:
662 showFolderList();
663 break;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800664 case R.id.refresh:
665 requestFolderRefresh();
666 break;
Mindy Pereira1f936682012-03-02 11:30:33 -0800667 case R.id.settings:
668 Utils.showSettings(mActivity.getActivityContext(), mAccount);
Paul Westbrook2861b6a2012-02-15 15:25:34 -0800669 break;
Paul Westbrooke5503552012-03-28 00:35:57 -0700670 case R.id.folder_options:
671 Utils.showFolderSettings(mActivity.getActivityContext(), mAccount, mFolder);
672 break;
Paul Westbrook94e440d2012-02-24 11:03:47 -0800673 case R.id.help_info_menu_item:
674 // TODO: enable context sensitive help
Paul Westbrook498e76d2012-04-12 16:33:02 -0700675 Utils.showHelp(mActivity.getActivityContext(), mAccount, null);
Paul Westbrook94e440d2012-02-24 11:03:47 -0800676 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700677 case R.id.feedback_menu_item:
Mindy Pereirafbe40192012-03-20 10:40:45 -0700678 Utils.sendFeedback(mActivity.getActivityContext(), mAccount);
679 break;
Paul Westbrook18babd22012-04-09 22:17:08 -0700680 case R.id.manage_folders_item:
681 Utils.showManageFolder(mActivity.getActivityContext(), mAccount);
682 break;
Vikram Aggarwald503df42012-05-11 10:13:35 -0700683 case R.id.change_folder:
684 new FoldersSelectionDialog(mActivity.getActivityContext(), mAccount, this,
Mindy Pereiraf3a45562012-05-24 16:30:19 -0700685 Conversation.listOf(mCurrentConversation), false).show();
Vikram Aggarwald503df42012-05-11 10:13:35 -0700686 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800687 default:
688 handled = false;
689 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800690 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800691 return handled;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800692 }
693
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700694 @Override
695 public void updateConversation(Collection <Conversation> target, String columnName,
696 boolean value) {
697 mConversationListCursor.updateBoolean(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700698 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700699 }
700
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700701 @Override
702 public void updateConversation(Collection <Conversation> target, String columnName, int value) {
703 mConversationListCursor.updateInt(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700704 refreshConversationList();
Mindy Pereirac9d59182012-03-22 16:06:46 -0700705 }
706
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700707 @Override
708 public void updateConversation(Collection <Conversation> target, String columnName,
709 String value) {
710 mConversationListCursor.updateString(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700711 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700712 }
713
Mindy Pereira28e0c342012-02-17 15:05:13 -0800714 private void requestFolderRefresh() {
715 if (mFolder != null) {
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800716 if (mAsyncRefreshTask != null) {
717 mAsyncRefreshTask.cancel(true);
718 }
Paul Westbrook7e2a2a12012-06-27 13:52:40 -0700719 mAsyncRefreshTask = new AsyncRefreshTask(mContext, mFolder.refreshUri);
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800720 mAsyncRefreshTask.execute();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800721 }
722 }
723
Mindy Pereirafbe40192012-03-20 10:40:45 -0700724 /**
725 * Confirm (based on user's settings) and delete a conversation from the conversation list and
726 * from the database.
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700727 * @param target the conversations to act upon
728 * @param showDialog true if a confirmation dialog is to be shown, false otherwise.
729 * @param confirmResource the resource ID of the string that is shown in the confirmation dialog
730 * @param action the action to perform after animating the deletion of the conversations.
Mindy Pereirafbe40192012-03-20 10:40:45 -0700731 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700732 protected void confirmAndDelete(final Collection<Conversation> target, boolean showDialog,
733 int confirmResource, final DestructiveAction action) {
Mindy Pereirafbe40192012-03-20 10:40:45 -0700734 if (showDialog) {
735 final AlertDialog.OnClickListener onClick = new AlertDialog.OnClickListener() {
736 @Override
737 public void onClick(DialogInterface dialog, int which) {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700738 delete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -0700739 }
740 };
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700741 final CharSequence message = Utils.formatPlural(mContext, confirmResource,
742 target.size());
Mindy Pereirafbe40192012-03-20 10:40:45 -0700743 new AlertDialog.Builder(mActivity.getActivityContext()).setMessage(message)
744 .setPositiveButton(R.string.ok, onClick)
745 .setNegativeButton(R.string.cancel, null)
746 .create().show();
747 } else {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700748 delete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -0700749 }
750 }
751
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700752 @Override
753 public void delete(final Collection<Conversation> target, final DestructiveAction action) {
Vikram Aggarwald503df42012-05-11 10:13:35 -0700754 // The conversation list handles deletion if it exists.
755 final ConversationListFragment convList = getConversationListFragment();
756 if (convList != null) {
Vikram Aggarwalc30fe412012-05-18 11:58:58 -0700757 LogUtils.d(LOG_TAG, "AAC.requestDelete: ListFragment is handling delete.");
Vikram Aggarwald503df42012-05-11 10:13:35 -0700758 convList.requestDelete(target, action);
759 return;
760 }
761 // Update the conversation fragment if the current conversation is deleted.
762 if (getConversationViewFragment() != null &&
763 !Conversation.contains(target, mCurrentConversation)) {
764 final Conversation next = mTracker.getNextConversation(
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700765 Settings.getAutoAdvanceSetting(mAccount.settings), target,
766 mCurrentConversation);
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700767 LogUtils.d(LOG_TAG, "requestDelete: showing %s next.", next);
768 showConversation(next);
Vikram Aggarwald503df42012-05-11 10:13:35 -0700769 }
770 // No visible UI element handled it on our behalf. Perform the action ourself.
771 action.performAction();
772 }
773
774 /**
775 * Requests that the action be performed and the UI state is updated to reflect the new change.
776 * @param target
777 * @param action
778 */
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700779 private void requestUpdate(final Collection<Conversation> target,
Vikram Aggarwald503df42012-05-11 10:13:35 -0700780 final DestructiveAction action) {
781 action.performAction();
782 refreshConversationList();
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700783 }
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700784
785 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800786 public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
787 // TODO(viki): Auto-generated method stub
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800788 }
789
790 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800791 public boolean onPrepareOptionsMenu(Menu menu) {
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800792 mActionBarView.onPrepareOptionsMenu(menu);
Mindy Pereira363451a2012-02-22 14:14:46 -0800793 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800794 }
795
Mindy Pereira68f2e222012-03-07 10:36:54 -0800796 @Override
797 public void onPause() {
798 isLoaderInitialized = false;
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700799 enableNotifications();
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700800 commitLeaveBehindItems();
Paul Westbrook94e440d2012-02-24 11:03:47 -0800801 }
802
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800803 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800804 public void onResume() {
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700805 // Register the receiver that will prevent the status receiver from
806 // displaying its notification icon as long as we're running.
807 // The SupressNotificationReceiver will block the broadcast if we're looking at the folder
808 // that the notification was received for.
809 disableNotifications();
810
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800811 if (mActionBarView != null) {
812 mActionBarView.onResume();
813 }
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700814
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800815 }
816
817 @Override
818 public void onSaveInstanceState(Bundle outState) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800819 if (mAccount != null) {
820 LogUtils.d(LOG_TAG, "Saving the account now");
821 outState.putParcelable(SAVED_ACCOUNT, mAccount);
822 }
Mindy Pereira5e478d22012-03-26 18:04:58 -0700823 if (mFolder != null) {
824 outState.putParcelable(SAVED_FOLDER, mFolder);
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800825 }
Mindy Pereira9fa43ca2012-05-17 14:18:01 -0700826 if (mCurrentConversation != null
827 && (mViewMode.getMode() == ViewMode.CONVERSATION ||
828 mViewMode.getMode() == ViewMode.SEARCH_RESULTS_CONVERSATION)) {
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700829 outState.putParcelable(SAVED_CONVERSATION, mCurrentConversation);
830 }
Andy Huang4556a442012-03-30 16:42:05 -0700831 if (!mSelectedSet.isEmpty()) {
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700832 outState.putParcelable(SAVED_SELECTED_SET, mSelectedSet);
Andy Huang4556a442012-03-30 16:42:05 -0700833 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800834 }
835
836 @Override
Mindy Pereira68f2e222012-03-07 10:36:54 -0800837 public void onSearchRequested(String query) {
838 Intent intent = new Intent();
839 intent.setAction(Intent.ACTION_SEARCH);
840 intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query);
841 intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount);
842 intent.setComponent(mActivity.getComponentName());
Vikram Aggarwalb17cbc02012-04-06 15:41:46 -0700843 mActionBarView.collapseSearch();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800844 mActivity.startActivity(intent);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800845 }
846
847 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800848 public void onStop() {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800849 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800850 }
851
Andy Huang632721e2012-04-11 16:57:26 -0700852 @Override
853 public void onDestroy() {
854 // unregister the ViewPager's observer on the conversation cursor
855 mPagerController.onDestroy();
856 }
857
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800858 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800859 * {@inheritDoc} Subclasses must override this to listen to mode changes
860 * from the ViewMode. Subclasses <b>must</b> call the parent's
861 * onViewModeChanged since the parent will handle common state changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800862 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800863 @Override
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800864 public void onViewModeChanged(int newMode) {
865 // Perform any mode specific work here.
Mindy Pereira161f50d2012-02-28 15:47:19 -0800866 // reset the action bar icon based on the mode. Why don't the individual
867 // controllers do
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800868 // this themselves?
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800869
Mindy Pereira161f50d2012-02-28 15:47:19 -0800870 // We don't want to invalidate the options menu when switching to
871 // conversation
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800872 // mode, as it will happen when the conversation finishes loading.
873 if (newMode != ViewMode.CONVERSATION) {
874 mActivity.invalidateOptionsMenu();
875 }
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800876 }
877
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700878 protected void commitLeaveBehindItems() {
879 ConversationListFragment fragment = getConversationListFragment();
880 if (fragment != null) {
881 fragment.commitLeaveBehindItems();
882 }
883 }
884
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800885 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800886 public void onWindowFocusChanged(boolean hasFocus) {
Paul Westbrook9f119c72012-04-24 16:10:59 -0700887 ConversationListFragment convList = getConversationListFragment();
888 if (hasFocus && convList != null && convList.isVisible()) {
889 // The conversation list is visible.
890 Utils.setConversationCursorVisibility(mConversationListCursor, true);
891 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800892 }
893
Mindy Pereira75181e82012-04-18 08:17:13 -0700894 private void setAccount(Account account) {
Andy Huangb9ca9792012-05-18 15:31:49 -0700895 if (account == null) {
896 LogUtils.w(LOG_TAG, new Error(),
897 "AAC ignoring null (presumably invalid) account restoration");
898 return;
899 }
Andy Huangb1148412012-05-19 00:16:30 -0700900 LogUtils.d(LOG_TAG, "AbstractActivityController.setAccount(): account = %s", account.uri);
Vikram Aggarwal91e87372012-05-18 15:36:04 -0700901 mAccount = account;
Mindy Pereira75181e82012-04-18 08:17:13 -0700902 mActionBarView.setAccount(mAccount);
Vikram Aggarwal91e87372012-05-18 15:36:04 -0700903 if (account.settings == null) {
904 LogUtils.w(LOG_TAG, new Error(), "AAC ignoring account with null settings.");
905 return;
906 }
907 dispatchSettingsChange(mAccount.settings);
Mindy Pereira75181e82012-04-18 08:17:13 -0700908 }
909
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800910 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800911 * Restore the state from the previous bundle. Subclasses should call this
912 * method from the parent class, since it performs important UI
913 * initialization.
914 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800915 * @param savedState
916 */
Andy Huang632721e2012-04-11 16:57:26 -0700917 @Override
918 public void onRestoreInstanceState(Bundle savedState) {
919 LogUtils.d(LOG_TAG, "IN AAC.onRestoreInstanceState");
920 if (savedState.containsKey(SAVED_CONVERSATION)) {
921 // Open the conversation.
Paul Westbrook534e4a22012-04-25 03:46:29 -0700922 final Conversation conversation =
923 (Conversation)savedState.getParcelable(SAVED_CONVERSATION);
924 if (conversation != null && conversation.position < 0) {
925 // Set the position to 0 on this conversation, as we don't know where it is
926 // in the list
927 conversation.position = 0;
928 }
929 setCurrentConversation(conversation);
Andy Huang632721e2012-04-11 16:57:26 -0700930 showConversation(mCurrentConversation);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800931 }
Mindy Pereira967ede62012-03-22 09:29:09 -0700932
933 /**
934 * Restore the state of selected conversations. This needs to be done after the correct mode
935 * is set and the action bar is fully initialized. If not, several key pieces of state
936 * information will be missing, and the split views may not be initialized correctly.
937 * @param savedState
938 */
Andy Huang4556a442012-03-30 16:42:05 -0700939 restoreSelectedConversations(savedState);
Andy Huang632721e2012-04-11 16:57:26 -0700940 }
941
942 private void handleIntent(Intent intent) {
943 boolean handled = false;
944 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
945 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
Andy Huang632721e2012-04-11 16:57:26 -0700946 setAccount(Account.newinstance(intent
Mindy Pereira5ad02912012-07-09 09:57:18 -0700947 .getStringExtra(Utils.EXTRA_ACCOUNT)));
Andy Huang632721e2012-04-11 16:57:26 -0700948 }
Andy Huangb9ca9792012-05-18 15:31:49 -0700949 if (mAccount == null) {
950 return;
Andy Huang632721e2012-04-11 16:57:26 -0700951 }
Andy Huangb9ca9792012-05-18 15:31:49 -0700952 mActivity.invalidateOptionsMenu();
Andy Huang632721e2012-04-11 16:57:26 -0700953
954 Folder folder = null;
955 if (intent.hasExtra(Utils.EXTRA_FOLDER)) {
956 // Open the folder.
Mindy Pereiraef0bef62012-07-03 15:04:05 -0700957 try {
958 folder = Folder
Mindy Pereira5ad02912012-07-09 09:57:18 -0700959 .fromJSONString(intent.getStringExtra(Utils.EXTRA_FOLDER));
Mindy Pereiraef0bef62012-07-03 15:04:05 -0700960 } catch (JSONException e) {
961 LogUtils.wtf(LOG_TAG, e, "Unable to parse folder extra");
962 }
Andy Huang632721e2012-04-11 16:57:26 -0700963 }
964 if (folder != null) {
965 onFolderChanged(folder);
966 handled = true;
967 }
968
969 if (intent.hasExtra(Utils.EXTRA_CONVERSATION)) {
970 // Open the conversation.
971 LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
972 intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
Paul Westbrooka9161912012-04-24 10:10:04 -0700973 final Conversation conversation =
974 (Conversation)intent.getParcelableExtra(Utils.EXTRA_CONVERSATION);
975 if (conversation != null && conversation.position < 0) {
976 // Set the position to 0 on this conversation, as we don't know where it is
977 // in the list
978 conversation.position = 0;
979 }
980 setCurrentConversation(conversation);
Andy Huang632721e2012-04-11 16:57:26 -0700981 showConversation(mCurrentConversation);
982 handled = true;
983 }
984
985 if (!handled) {
986 // Nothing was saved; just load the account inbox.
987 loadAccountInbox();
988 }
989 } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
990 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
991 // Save this search query for future suggestions.
992 final String query = intent.getStringExtra(SearchManager.QUERY);
993 final String authority = mContext.getString(R.string.suggestions_authority);
994 SearchRecentSuggestions suggestions = new SearchRecentSuggestions(
995 mContext, authority, SuggestionsProvider.MODE);
996 suggestions.saveRecentQuery(query, null);
Mindy Pereiraac254822012-06-18 10:46:43 -0700997 if (Utils.showTwoPaneSearchResults(mActivity.getActivityContext())) {
998 mViewMode.enterSearchResultsConversationMode();
999 } else {
1000 mViewMode.enterSearchResultsListMode();
1001 }
Andy Huang632721e2012-04-11 16:57:26 -07001002 setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
1003 mActivity.invalidateOptionsMenu();
1004 restartOptionalLoader(LOADER_RECENT_FOLDERS);
1005 mRecentFolderList.setCurrentAccount(mAccount);
1006 fetchSearchFolder(intent);
1007 } else {
1008 LogUtils.e(LOG_TAG, "Missing account extra from search intent. Finishing");
1009 mActivity.finish();
1010 }
1011 }
1012 if (mAccount != null) {
1013 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1014 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001015 }
1016
Andy Huang4556a442012-03-30 16:42:05 -07001017 /**
1018 * Copy any selected conversations stored in the saved bundle into our selection set,
1019 * triggering {@link ConversationSetObserver} callbacks as our selection set changes.
1020 *
1021 */
Vikram Aggarwal4eb52712012-06-19 16:24:50 -07001022 private final void restoreSelectedConversations(Bundle savedState) {
Mindy Pereira967ede62012-03-22 09:29:09 -07001023 if (savedState == null) {
Andy Huang4556a442012-03-30 16:42:05 -07001024 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001025 return;
1026 }
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001027 final ConversationSelectionSet selectedSet = savedState.getParcelable(SAVED_SELECTED_SET);
Andy Huang4556a442012-03-30 16:42:05 -07001028 if (selectedSet == null || selectedSet.isEmpty()) {
1029 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001030 return;
1031 }
Andy Huang632721e2012-04-11 16:57:26 -07001032
1033 // putAll will take care of calling our registered onSetPopulated method
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001034 mSelectedSet.putAll(selectedSet);
Mindy Pereira967ede62012-03-22 09:29:09 -07001035 }
1036
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001037 @Override
Andy Huang5895f7b2012-06-01 17:07:20 -07001038 public SubjectDisplayChanger getSubjectDisplayChanger() {
1039 return mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001040 }
1041
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001042 /**
1043 * Children can override this method, but they must call super.showConversation().
1044 * {@inheritDoc}
1045 */
1046 @Override
1047 public void showConversation(Conversation conversation) {
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001048 // Set the current conversation just in case it wasn't already set.
1049 setCurrentConversation(conversation);
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001050 }
1051
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001052 /**
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001053 * Children can override this method, but they must call super.showWaitForInitialization().
1054 * {@inheritDoc}
1055 */
1056 @Override
1057 public void showWaitForInitialization() {
1058 mViewMode.enterWaitingForInitializationMode();
1059 }
1060
1061 @Override
1062 public void hideWaitForInitialization() {
1063 }
1064
1065 @Override
1066 public void updateWaitMode() {
1067 final FragmentManager manager = mActivity.getFragmentManager();
1068 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001069 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001070 if (waitFragment != null) {
1071 waitFragment.updateAccount(mAccount);
1072 }
1073 }
1074
Vikram Aggarwal48b2a6c2012-05-29 14:09:27 -07001075 /**
1076 * Returns true if we are waiting for the account to sync, and cannot show any folders or
1077 * conversation for the current account yet.
1078 * @return
1079 */
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001080 public boolean inWaitMode() {
1081 final FragmentManager manager = mActivity.getFragmentManager();
1082 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001083 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001084 if (waitFragment != null) {
1085 final Account fragmentAccount = waitFragment.getAccount();
1086 return fragmentAccount.uri.equals(mAccount.uri) &&
1087 mViewMode.getMode() == ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION;
1088 }
1089 return false;
1090 }
1091
1092 /**
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001093 * Children can override this method, but they must call super.showConversationList().
1094 * {@inheritDoc}
1095 */
1096 @Override
1097 public void showConversationList(ConversationListContext listContext) {
1098 }
1099
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001100 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -08001101 public void onConversationSelected(Conversation conversation) {
Vikram Aggarwal5b7b3ab2012-04-03 15:43:55 -07001102 showConversation(conversation);
Mindy Pereira9fa43ca2012-05-17 14:18:01 -07001103 if (Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())) {
Mindy Pereira68f2e222012-03-07 10:36:54 -08001104 mViewMode.enterSearchResultsConversationMode();
1105 } else {
1106 mViewMode.enterConversationMode();
1107 }
Vikram Aggarwal7d602882012-02-07 15:01:20 -08001108 }
Mindy Pereira555140c2012-02-15 14:55:29 -08001109
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001110 /**
1111 * Set the current conversation. This is the conversation on which all actions are performed.
1112 * Do not modify mCurrentConversation except through this method, which makes it easy to
1113 * perform common actions associated with changing the current conversation.
1114 * @param conversation
1115 */
Andy Huang632721e2012-04-11 16:57:26 -07001116 @Override
1117 public void setCurrentConversation(Conversation conversation) {
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001118 mCurrentConversation = conversation;
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001119 mTracker.initialize(mCurrentConversation);
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001120 }
1121
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001122 /**
1123 * {@inheritDoc}
1124 */
Mindy Pereira555140c2012-02-15 14:55:29 -08001125 @Override
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001126 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
1127 // Create a loader to listen in on account changes.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001128 switch (id) {
1129 case LOADER_ACCOUNT_CURSOR:
Paul Westbrookc2074c42012-03-22 15:26:58 -07001130 return new CursorLoader(mContext, MailAppProvider.getAccountsUri(),
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001131 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
1132 case LOADER_FOLDER_CURSOR:
Paul Westbrookc7a070f2012-04-12 01:46:41 -07001133 final CursorLoader loader = new CursorLoader(mContext, mFolder.uri,
1134 UIProvider.FOLDERS_PROJECTION, null, null, null);
1135 loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
1136 return loader;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001137 case LOADER_RECENT_FOLDERS:
Paul Westbrook91d10502012-04-13 12:01:39 -07001138 if (mAccount != null && mAccount.recentFolderListUri != null) {
Paul Westbrookea4ee202012-03-12 14:12:33 -07001139 return new CursorLoader(mContext, mAccount.recentFolderListUri,
1140 UIProvider.FOLDERS_PROJECTION, null, null, null);
1141 }
1142 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001143 case LOADER_ACCOUNT_INBOX:
Vikram Aggarwal025eba82012-05-08 10:45:30 -07001144 final Uri defaultInbox = Settings.getDefaultInboxUri(mAccount.settings);
Vikram Aggarwal1e57e672012-05-07 14:48:24 -07001145 final Uri inboxUri = defaultInbox.equals(Uri.EMPTY) ?
1146 mAccount.folderListUri : defaultInbox;
Paul Westbrook7496e822012-04-24 09:50:54 -07001147 LogUtils.d(LOG_TAG, "Loading the default inbox: %s", inboxUri);
Paul Westbrook1220b6d2012-04-10 00:48:00 -07001148 if (inboxUri != null) {
1149 return new CursorLoader(mContext, inboxUri, UIProvider.FOLDERS_PROJECTION, null,
1150 null, null);
1151 }
1152 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001153 case LOADER_SEARCH:
1154 return Folder.forSearchResults(mAccount,
1155 args.getString(ConversationListContext.EXTRA_SEARCH_QUERY),
1156 mActivity.getActivityContext());
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001157 case LOADER_ACCOUNT_UPDATE_CURSOR:
1158 return new CursorLoader(mContext, mAccount.uri, UIProvider.ACCOUNTS_PROJECTION,
1159 null, null, null);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001160 default:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001161 LogUtils.wtf(LOG_TAG, "Loader returned unexpected id: %d", id);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001162 }
1163 return null;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001164 }
1165
Paul Westbrookb1f573c2012-04-06 11:38:28 -07001166 @Override
1167 public void onLoaderReset(Loader<Cursor> loader) {
1168
1169 }
1170
Andy Huangf9a73482012-03-13 15:54:02 -07001171 /**
1172 * {@link LoaderManager} currently has a bug in
1173 * {@link LoaderManager#restartLoader(int, Bundle, android.app.LoaderManager.LoaderCallbacks)}
1174 * where, if a previous onCreateLoader returned a null loader, this method will NPE. Work around
1175 * this bug by destroying any loaders that may have been created as null (essentially because
1176 * they are optional loads, and may not apply to a particular account).
1177 * <p>
1178 * A simple null check before restarting a loader will not work, because that would not
1179 * give the controller a chance to invalidate UI corresponding the prior loader result.
1180 *
1181 * @param id loader ID to safely restart
Andy Huangf9a73482012-03-13 15:54:02 -07001182 */
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001183 private void restartOptionalLoader(int id) {
Andy Huangf9a73482012-03-13 15:54:02 -07001184 final LoaderManager lm = mActivity.getLoaderManager();
1185 lm.destroyLoader(id);
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001186 lm.restartLoader(id, Bundle.EMPTY, this);
1187 }
1188
Andy Huang632721e2012-04-11 16:57:26 -07001189 @Override
1190 public void registerConversationListObserver(DataSetObserver observer) {
1191 mConversationListObservable.registerObserver(observer);
1192 }
1193
1194 @Override
1195 public void unregisterConversationListObserver(DataSetObserver observer) {
1196 mConversationListObservable.unregisterObserver(observer);
1197 }
1198
Paul Westbrook23b74b92012-02-29 11:36:12 -08001199 private boolean accountsUpdated(Cursor accountCursor) {
1200 // Check to see if the current account hasn't been set, or the account cursor is empty
1201 if (mAccount == null || !accountCursor.moveToFirst()) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001202 return true;
Paul Westbrook23b74b92012-02-29 11:36:12 -08001203 }
1204
1205 // Check to see if the number of accounts are different, from the number we saw on the last
1206 // updated
1207 if (mCurrentAccountUris.size() != accountCursor.getCount()) {
1208 return true;
1209 }
1210
1211 // Check to see if the account list is different or if the current account is not found in
1212 // the cursor.
1213 boolean foundCurrentAccount = false;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001214 do {
Paul Westbrook23b74b92012-02-29 11:36:12 -08001215 final Uri accountUri =
1216 Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
1217 if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
1218 foundCurrentAccount = true;
1219 }
1220
1221 if (!mCurrentAccountUris.contains(accountUri)) {
1222 return true;
1223 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001224 } while (accountCursor.moveToNext());
Paul Westbrook23b74b92012-02-29 11:36:12 -08001225
1226 // As long as we found the current account, the list hasn't been updated
1227 return !foundCurrentAccount;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001228 }
1229
1230 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -08001231 * Update the accounts on the device. This currently loads the first account
1232 * in the list.
1233 *
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001234 * @param loader
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001235 * @param accounts cursor into the AccountCache
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001236 * @return true if the update was successful, false otherwise
1237 */
Paul Westbrook23b74b92012-02-29 11:36:12 -08001238 private boolean updateAccounts(Loader<Cursor> loader, Cursor accounts) {
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001239 if (accounts == null || !accounts.moveToFirst()) {
1240 return false;
1241 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001242
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001243 final Account[] allAccounts = Account.getAllAccounts(accounts);
Paul Westbrook23b74b92012-02-29 11:36:12 -08001244
1245 // Save the uris for the accounts
1246 mCurrentAccountUris.clear();
1247 for (Account account : allAccounts) {
1248 mCurrentAccountUris.add(account.uri);
1249 }
1250
Andy Huang0d647352012-03-21 21:48:16 -07001251 // 1. current account is already set and is in allAccounts -> no-op
1252 // 2. current account is set and is not in allAccounts -> pick first (acct was deleted?)
1253 // 3. saved pref has an account -> pick that one
1254 // 4. otherwise just pick first
1255
1256 Account newAccount = null;
1257
1258 if (mAccount != null) {
1259 if (!mCurrentAccountUris.contains(mAccount.uri)) {
1260 newAccount = allAccounts[0];
1261 } else {
1262 newAccount = mAccount;
1263 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001264 } else {
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001265 final String lastAccountUri = MailAppProvider.getInstance().getLastViewedAccount();
Andy Huang0d647352012-03-21 21:48:16 -07001266 if (lastAccountUri != null) {
1267 for (int i = 0; i < allAccounts.length; i++) {
1268 final Account acct = allAccounts[i];
1269 if (lastAccountUri.equals(acct.uri.toString())) {
1270 newAccount = acct;
1271 break;
1272 }
1273 }
1274 }
1275 if (newAccount == null) {
1276 newAccount = allAccounts[0];
1277 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001278 }
Andy Huang0d647352012-03-21 21:48:16 -07001279
Paul Westbrook23b74b92012-02-29 11:36:12 -08001280 onAccountChanged(newAccount);
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001281 mActionBarView.setAccounts(allAccounts);
1282 return (allAccounts.length > 0);
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001283 }
1284
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001285 private void disableNotifications() {
1286 mNewEmailReceiver.activate(mContext, this);
1287 }
1288
1289 private void enableNotifications() {
1290 mNewEmailReceiver.deactivate();
1291 }
1292
1293 private void disableNotificationsOnAccountChange(Account account) {
1294 // If the new mail suppression receiver is activated for a different account, we want to
1295 // activate it for the new account.
1296 if (mNewEmailReceiver.activated() &&
1297 !mNewEmailReceiver.notificationsDisabledForAccount(account)) {
1298 // Deactivate the current receiver, otherwise multiple receivers may be registered.
1299 mNewEmailReceiver.deactivate();
1300 mNewEmailReceiver.activate(mContext, this);
1301 }
1302 }
1303
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001304 /**
1305 * {@inheritDoc}
1306 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001307 @Override
1308 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001309 // We want to reinitialize only if we haven't ever been initialized, or
1310 // if the current account has vanished.
Paul Westbrooke3e84292012-03-05 16:19:30 -08001311 if (data == null) {
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001312 LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
Paul Westbrooke3e84292012-03-05 16:19:30 -08001313 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001314 switch (loader.getId()) {
1315 case LOADER_ACCOUNT_CURSOR:
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -07001316 // If the account list is not null, and the account list cursor is empty,
Paul Westbrook2388c5d2012-03-25 12:29:11 -07001317 // we need to start the specified activity.
1318 if (data != null && data.getCount() == 0) {
1319 // If an empty cursor is returned, the MailAppProvider is indicating that
1320 // no accounts have been specified. We want to navigate to the "add account"
1321 // activity that will handle the intent returned by the MailAppProvider
1322
1323 // If the MailAppProvider believes that all accounts have been loaded, and the
1324 // account list is still empty, we want to prompt the user to add an account
1325 final Bundle extras = data.getExtras();
1326 final boolean accountsLoaded =
1327 extras.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
1328
1329 if (accountsLoaded) {
1330 final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(mContext);
1331 if (noAccountIntent != null) {
1332 mActivity.startActivityForResult(noAccountIntent,
1333 ADD_ACCOUNT_REQUEST_CODE);
1334 }
1335 }
1336 } else {
1337 final boolean accountListUpdated = accountsUpdated(data);
1338 if (!isLoaderInitialized || accountListUpdated) {
1339 isLoaderInitialized = updateAccounts(loader, data);
1340 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001341 }
1342 break;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001343 case LOADER_ACCOUNT_UPDATE_CURSOR:
1344 // We have gotten an update for current account.
1345
1346 // Make sure that this is an update for what is the current account
1347 if (data != null && data.moveToFirst()) {
1348 final Account updatedAccount = new Account(data);
1349
1350 if (updatedAccount.uri.equals(mAccount.uri)) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001351 // Update the controller's reference to the current account
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001352 mAccount = updatedAccount;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -07001353 LogUtils.d(LOG_TAG, "AbstractActivityController.onLoadFinished(): "
1354 + "mAccount = %s", mAccount.uri);
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001355 dispatchSettingsChange(mAccount.settings);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001356
1357 // Got an update for the current account
1358 final boolean inWaitingMode = inWaitMode();
1359 if (!updatedAccount.isAccountIntialized() && !inWaitingMode) {
1360 // Transition to waiting mode
1361 showWaitForInitialization();
1362 } else if (updatedAccount.isAccountIntialized() && inWaitingMode) {
1363 // Dismiss waiting mode
1364 hideWaitForInitialization();
1365 } else if (!updatedAccount.isAccountIntialized() && inWaitingMode) {
1366 // Update the WaitFragment's account object
1367 updateWaitMode();
1368 }
1369 } else {
1370 LogUtils.e(LOG_TAG, "Got update for account: %s with current account: %s",
1371 updatedAccount.uri, mAccount.uri);
1372 // We need to restart the loader, so the correct account information will
1373 // be returned
1374 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1375 }
1376 }
1377 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001378 case LOADER_FOLDER_CURSOR:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001379 // Check status of the cursor.
Marc Blankfd9d0b82012-04-23 16:01:51 -07001380 if (data != null && data.moveToFirst()) {
1381 Folder folder = new Folder(data);
1382 if (folder.isSyncInProgress()) {
1383 mActionBarView.onRefreshStarted();
1384 } else {
1385 // Stop the spinner here.
1386 mActionBarView.onRefreshStopped(folder.lastSyncResult);
1387 }
1388 mActionBarView.onFolderUpdated(folder);
1389 final ConversationListFragment convList = getConversationListFragment();
1390 if (convList != null) {
1391 convList.onFolderUpdated(folder);
1392 }
1393 LogUtils.d(LOG_TAG, "FOLDER STATUS = %d", folder.syncStatus);
Paul Westbrookc808fac2012-02-22 16:42:18 -08001394 } else {
Marc Blankfd9d0b82012-04-23 16:01:51 -07001395 LogUtils.d(LOG_TAG, "Unable to get the folder %s",
1396 mFolder != null ? mAccount.name : "");
Mindy Pereira11dd5ef2012-03-10 15:10:18 -08001397 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001398 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001399 case LOADER_RECENT_FOLDERS:
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -07001400 // No recent folders and we are running on a phone? Populate the default recents.
1401 if (data != null && data.getCount() == 0 && !Utils.useTabletUI(mContext)) {
1402 final class PopulateDefault extends AsyncTask<Uri, Void, Void> {
1403 @Override
1404 protected Void doInBackground(Uri... uri) {
1405 // Asking for an update on the URI and ignore the result.
1406 final ContentResolver resolver = mContext.getContentResolver();
1407 resolver.update(uri[0], null, null, null);
1408 return null;
1409 }
1410 }
1411 final Uri uri = mAccount.defaultRecentFolderListUri;
1412 LogUtils.v(LOG_TAG, "Default recents at %s", uri);
1413 new PopulateDefault().execute(uri);
1414 break;
1415 }
1416 LogUtils.v(LOG_TAG, "Reading recent folders from the cursor.");
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001417 mRecentFolderList.loadFromUiProvider(data);
Vikram Aggarwal37263972012-04-17 15:51:14 -07001418 mActionBarView.requestRecentFoldersAndRedraw();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001419 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001420 case LOADER_ACCOUNT_INBOX:
Marc Blankfd9d0b82012-04-23 16:01:51 -07001421 if (data != null && !data.isClosed() && data.moveToFirst()) {
Mindy Pereira0e88e9f2012-03-25 13:47:41 -07001422 Folder inbox = new Folder(data);
1423 onFolderChanged(inbox);
Mindy Pereirab4a43282012-03-23 16:20:03 -07001424 // Just want to get the inbox, don't care about updates to it
1425 // as this will be tracked by the folder change listener.
1426 mActivity.getLoaderManager().destroyLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereira5ba33802012-03-26 16:30:11 -07001427 } else {
1428 LogUtils.d(LOG_TAG, "Unable to get the account inbox for account %s",
1429 mAccount != null ? mAccount.name : "");
Mindy Pereirab4a43282012-03-23 16:20:03 -07001430 }
Mindy Pereiraab486362012-03-21 18:18:53 -07001431 break;
1432 case LOADER_SEARCH:
1433 data.moveToFirst();
1434 Folder search = new Folder(data);
Mindy Pereira11e35962012-06-01 14:49:46 -07001435 updateFolder(search);
Mindy Pereiraab486362012-03-21 18:18:53 -07001436 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder,
Mindy Pereirad660d252012-03-26 11:48:43 -07001437 mActivity.getIntent()
1438 .getStringExtra(UIProvider.SearchQueryParameters.QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -07001439 showConversationList(mConvListContext);
1440 mActivity.invalidateOptionsMenu();
Mindy Pereira3b399222012-03-28 15:19:47 -07001441 mActivity.getLoaderManager().destroyLoader(LOADER_SEARCH);
Mindy Pereiraab486362012-03-21 18:18:53 -07001442 break;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001443 }
1444 }
1445
Vikram Aggarwalc7694222012-04-23 13:37:01 -07001446 /**
1447 * Destructive actions on Conversations. This class should only be created by controllers, and
1448 * clients should only require {@link DestructiveAction}s, not specific implementations of the.
1449 * Only the controllers should know what kind of destructive actions are being created.
1450 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001451 private class ConversationAction implements DestructiveAction {
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001452 /**
1453 * The action to be performed. This is specified as the resource ID of the menu item
1454 * corresponding to this action: R.id.delete, R.id.report_spam, etc.
1455 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001456 private final int mAction;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001457 /** The action will act upon these conversations */
1458 private final Collection<Conversation> mTarget = new ArrayList<Conversation>();
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001459 /** Whether this destructive action has already been performed */
1460 private boolean mCompleted;
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001461 /** Whether this is an action on the currently selected set. */
1462 private final boolean mIsSelectedSet;
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001463
Mindy Pereirafbe40192012-03-20 10:40:45 -07001464 /**
1465 * Create a listener object. action is one of four constants: R.id.y_button (archive),
1466 * R.id.delete , R.id.mute, and R.id.report_spam.
1467 * @param action
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001468 * @param target Conversation that we want to apply the action to.
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001469 * @param isBatch whether the conversations are in the currently selected batch set.
1470 */
1471 public ConversationAction(int action, Collection<Conversation> target, boolean isBatch) {
Mindy Pereirafbe40192012-03-20 10:40:45 -07001472 mAction = action;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001473 mTarget.addAll(target);
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001474 mIsSelectedSet = isBatch;
Mindy Pereirafbe40192012-03-20 10:40:45 -07001475 }
1476
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001477 /**
1478 * The action common to child classes. This performs the action specified in the constructor
1479 * on the conversations given here.
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001480 */
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001481 @Override
1482 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001483 if (isPerformed()) {
1484 return;
1485 }
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001486 // Certain actions force a return to list.
Mindy Pereira3cb28b52012-05-24 15:26:39 -07001487 boolean undoEnabled = mAccount.supportsCapability(AccountCapabilities.UNDO);
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001488
1489 // Are we destroying the currently shown conversation? Show the next one.
1490 if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)){
1491 LogUtils.d(LOG_TAG, "ConversationAction.performAction(): mIsConversationVisible=%b"
1492 + "\nmTarget=%s\nCurrent=%s", mIsConversationVisible,
1493 Conversation.toString(mTarget), mCurrentConversation);
1494 }
1495 if (mIsConversationVisible && Conversation.contains(mTarget, mCurrentConversation)) {
Mindy Pereiraa0c24442012-05-24 15:39:17 -07001496 int advance = Settings.getAutoAdvanceSetting(mAccount.settings);
1497 final Conversation next = advance == AutoAdvance.LIST ? null : mTracker
1498 .getNextConversation(advance, mTarget, mCurrentConversation);
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001499 LogUtils.d(LOG_TAG, "Next conversation is: %s", next);
1500 showConversation(next);
1501 }
1502
Mindy Pereirafbe40192012-03-20 10:40:45 -07001503 switch (mAction) {
Mindy Pereira0692baf2012-03-23 17:34:31 -07001504 case R.id.archive:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001505 LogUtils.d(LOG_TAG, "Archiving");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001506 mConversationListCursor.archive(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001507 break;
1508 case R.id.delete:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001509 LogUtils.d(LOG_TAG, "Deleting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001510 mConversationListCursor.delete(mContext, mTarget);
Mindy Pereira695d6962012-06-18 13:02:10 -07001511 if (mFolder.supportsCapability(FolderCapabilities.DELETE_ACTION_FINAL)) {
Marc Blank386243f2012-05-25 10:40:59 -07001512 undoEnabled = false;
1513 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001514 break;
1515 case R.id.mute:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001516 LogUtils.d(LOG_TAG, "Muting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001517 if (mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE)) {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001518 for (Conversation c : mTarget) {
1519 c.localDeleteOnUpdate = true;
1520 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001521 }
1522 mConversationListCursor.mute(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001523 break;
1524 case R.id.report_spam:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001525 LogUtils.d(LOG_TAG, "Reporting spam");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001526 mConversationListCursor.reportSpam(mContext, mTarget);
1527 break;
1528 case R.id.remove_star:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001529 LogUtils.d(LOG_TAG, "Removing star");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001530 // Star removal is destructive in the Starred folder.
1531 mConversationListCursor.updateBoolean(mContext, mTarget,
1532 ConversationColumns.STARRED, false);
1533 break;
1534 case R.id.mark_not_important:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001535 LogUtils.d(LOG_TAG, "Marking not-important");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001536 // Marking not important is destructive in a mailbox containing only important
1537 // messages
1538 mConversationListCursor.updateInt(mContext, mTarget,
1539 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001540 break;
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001541 }
1542 if (undoEnabled) {
1543 onUndoAvailable(new UndoOperation(mTarget.size(), mAction));
1544 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001545 refreshConversationList();
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001546 if (mIsSelectedSet) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001547 mSelectedSet.clear();
1548 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001549 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001550
1551 /**
1552 * Returns true if this action has been performed, false otherwise.
1553 * @return
1554 */
1555 private synchronized boolean isPerformed() {
1556 if (mCompleted) {
1557 return true;
1558 }
1559 mCompleted = true;
1560 return false;
1561 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001562 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001563
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001564 /**
1565 * Get a destructive action for a menu action.
1566 * This is a temporary method, to control the profusion of {@link DestructiveAction} classes
1567 * that are created. Please do not copy this paradigm.
1568 * @param action the resource ID of the menu action: R.id.delete, for example
1569 * @param target the conversations to act upon.
1570 * @return a {@link DestructiveAction} that performs the specified action.
1571 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001572 private final DestructiveAction getAction(int action, Collection<Conversation> target) {
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001573 final DestructiveAction da = new ConversationAction(action, target, false);
1574 registerDestructiveAction(da);
1575 return da;
1576 }
1577
Vikram Aggarwald503df42012-05-11 10:13:35 -07001578 // Called from the FolderSelectionDialog after a user is done selecting folders to assign the
1579 // conversations to.
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001580 @Override
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001581 public final void assignFolder(
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001582 Collection<Folder> folders, Collection<Conversation> target, boolean batch) {
Vikram Aggarwal84db8682012-06-18 13:58:22 -07001583 // Actions are destructive only when the current folder can be assigned to (which is the
1584 // same as being able to un-assign a conversation from the folder) and when the list of
1585 // folders contains the current folder.
1586 final boolean isDestructive =
1587 mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES) &&
1588 !Folder.containerIncludes(folders, mFolder);
Vikram Aggarwald503df42012-05-11 10:13:35 -07001589 LogUtils.d(LOG_TAG, "onFolderChangesCommit: isDestructive = %b", isDestructive);
1590 if (isDestructive) {
1591 for (final Conversation c : target) {
1592 c.localDeleteOnUpdate = true;
Mindy Pereira6778f462012-03-23 18:01:55 -07001593 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001594 }
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001595 final DestructiveAction folderChange = getFolderChange(target, folders, isDestructive,
1596 batch);
Vikram Aggarwald503df42012-05-11 10:13:35 -07001597 // Update the UI elements depending no their visibility and availability
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001598 // TODO(viki): Consolidate this into a single method requestDelete.
Vikram Aggarwald503df42012-05-11 10:13:35 -07001599 if (isDestructive) {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001600 delete(target, folderChange);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001601 } else {
Vikram Aggarwald503df42012-05-11 10:13:35 -07001602 requestUpdate(target, folderChange);
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001603 }
1604 }
1605
Mindy Pereira967ede62012-03-22 09:29:09 -07001606 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001607 public final void onRefreshRequired() {
Marc Blankbf128eb2012-04-18 15:58:45 -07001608 if (mIsConversationListScrolling) {
1609 LogUtils.d(LOG_TAG, "onRefreshRequired: delay until scrolling done");
1610 return;
1611 }
1612 // Refresh the query in the background
Vikram Aggarwalb9fb2c12012-05-11 14:25:25 -07001613 final long now = System.currentTimeMillis();
1614 final long sinceLastRefresh = now - mConversationListRefreshTime;
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001615 if (mConversationListCursor.isRefreshRequired()) {
1616 mConversationListCursor.refresh();
Marc Blankbf128eb2012-04-18 15:58:45 -07001617 mTracker.updateCursor(mConversationListCursor);
1618 mConversationListRefreshTime = now;
1619 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001620 }
1621
1622 /**
1623 * Called when the {@link ConversationCursor} is changed or has new data in it.
1624 * <p>
1625 * {@inheritDoc}
1626 */
1627 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001628 public final void onRefreshReady() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001629 if (!mIsConversationListScrolling) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001630 // Swap cursors
1631 mConversationListCursor.sync();
Marc Blankbf128eb2012-04-18 15:58:45 -07001632 }
1633 mTracker.updateCursor(mConversationListCursor);
1634 }
1635
1636 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001637 public final void onDataSetChanged() {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001638 updateConversationListFragment();
Andy Huang632721e2012-04-11 16:57:26 -07001639 mConversationListObservable.notifyChanged();
Marc Blankbf128eb2012-04-18 15:58:45 -07001640 }
1641
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001642 /**
1643 * If the Conversation List Fragment is visible, updates the fragment.
1644 */
1645 private final void updateConversationListFragment() {
Marc Blankbf128eb2012-04-18 15:58:45 -07001646 final ConversationListFragment convList = getConversationListFragment();
1647 if (convList != null) {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07001648 refreshConversationList();
Paul Westbrook9f119c72012-04-24 16:10:59 -07001649 if (convList.isVisible()) {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001650 Utils.setConversationCursorVisibility(mConversationListCursor, true);
1651 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001652 }
1653 }
1654
1655 /**
1656 * This class handles throttled refresh of the conversation list
1657 */
1658 static class RefreshTimerTask extends TimerTask {
1659 final Handler mHandler;
1660 final AbstractActivityController mController;
1661
1662 RefreshTimerTask(AbstractActivityController controller, Handler handler) {
1663 mHandler = handler;
1664 mController = controller;
1665 }
1666
1667 @Override
1668 public void run() {
1669 mHandler.post(new Runnable() {
1670 @Override
1671 public void run() {
1672 LogUtils.d(LOG_TAG, "Delay done... calling onRefreshRequired");
1673 mController.onRefreshRequired();
1674 }});
1675 }
1676 }
1677
1678 /**
1679 * Cancel the refresh task, if it's running
1680 */
1681 private void cancelRefreshTask () {
1682 if (mConversationListRefreshTask != null) {
1683 mConversationListRefreshTask.cancel();
1684 mConversationListRefreshTask = null;
1685 }
1686 }
1687
1688 @Override
1689 public void onScrollStateChanged(AbsListView view, int scrollState) {
1690 boolean isScrolling = (scrollState != OnScrollListener.SCROLL_STATE_IDLE);
1691 if (!isScrolling) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001692 if (mConversationListCursor.isRefreshRequired()) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001693 LogUtils.d(LOG_TAG, "Stop scrolling: refresh");
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001694 mConversationListCursor.refresh();
1695 } else if (mConversationListCursor.isRefreshReady()) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001696 LogUtils.d(LOG_TAG, "Stop scrolling: try sync");
1697 onRefreshReady();
1698 }
1699 }
1700 mIsConversationListScrolling = isScrolling;
1701 }
1702
1703 @Override
1704 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1705 int totalItemCount) {
1706 }
1707
1708 @Override
Mindy Pereira967ede62012-03-22 09:29:09 -07001709 public void onSetEmpty() {
Mindy Pereira967ede62012-03-22 09:29:09 -07001710 }
1711
1712 @Override
1713 public void onSetPopulated(ConversationSelectionSet set) {
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001714 final ConversationListFragment convList = getConversationListFragment();
1715 if (convList == null) {
1716 return;
1717 }
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001718 mCabActionMenu = new SelectedConversationsActionMenu(mActivity, set, mAccount, mFolder,
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001719 (SwipeableListView) convList.getListView());
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001720 enableCabMode();
Mindy Pereira967ede62012-03-22 09:29:09 -07001721 }
1722
Mindy Pereira967ede62012-03-22 09:29:09 -07001723 @Override
1724 public void onSetChanged(ConversationSelectionSet set) {
1725 // Do nothing. We don't care about changes to the set.
1726 }
1727
1728 @Override
1729 public ConversationSelectionSet getSelectedSet() {
1730 return mSelectedSet;
1731 }
1732
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001733 /**
1734 * Disable the Contextual Action Bar (CAB). The selected set is not changed.
1735 */
1736 protected void disableCabMode() {
1737 if (mCabActionMenu != null) {
1738 mCabActionMenu.deactivate();
1739 }
1740 }
1741
1742 /**
1743 * Re-enable the CAB menu if required. The selection set is not changed.
1744 */
1745 protected void enableCabMode() {
1746 if (mCabActionMenu != null) {
1747 mCabActionMenu.activate();
1748 }
1749 }
1750
Vikram Aggarwal4eb52712012-06-19 16:24:50 -07001751 /**
1752 * Unselect conversations and exit CAB mode.
1753 */
1754 protected final void exitCabMode() {
1755 mSelectedSet.clear();
1756 }
1757
Mindy Pereira967ede62012-03-22 09:29:09 -07001758 @Override
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001759 public void startSearch() {
Vikram Aggarwal35f19d72012-04-24 13:24:48 -07001760 if (mAccount == null) {
1761 // We cannot search if there is no account. Drop the request to the floor.
1762 LogUtils.d(LOG_TAG, "AbstractActivityController.startSearch(): null account");
1763 return;
1764 }
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001765 if (mAccount.supportsCapability(UIProvider.AccountCapabilities.LOCAL_SEARCH)
1766 | mAccount.supportsCapability(UIProvider.AccountCapabilities.SERVER_SEARCH)) {
Vikram Aggarwal70f130e2012-04-03 12:32:14 -07001767 onSearchRequested(mActionBarView.getQuery());
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001768 } else {
1769 Toast.makeText(mActivity.getActivityContext(), mActivity.getActivityContext()
Mindy Pereiraa46c2992012-03-27 14:12:39 -07001770 .getString(R.string.search_unsupported), Toast.LENGTH_SHORT).show();
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001771 }
1772 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07001773
Vikram Aggarwal0dda5732012-04-06 11:20:16 -07001774 @Override
1775 public void exitSearchMode() {
1776 if (mViewMode.getMode() == ViewMode.SEARCH_RESULTS_LIST) {
1777 mActivity.finish();
1778 }
1779 }
1780
Mindy Pereiraacf60392012-04-06 09:11:00 -07001781 /**
1782 * Supports dragging conversations to a folder.
1783 */
1784 @Override
1785 public boolean supportsDrag(DragEvent event, Folder folder) {
1786 return (folder != null
1787 && event != null
1788 && event.getClipDescription() != null
1789 && folder.supportsCapability
1790 (UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
1791 && folder.supportsCapability
1792 (UIProvider.FolderCapabilities.CAN_HOLD_MAIL)
1793 && !mFolder.uri.equals(folder.uri));
1794 }
1795
1796 /**
1797 * Handles dropping conversations to a label.
1798 */
1799 @Override
1800 public void handleDrop(DragEvent event, final Folder folder) {
Mindy Pereiraacf60392012-04-06 09:11:00 -07001801 if (!supportsDrag(event, folder)) {
1802 return;
1803 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07001804 final Collection<Conversation> conversations = mSelectedSet.values();
Vikram Aggarwal440fe792012-05-10 17:23:15 -07001805 final Collection<Folder> dropTarget = Folder.listOf(folder);
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001806 // Drag and drop is destructive: we remove conversations from the current folder.
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001807 final DestructiveAction action = getFolderChange(conversations, dropTarget, true, true);
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001808 delete(conversations, action);
Mindy Pereiraacf60392012-04-06 09:11:00 -07001809 }
Mindy Pereira0963ef82012-04-10 11:43:01 -07001810
1811 @Override
Mindy Pereira0963ef82012-04-10 11:43:01 -07001812 public void onTouchEvent(MotionEvent event) {
1813 if (event.getAction() == MotionEvent.ACTION_DOWN) {
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07001814 if (mToastBar != null && !mToastBar.isEventInToastBar(event)) {
1815 mToastBar.hide(true);
Mindy Pereira0963ef82012-04-10 11:43:01 -07001816 }
1817 }
1818 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001819
Andy Huang632721e2012-04-11 16:57:26 -07001820 @Override
1821 public void onConversationSeen(Conversation conv) {
1822 mPagerController.onConversationSeen(conv);
1823 }
1824
Andy Huangb1c34dc2012-04-17 16:36:19 -07001825 private class ConversationListLoaderCallbacks implements
1826 LoaderManager.LoaderCallbacks<ConversationCursor> {
1827
1828 @Override
1829 public Loader<ConversationCursor> onCreateLoader(int id, Bundle args) {
1830 Loader<ConversationCursor> result = new ConversationCursorLoader((Activity) mActivity,
Paul Westbrookbf232c32012-04-18 03:17:41 -07001831 mAccount, mFolder.conversationListUri, mFolder.name);
Andy Huangb1c34dc2012-04-17 16:36:19 -07001832 return result;
1833 }
1834
1835 @Override
1836 public void onLoadFinished(Loader<ConversationCursor> loader, ConversationCursor data) {
Andy Huang632721e2012-04-11 16:57:26 -07001837 LogUtils.d(LOG_TAG, "IN AAC.ConversationCursor.onLoadFinished, data=%s loader=%s",
1838 data, loader);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001839 // Clear our all pending destructive actions before swapping the conversation cursor
1840 destroyPending(null);
Andy Huangb1c34dc2012-04-17 16:36:19 -07001841 mConversationListCursor = data;
Paul Westbrookbf232c32012-04-18 03:17:41 -07001842 mConversationListCursor.addListener(AbstractActivityController.this);
Andy Huangb1c34dc2012-04-17 16:36:19 -07001843
Andy Huange3df1ad2012-04-24 17:15:23 -07001844 mConversationListObservable.notifyChanged();
1845
Andy Huangb1c34dc2012-04-17 16:36:19 -07001846 // Register the AbstractActivityController as a listener to changes in
1847 // data in the cursor.
1848 final ConversationListFragment convList = getConversationListFragment();
1849 if (convList != null) {
1850 convList.onCursorUpdated();
Paul Westbrookbf232c32012-04-18 03:17:41 -07001851 convList.getListView().setOnScrollListener(AbstractActivityController.this);
Paul Westbrook9f119c72012-04-24 16:10:59 -07001852
1853 if (convList.isVisible()) {
1854 // The conversation list is visible.
1855 Utils.setConversationCursorVisibility(mConversationListCursor, true);
1856 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001857 }
1858 // Shown for search results in two-pane mode only.
1859 if (shouldShowFirstConversation()) {
1860 if (mConversationListCursor.getCount() > 0) {
1861 mConversationListCursor.moveToPosition(0);
1862 if (convList != null) {
1863 convList.getListView().setItemChecked(0, true);
1864 }
1865 final Conversation conv = new Conversation(mConversationListCursor);
1866 conv.position = 0;
1867 onConversationSelected(conv);
1868 }
1869 }
1870 }
1871
1872 @Override
1873 public void onLoaderReset(Loader<ConversationCursor> loader) {
1874 final ConversationListFragment convList = getConversationListFragment();
1875 if (convList == null) {
1876 return;
1877 }
1878 convList.onCursorUpdated();
1879 }
Paul Westbrookbf232c32012-04-18 03:17:41 -07001880 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001881
Paul Westbrookbf232c32012-04-18 03:17:41 -07001882 @Override
1883 public void sendConversationRead(String toFragment, Conversation conversation, boolean state,
1884 boolean local) {
1885 if (toFragment.equals(TAG_CONVERSATION_LIST)) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001886 if (mConversationListCursor != null) {
Paul Westbrookbf232c32012-04-18 03:17:41 -07001887 if (local) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001888 mConversationListCursor.setConversationColumn(conversation.uri.toString(), ConversationColumns.READ,
Paul Westbrookbf232c32012-04-18 03:17:41 -07001889 state);
1890 } else {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001891 mConversationListCursor.markRead(mContext, state, conversation);
Paul Westbrookbf232c32012-04-18 03:17:41 -07001892 }
1893 }
1894 } else if (toFragment.equals(TAG_CONVERSATION)) {
1895 // TODO Handle setting read in conversation view
1896 }
1897 }
1898
1899 @Override
1900 public void sendConversationUriStarred(String toFragment, String conversationUri,
1901 boolean state, boolean local) {
1902 if (toFragment.equals(TAG_CONVERSATION_LIST)) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001903 if (mConversationListCursor != null) {
Paul Westbrookbf232c32012-04-18 03:17:41 -07001904 if (local) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001905 mConversationListCursor.setConversationColumn(conversationUri, ConversationColumns.STARRED, state);
Paul Westbrookbf232c32012-04-18 03:17:41 -07001906 } else {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001907 mConversationListCursor.updateBoolean(mContext, conversationUri, ConversationColumns.STARRED, state);
Paul Westbrookbf232c32012-04-18 03:17:41 -07001908 }
1909 }
1910 } else if (toFragment.equals(TAG_CONVERSATION)) {
1911 // TODO Handle setting starred in conversation view
1912 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001913 }
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001914
1915 /**
1916 * Destroy the pending {@link DestructiveAction} till now and assign the given action as the
1917 * next destructive action..
1918 * @param nextAction the next destructive action to be performed. This can be null.
1919 */
1920 private final void destroyPending(DestructiveAction nextAction) {
1921 // If there is a pending action, perform that first.
1922 if (mPendingDestruction != null) {
1923 mPendingDestruction.performAction();
1924 }
1925 mPendingDestruction = nextAction;
1926 }
1927
1928 /**
1929 * Register a destructive action with the controller. This performs the previous destructive
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001930 * 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 -07001931 * embellish this method any more.
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001932 * @param action
1933 */
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001934 private final void registerDestructiveAction(DestructiveAction action) {
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001935 // TODO(viki): This is not a good idea. The best solution is for clients to request a
1936 // destructive action from the controller and for the controller to own the action. This is
1937 // a half-way solution while refactoring DestructiveAction.
1938 destroyPending(action);
1939 return;
1940 }
1941
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001942 @Override
1943 public final DestructiveAction getBatchAction(int action) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001944 final DestructiveAction da = new ConversationAction(action, mSelectedSet.values(), true);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001945 registerDestructiveAction(da);
1946 return da;
1947 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001948
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001949 /**
1950 * Class to change the folders that are assigned to a set of conversations. This is destructive
1951 * because the user can remove the current folder from the conversation, in which case it has
1952 * to be animated away from the current folder.
1953 */
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001954 private class FolderDestruction implements DestructiveAction {
1955 private final Collection<Conversation> mTarget = new ArrayList<Conversation>();
1956 private final ArrayList<Folder> mFolderList = new ArrayList<Folder>();
1957 private final boolean mIsDestructive;
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001958 /** Whether this destructive action has already been performed */
1959 private boolean mCompleted;
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001960 private boolean mIsSelectedSet;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001961
1962 /**
1963 * Create a new folder destruction object to act on the given conversations.
1964 * @param target
1965 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001966 private FolderDestruction(final Collection<Conversation> target,
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001967 final Collection<Folder> folders, boolean isDestructive, boolean isBatch) {
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001968 mTarget.addAll(target);
1969 mFolderList.addAll(folders);
1970 mIsDestructive = isDestructive;
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001971 mIsSelectedSet = isBatch;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001972 }
1973
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001974 @Override
1975 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001976 if (isPerformed()) {
1977 return;
1978 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001979 if (mIsDestructive) {
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001980 UndoOperation undoOp = new UndoOperation(mTarget.size(), R.id.change_folder);
1981 onUndoAvailable(undoOp);
1982 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001983 mConversationListCursor.updateString(mContext, mTarget,
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001984 ConversationColumns.FOLDER_LIST, Folder.getUriString(mFolderList));
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001985 mConversationListCursor.updateString(mContext, mTarget,
1986 ConversationColumns.RAW_FOLDERS,
1987 Folder.getSerializedFolderString(mFolder, mFolderList));
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001988 refreshConversationList();
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001989 if (mIsSelectedSet) {
1990 mSelectedSet.clear();
1991 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001992 }
1993 /**
1994 * Returns true if this action has been performed, false otherwise.
1995 * @return
1996 */
1997 private synchronized boolean isPerformed() {
1998 if (mCompleted) {
1999 return true;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002000 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002001 mCompleted = true;
2002 return false;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002003 }
2004 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002005
Vikram Aggarwald503df42012-05-11 10:13:35 -07002006 private final DestructiveAction getFolderChange(Collection<Conversation> target,
Mindy Pereiraf3a45562012-05-24 16:30:19 -07002007 Collection<Folder> folders, boolean isDestructive, boolean isBatch){
2008 final DestructiveAction da = new FolderDestruction(target, folders, isDestructive, isBatch);
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002009 registerDestructiveAction(da);
2010 return da;
2011 }
Vikram Aggarwal75daee52012-04-30 11:13:09 -07002012
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07002013 @Override
2014 public final void refreshConversationList() {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07002015 final ConversationListFragment convList = getConversationListFragment();
2016 if (convList == null) {
2017 return;
2018 }
2019 convList.requestListRefresh();
2020 }
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002021
2022 protected final ActionClickedListener getUndoClickedListener(
2023 final AnimatedAdapter listAdapter) {
2024 return new ActionClickedListener() {
2025 @Override
2026 public void onActionClicked() {
2027 if (mAccount.undoUri != null) {
2028 // NOTE: We might want undo to return the messages affected, in which case
2029 // the resulting cursor might be interesting...
2030 // TODO: Use UIProvider.SEQUENCE_QUERY_PARAMETER to indicate the set of
2031 // commands to undo
2032 if (mConversationListCursor != null) {
2033 mConversationListCursor.undo(
2034 mActivity.getActivityContext(), mAccount.undoUri);
2035 }
2036 if (listAdapter != null) {
2037 listAdapter.setUndo(true);
2038 }
2039 }
2040 }
2041 };
2042 }
2043
2044 protected final void showErrorToast(final Folder folder) {
2045 mToastBar.setConversationMode(false);
2046 mToastBar.show(
2047 getRetryClickedListener(folder),
2048 R.drawable.ic_email_network_error,
2049 Utils.getSyncStatusText(mActivity.getActivityContext(),
2050 folder.lastSyncResult),
2051 false, /* showActionIcon */
2052 R.string.retry,
2053 false); /* replaceVisibleToast */
2054 }
2055
2056 private final ActionClickedListener getRetryClickedListener(final Folder folder) {
2057 return new ActionClickedListener() {
2058 @Override
2059 public void onActionClicked() {
2060 final Uri uri = folder.refreshUri;
2061
2062 if (uri != null) {
2063 if (mFolderSyncTask != null) {
2064 mFolderSyncTask.cancel(true);
2065 }
2066 mFolderSyncTask = new AsyncRefreshTask(mActivity.getActivityContext(), uri);
2067 mFolderSyncTask.execute();
2068 }
2069 }
2070 };
2071 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08002072}