blob: 0f3f4b75dbe44fce0eecd2ce4192f3a6e379e1df [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;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080072import com.android.mail.utils.LogUtils;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -080073import com.android.mail.utils.Utils;
Mindy Pereiraacf60392012-04-06 09:11:00 -070074import com.google.common.collect.Lists;
Marc Blank167faa82012-03-21 13:11:53 -070075import com.google.common.collect.Sets;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080076
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -070077import java.net.URI;
Marc Blank167faa82012-03-21 13:11:53 -070078import java.util.ArrayList;
Mindy Pereirafbe40192012-03-20 10:40:45 -070079import java.util.Collection;
Paul Westbrook23b74b92012-02-29 11:36:12 -080080import java.util.Set;
Marc Blankbf128eb2012-04-18 15:58:45 -070081import java.util.TimerTask;
Paul Westbrook23b74b92012-02-29 11:36:12 -080082
83
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080084/**
Mindy Pereira161f50d2012-02-28 15:47:19 -080085 * This is an abstract implementation of the Activity Controller. This class
86 * knows how to respond to menu items, state changes, layout changes, etc. It
87 * weaves together the views and listeners, dispatching actions to the
88 * respective underlying classes.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080089 * <p>
Mindy Pereira161f50d2012-02-28 15:47:19 -080090 * Even though this class is abstract, it should provide default implementations
91 * for most, if not all the methods in the ActivityController interface. This
92 * makes the task of the subclasses easier: OnePaneActivityController and
93 * TwoPaneActivityController can be concise when the common functionality is in
94 * AbstractActivityController.
95 * </p>
96 * <p>
97 * In the Gmail codebase, this was called BaseActivityController
98 * </p>
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080099 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700100public abstract class AbstractActivityController implements ActivityController {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800101 // Keys for serialization of various information in Bundles.
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700102 /** Tag for {@link #mAccount} */
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800103 private static final String SAVED_ACCOUNT = "saved-account";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700104 /** Tag for {@link #mFolder} */
Mindy Pereira5e478d22012-03-26 18:04:58 -0700105 private static final String SAVED_FOLDER = "saved-folder";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700106 /** Tag for {@link #mCurrentConversation} */
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700107 private static final String SAVED_CONVERSATION = "saved-conversation";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700108 /** Tag for {@link #mSelectedSet} */
109 private static final String SAVED_SELECTED_SET = "saved-selected-set";
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800110
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700111 /** Tag used when loading a wait fragment */
112 protected static final String TAG_WAIT = "wait-fragment";
113 /** Tag used when loading a conversation list fragment. */
Paul Westbrookbf232c32012-04-18 03:17:41 -0700114 public static final String TAG_CONVERSATION_LIST = "tag-conversation-list";
115 /** Tag used when loading a conversation fragment. */
116 public static final String TAG_CONVERSATION = "tag-conversation";
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700117 /** Tag used when loading a folder list fragment. */
118 protected static final String TAG_FOLDER_LIST = "tag-folder-list";
119
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800120 protected Account mAccount;
Mindy Pereira11e35962012-06-01 14:49:46 -0700121 private Folder mFolder;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800122 protected ActionBarView mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800123 protected final RestrictedActivity mActivity;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800124 protected final Context mContext;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700125 private final FragmentManager mFragmentManager;
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800126 protected final RecentFolderList mRecentFolderList;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800127 protected ConversationListContext mConvListContext;
Mindy Pereira9b875682012-02-15 18:10:54 -0800128 protected Conversation mCurrentConversation;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800129
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700130 /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
131 private SuppressNotificationReceiver mNewEmailReceiver = null;
132
Mindy Pereirafbe40192012-03-20 10:40:45 -0700133 protected Handler mHandler = new Handler();
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800134 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800135 * The current mode of the application. All changes in mode are initiated by
136 * the activity controller. View mode changes are propagated to classes that
137 * attach themselves as listeners of view mode changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800138 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800139 protected final ViewMode mViewMode;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800140 protected ContentResolver mResolver;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800141 protected boolean isLoaderInitialized = false;
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800142 private AsyncRefreshTask mAsyncRefreshTask;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800143
Paul Westbrook23b74b92012-02-29 11:36:12 -0800144 private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
Mindy Pereira967ede62012-03-22 09:29:09 -0700145 protected ConversationCursor mConversationListCursor;
Andy Huang632721e2012-04-11 16:57:26 -0700146 private final DataSetObservable mConversationListObservable = new DataSetObservable() {
147 @Override
148 public void registerObserver(DataSetObserver observer) {
149 final int count = mObservers.size();
150 super.registerObserver(observer);
151 LogUtils.d(LOG_TAG, "IN AAC.registerListObserver: %s before=%d after=%d", observer,
152 count, mObservers.size());
153 }
154 @Override
155 public void unregisterObserver(DataSetObserver observer) {
156 final int count = mObservers.size();
157 super.unregisterObserver(observer);
158 LogUtils.d(LOG_TAG, "IN AAC.unregisterListObserver: %s before=%d after=%d", observer,
159 count, mObservers.size());
160 }
161 };
Marc Blankbf128eb2012-04-18 15:58:45 -0700162 protected boolean mConversationListenerAdded = false;
163
164 private boolean mIsConversationListScrolling = false;
165 private long mConversationListRefreshTime = 0;
Marc Blankbf128eb2012-04-18 15:58:45 -0700166 private RefreshTimerTask mConversationListRefreshTask;
Marc Blanke1d1b072012-04-13 17:29:16 -0700167
Vikram Aggarwalc7694222012-04-23 13:37:01 -0700168 /** Listeners that are interested in changes to current account settings. */
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700169 private final ArrayList<Settings.ChangeListener> mSettingsListeners = Lists.newArrayList();
170
Mindy Pereira967ede62012-03-22 09:29:09 -0700171 /**
172 * Selected conversations, if any.
173 */
Andy Huang4556a442012-03-30 16:42:05 -0700174 private final ConversationSelectionSet mSelectedSet = new ConversationSelectionSet();
Mindy Pereiradac00542012-03-01 10:50:33 -0800175
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700176 private final int mFolderItemUpdateDelayMs;
177
Vikram Aggarwal135fd022012-04-11 14:44:15 -0700178 /** Keeps track of selected and unselected conversations */
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700179 final protected ConversationPositionTracker mTracker = new ConversationPositionTracker();
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700180
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700181 /**
182 * Action menu associated with the selected set.
183 */
184 SelectedConversationsActionMenu mCabActionMenu;
Mindy Pereira0963ef82012-04-10 11:43:01 -0700185 protected UndoBarView mUndoBarView;
Andy Huang632721e2012-04-11 16:57:26 -0700186 protected ConversationPagerController mPagerController;
Mindy Pereiraab486362012-03-21 18:18:53 -0700187
Andy Huangb1c34dc2012-04-17 16:36:19 -0700188 // this is split out from the general loader dispatcher because its loader doesn't return a
189 // basic Cursor
190 private final ConversationListLoaderCallbacks mListCursorCallbacks =
191 new ConversationListLoaderCallbacks();
192
Vikram Aggarwal04ff99c2012-02-28 15:29:13 -0800193 protected static final String LOG_TAG = new LogUtils().getLogTag();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800194 /** Constants used to differentiate between the types of loaders. */
195 private static final int LOADER_ACCOUNT_CURSOR = 0;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800196 private static final int LOADER_FOLDER_CURSOR = 2;
197 private static final int LOADER_RECENT_FOLDERS = 3;
Mindy Pereira967ede62012-03-22 09:29:09 -0700198 private static final int LOADER_CONVERSATION_LIST = 4;
Mindy Pereiraab486362012-03-21 18:18:53 -0700199 private static final int LOADER_ACCOUNT_INBOX = 5;
200 private static final int LOADER_SEARCH = 6;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -0700201 private static final int LOADER_ACCOUNT_UPDATE_CURSOR = 7;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800202
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700203 private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
204
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700205 /** The pending destructive action to be carried out before swapping the conversation cursor.*/
206 private DestructiveAction mPendingDestruction;
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700207 /** Indicates if a conversation view is visible. */
208 private boolean mIsConversationVisible;
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700209
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800210 public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
211 mActivity = activity;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700212 mFragmentManager = mActivity.getFragmentManager();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800213 mViewMode = viewMode;
214 mContext = activity.getApplicationContext();
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700215 mRecentFolderList = new RecentFolderList(mContext);
Mindy Pereira967ede62012-03-22 09:29:09 -0700216 // Allow the fragment to observe changes to its own selection set. No other object is
217 // aware of the selected set.
218 mSelectedSet.addObserver(this);
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700219
220 mFolderItemUpdateDelayMs =
221 mContext.getResources().getInteger(R.integer.folder_item_refresh_delay_ms);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800222 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800223
224 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800225 public void clearSubject() {
226 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800227 }
228
229 @Override
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800230 public Account getCurrentAccount() {
231 return mAccount;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800232 }
233
234 @Override
235 public ConversationListContext getCurrentListContext() {
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800236 return mConvListContext;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800237 }
238
239 @Override
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800240 public String getHelpContext() {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800241 return "Mail";
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800242 }
243
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800244 @Override
245 public String getUnshownSubject(String subject) {
246 // Calculate how much of the subject is shown, and return the remaining.
247 return null;
248 }
249
250 @Override
Vikram Aggarwalef7c9922012-04-23 12:35:04 -0700251 public final ConversationCursor getConversationListCursor() {
Mindy Pereira967ede62012-03-22 09:29:09 -0700252 return mConversationListCursor;
253 }
254
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700255 /**
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700256 * Check if the fragment is attached to an activity and has a root view.
257 * @param in
258 * @return true if the fragment is valid, false otherwise
259 */
260 private static final boolean isValidFragment(Fragment in) {
261 if (in == null || in.getActivity() == null || in.getView() == null) {
262 return false;
263 }
264 return true;
265 }
266
267 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700268 * Get the conversation list fragment for this activity. If the conversation list fragment
269 * is not attached, this method returns null
270 * @return
271 */
272 protected ConversationListFragment getConversationListFragment() {
273 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700274 if (isValidFragment(fragment)) {
275 return (ConversationListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700276 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700277 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700278 }
279
280 /**
Vikram Aggarwald503df42012-05-11 10:13:35 -0700281 * Get the conversation view fragment for this activity. If the conversation view fragment
282 * is not attached, this method returns null
283 * @return
284 */
285 protected ConversationViewFragment getConversationViewFragment() {
286 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION);
287 if (isValidFragment(fragment)) {
288 return (ConversationViewFragment) fragment;
289 }
290 return null;
291 }
292
293 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700294 * Returns the folder list fragment attached with this activity. If no such fragment is attached
295 * this method returns null.
296 * @return
297 */
298 protected FolderListFragment getFolderListFragment() {
299 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_FOLDER_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700300 if (isValidFragment(fragment)) {
301 return (FolderListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700302 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700303 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700304 }
305
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800306 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800307 * Initialize the action bar. This is not visible to OnePaneController and
308 * TwoPaneController so they cannot override this behavior.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800309 */
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700310 private void initializeActionBar() {
311 final ActionBar actionBar = mActivity.getActionBar();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800312 mActionBarView = (ActionBarView) LayoutInflater.from(mContext).inflate(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800313 R.layout.actionbar_view, null);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800314 if (actionBar != null && mActionBarView != null) {
Mindy Pereira161f50d2012-02-28 15:47:19 -0800315 // Why have a different variable for the same thing? We should apply
316 // the same actions
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800317 // on mActionBarView instead.
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800318 mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700319 }
320 }
321
322 /**
323 * Attach the action bar to the activity.
324 */
325 private void attachActionBar() {
326 final ActionBar actionBar = mActivity.getActionBar();
327 if (actionBar != null && mActionBarView != null) {
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800328 actionBar.setCustomView(mActionBarView, new ActionBar.LayoutParams(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800329 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
330 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
Vikram Aggarwal2a25d0c2012-02-21 16:43:10 -0800331 ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700332 mActionBarView.attach();
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800333 }
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700334 mViewMode.addListener(mActionBarView);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800335 }
336
337 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800338 * Returns whether the conversation list fragment is visible or not.
339 * Different layouts will have their own notion on the visibility of
340 * fragments, so this method needs to be overriden.
341 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800342 * @return
343 */
344 protected abstract boolean isConversationListVisible();
345
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700346 /**
347 * Switch the current account to the one provided as an argument to the method.
348 * @param account
349 */
350 private void switchAccount(Account account){
351 // Current account is different from the new account, restart loaders and show
352 // the account Inbox.
353 mAccount = account;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -0700354 LogUtils.d(LOG_TAG, "AbstractActivityController.switchAccount(): mAccount = %s",
355 mAccount.uri);
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700356 cancelRefreshTask();
357 onSettingsChanged(mAccount.settings);
358 mActionBarView.setAccount(mAccount);
359 loadAccountInbox();
360
361 mRecentFolderList.setCurrentAccount(account);
362 restartOptionalLoader(LOADER_RECENT_FOLDERS);
363 mActivity.invalidateOptionsMenu();
364 disableNotificationsOnAccountChange(mAccount);
365 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
366 MailAppProvider.getInstance().setLastViewedAccount(mAccount.uri.toString());
367 }
368
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800369 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800370 public void onAccountChanged(Account account) {
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700371 LogUtils.d(LOG_TAG, "onAccountChanged (%s) called.", account.uri);
372 final boolean accountChanged = (mAccount == null) || !account.uri.equals(mAccount.uri);
373 if (accountChanged) {
374 switchAccount(account);
375 return;
376 }
377 // Current account is the same as the new account, but the settings might be different.
378 if (!account.settings.equals(mAccount.settings)){
379 onSettingsChanged(account.settings);
380 return;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800381 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800382 }
383
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700384 /**
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700385 * Changes the settings for the current account. The new settings are provided as a parameter.
386 * @param settings
387 */
Mindy Pereiradac00542012-03-01 10:50:33 -0800388 public void onSettingsChanged(Settings settings) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700389 dispatchSettingsChange(settings);
Mindy Pereira12a676a2012-03-23 13:00:22 -0700390 resetActionBarIcon();
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700391 mActivity.invalidateOptionsMenu();
392 // If the user was viewing the default Inbox here, and the new setting contains a different
393 // default Inbox, we don't want to load a different folder here.
Mindy Pereiradac00542012-03-01 10:50:33 -0800394 }
395
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800396 @Override
397 public Settings getSettings() {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700398 return mAccount.settings;
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800399 }
400
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700401 /**
402 * Adds a listener interested in change in settings. If a class is storing a reference to
403 * Settings, it should listen on changes, so it can receive updates to settings.
404 * Must happen in the UI thread.
405 */
406 public void addSettingsListener(Settings.ChangeListener listener) {
407 mSettingsListeners.add(listener);
408 }
409
410 /**
411 * Removes a listener from receiving settings changes.
412 * Must happen in the UI thread.
413 */
414 public void removeSettingsListener(Settings.ChangeListener listener) {
415 mSettingsListeners.remove(listener);
416 }
417
418 /**
419 * Method that lets the settings listeners know when the settings got changed.
420 */
421 private void dispatchSettingsChange(Settings updatedSettings) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700422 // Copy the list of current listeners so that
423 final ArrayList<Settings.ChangeListener> allListeners =
424 new ArrayList<Settings.ChangeListener>(mSettingsListeners);
425 for (Settings.ChangeListener listener : allListeners) {
426 if (listener != null) {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700427 listener.onSettingsChanged(updatedSettings);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700428 }
429 }
430 // And we know that the ConversationListFragment is interested in changes to settings,
431 // though it hasn't registered itself with us.
432 final ConversationListFragment convList = getConversationListFragment();
433 if (convList != null) {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700434 convList.onSettingsChanged(updatedSettings);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700435 }
436 }
437
Mindy Pereirae0828392012-03-08 10:38:40 -0800438 private void fetchSearchFolder(Intent intent) {
Mindy Pereiraab486362012-03-21 18:18:53 -0700439 Bundle args = new Bundle();
440 args.putString(ConversationListContext.EXTRA_SEARCH_QUERY, intent
Mindy Pereirae0828392012-03-08 10:38:40 -0800441 .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -0700442 mActivity.getLoaderManager().restartLoader(LOADER_SEARCH, args, this);
Mindy Pereirae0828392012-03-08 10:38:40 -0800443 }
444
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800445 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800446 public void onFolderChanged(Folder folder) {
Mindy Pereira7a3471f2012-03-06 12:23:41 -0800447 if (folder != null && !folder.equals(mFolder)) {
Mindy Pereira11e35962012-06-01 14:49:46 -0700448 updateFolder(folder);
Mindy Pereira161f50d2012-02-28 15:47:19 -0800449 mConvListContext = ConversationListContext.forFolder(mContext, mAccount, mFolder);
Mindy Pereira28e0c342012-02-17 15:05:13 -0800450 showConversationList(mConvListContext);
Paul Westbrook9024b6d2012-03-19 13:57:55 -0700451
452 // Add the folder that we were viewing to the recent folders list.
453 // TODO: this may need to be fine tuned. If this is the signal that is indicating that
454 // the list is shown to the user, this could fire in one pane if the user goes directly
455 // to a conversation
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700456 updateRecentFolderList();
Marc Blankbf128eb2012-04-18 15:58:45 -0700457 cancelRefreshTask();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800458 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800459 }
460
Mindy Pereira13c12a62012-05-31 15:41:08 -0700461 @Override
462 public void onFolderSelected(Folder folder, boolean childView) {
463 onFolderChanged(folder);
464 }
465
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700466 /**
467 * Update the recent folders. This only needs to be done once when accessing a new folder.
468 */
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700469 private void updateRecentFolderList() {
Mindy Pereiraab486362012-03-21 18:18:53 -0700470 if (mFolder != null) {
Marc Blank2675dbc2012-04-03 10:17:13 -0700471 mRecentFolderList.touchFolder(mFolder, mAccount);
Mindy Pereiraab486362012-03-21 18:18:53 -0700472 }
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700473 }
474
Mindy Pereiraab486362012-03-21 18:18:53 -0700475 // TODO(mindyp): set this up to store a copy of the folder as a transient
476 // field in the account.
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700477 @Override
478 public void loadAccountInbox() {
Vikram Aggarwal94c94de2012-04-04 15:38:28 -0700479 restartOptionalLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700480 }
481
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800482 /** Set the current folder */
Mindy Pereira11e35962012-06-01 14:49:46 -0700483 private void updateFolder(Folder folder) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800484 // Start watching folder for sync status.
Mindy Pereirae0458e82012-03-06 11:54:55 -0800485 if (folder != null && !folder.equals(mFolder)) {
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700486 LogUtils.d(LOG_TAG, "AbstractActivityController.setFolder(%s)", folder.name);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700487 final LoaderManager lm = mActivity.getLoaderManager();
Mindy Pereira9b623802012-03-07 17:15:49 -0800488 mActionBarView.setRefreshInProgress(false);
Mindy Pereira11e35962012-06-01 14:49:46 -0700489 setFolder(folder);
Mindy Pereiraf9323cd2012-02-29 13:47:09 -0800490 mActionBarView.setFolder(mFolder);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700491
492 // Only when we switch from one folder to another do we want to restart the
493 // folder and conversation list loaders (to trigger onCreateLoader).
494 // The first time this runs when the activity is [re-]initialized, we want to re-use the
495 // previous loader's instance and data upon configuration change (e.g. rotation).
Mindy Pereira11e35962012-06-01 14:49:46 -0700496 // If there was not already an instance of the loader, init it.
497 if (lm.getLoader(LOADER_FOLDER_CURSOR) == null) {
Andy Huangb1c34dc2012-04-17 16:36:19 -0700498 lm.initLoader(LOADER_FOLDER_CURSOR, null, this);
499 lm.initLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
500 } else {
501 lm.restartLoader(LOADER_FOLDER_CURSOR, null, this);
502 lm.restartLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
503 }
Mindy Pereirae0458e82012-03-06 11:54:55 -0800504 } else if (folder == null) {
505 LogUtils.wtf(LOG_TAG, "Folder in setFolder is null");
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800506 }
507 }
508
Mindy Pereira11e35962012-06-01 14:49:46 -0700509 /**
510 * Set the folder that is used for all current operations, including what
511 * conversation list to show (if applicable), what item to select in the
512 * FolderListFragment.
513 *
514 * @param folder
515 */
516 public void setFolder(Folder folder) {
517 mFolder = folder;
518 }
519
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800520 @Override
Mindy Pereira23aadfd2012-05-25 11:24:33 -0700521 public Folder getFolder() {
522 return mFolder;
523 }
524
525 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800526 public void onActivityResult(int requestCode, int resultCode, Intent data) {
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700527 if (requestCode == ADD_ACCOUNT_REQUEST_CODE) {
528 // We were waiting for the user to create an account
529 if (resultCode == Activity.RESULT_OK) {
530 // restart the loader to get the updated list of accounts
531 mActivity.getLoaderManager().initLoader(
532 LOADER_ACCOUNT_CURSOR, null, this);
533 } else {
534 // The user failed to create an account, just exit the app
535 mActivity.finish();
536 }
537 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800538 }
539
540 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800541 public void onConversationListVisibilityChanged(boolean visible) {
Paul Westbrook9f119c72012-04-24 16:10:59 -0700542 if (mConversationListCursor != null) {
543 // The conversation list is visible.
544 Utils.setConversationCursorVisibility(mConversationListCursor, visible);
545 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800546 }
547
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800548 /**
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700549 * Called when a conversation is visible. Child classes must call the super class implementation
550 * before performing local computation.
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800551 */
552 @Override
553 public void onConversationVisibilityChanged(boolean visible) {
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700554 mIsConversationVisible = visible;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800555 return;
556 }
557
558 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800559 public boolean onCreate(Bundle savedState) {
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700560 initializeActionBar();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800561 // Allow shortcut keys to function for the ActionBar and menus.
562 mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800563 mResolver = mActivity.getContentResolver();
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700564 mNewEmailReceiver = new SuppressNotificationReceiver();
565
Mindy Pereira161f50d2012-02-28 15:47:19 -0800566 // All the individual UI components listen for ViewMode changes. This
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800567 // simplifies the amount of logic in the AbstractActivityController, but increases the
568 // possibility of timing-related bugs.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800569 mViewMode.addListener(this);
Andy Huang632721e2012-04-11 16:57:26 -0700570 mPagerController = new ConversationPagerController(mActivity, this);
Andy Huang632721e2012-04-11 16:57:26 -0700571 mUndoBarView = (UndoBarView) mActivity.findViewById(R.id.undo_view);
Vikram Aggarwalada51782012-04-26 14:18:31 -0700572 attachActionBar();
Andy Huang632721e2012-04-11 16:57:26 -0700573
574 final Intent intent = mActivity.getIntent();
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700575 // Immediately handle a clean launch with intent, and any state restoration
Andy Huang632721e2012-04-11 16:57:26 -0700576 // that does not rely on restored fragments or loader data
577 // any state restoration that relies on those can be done later in
578 // onRestoreInstanceState, once fragments are up and loader data is re-delivered
579 if (savedState != null) {
580 if (savedState.containsKey(SAVED_ACCOUNT)) {
581 setAccount((Account) savedState.getParcelable(SAVED_ACCOUNT));
582 mActivity.invalidateOptionsMenu();
583 }
584 if (savedState.containsKey(SAVED_FOLDER)) {
585 // Open the folder.
586 onFolderChanged((Folder) savedState.getParcelable(SAVED_FOLDER));
587 }
588 } else if (intent != null) {
589 handleIntent(intent);
590 }
Andy Huang632721e2012-04-11 16:57:26 -0700591 // Create the accounts loader; this loads the account switch spinner.
592 mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
Andy Huang632721e2012-04-11 16:57:26 -0700593 return true;
Andy Huangb1c34dc2012-04-17 16:36:19 -0700594 }
595
596 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800597 public Dialog onCreateDialog(int id, Bundle bundle) {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800598 return null;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800599 }
600
601 @Override
602 public boolean onCreateOptionsMenu(Menu menu) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800603 MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800604 inflater.inflate(mActionBarView.getOptionsMenuId(), menu);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800605 mActionBarView.onCreateOptionsMenu(menu);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800606 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800607 }
608
609 @Override
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800610 public boolean onKeyDown(int keyCode, KeyEvent event) {
611 // TODO(viki): Auto-generated method stub
612 return false;
613 }
614
615 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800616 public boolean onOptionsItemSelected(MenuItem item) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700617 final int id = item.getItemId();
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700618 LogUtils.d(LOG_TAG, "AbstractController.onOptionsItemSelected(%d) called.", id);
Mindy Pereira9b875682012-02-15 18:10:54 -0800619 boolean handled = true;
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700620 final Collection<Conversation> target = Conversation.listOf(mCurrentConversation);
621 final Settings settings = mAccount.settings;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800622 switch (id) {
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700623 case R.id.archive: {
624 final boolean showDialog = (settings != null && settings.confirmArchive);
625 confirmAndDelete(target, showDialog, R.plurals.confirm_archive_conversation,
626 getAction(R.id.archive, target));
627 break;
628 }
629 case R.id.delete: {
630 final boolean showDialog = (settings != null && settings.confirmDelete);
631 confirmAndDelete(target, showDialog, R.plurals.confirm_delete_conversation,
632 getAction(R.id.delete, target));
633 break;
634 }
635 case R.id.mark_important:
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700636 updateConversation(Conversation.listOf(mCurrentConversation),
637 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.HIGH);
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700638 break;
639 case R.id.mark_not_important:
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700640 updateConversation(Conversation.listOf(mCurrentConversation),
641 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700642 break;
643 case R.id.mute:
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700644 delete(target, getAction(R.id.mute, target));
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700645 break;
646 case R.id.report_spam:
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700647 delete(target, getAction(R.id.report_spam, target));
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700648 break;
649 case R.id.inside_conversation_unread:
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700650 // TODO(viki): This is strange, and potentially incorrect. READ is an int column
651 // in the provider.
652 updateConversation(Conversation.listOf(mCurrentConversation),
653 ConversationColumns.READ, false);
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700654 mViewMode.enterConversationListMode();
655 break;
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800656 case android.R.id.home:
657 onUpPressed();
658 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800659 case R.id.compose:
660 ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
661 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800662 case R.id.show_all_folders:
663 showFolderList();
664 break;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800665 case R.id.refresh:
666 requestFolderRefresh();
667 break;
Mindy Pereira1f936682012-03-02 11:30:33 -0800668 case R.id.settings:
669 Utils.showSettings(mActivity.getActivityContext(), mAccount);
Paul Westbrook2861b6a2012-02-15 15:25:34 -0800670 break;
Paul Westbrooke5503552012-03-28 00:35:57 -0700671 case R.id.folder_options:
672 Utils.showFolderSettings(mActivity.getActivityContext(), mAccount, mFolder);
673 break;
Paul Westbrook94e440d2012-02-24 11:03:47 -0800674 case R.id.help_info_menu_item:
675 // TODO: enable context sensitive help
Paul Westbrook498e76d2012-04-12 16:33:02 -0700676 Utils.showHelp(mActivity.getActivityContext(), mAccount, null);
Paul Westbrook94e440d2012-02-24 11:03:47 -0800677 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700678 case R.id.feedback_menu_item:
Mindy Pereirafbe40192012-03-20 10:40:45 -0700679 Utils.sendFeedback(mActivity.getActivityContext(), mAccount);
680 break;
Paul Westbrook18babd22012-04-09 22:17:08 -0700681 case R.id.manage_folders_item:
682 Utils.showManageFolder(mActivity.getActivityContext(), mAccount);
683 break;
Vikram Aggarwald503df42012-05-11 10:13:35 -0700684 case R.id.change_folder:
685 new FoldersSelectionDialog(mActivity.getActivityContext(), mAccount, this,
Mindy Pereiraf3a45562012-05-24 16:30:19 -0700686 Conversation.listOf(mCurrentConversation), false).show();
Vikram Aggarwald503df42012-05-11 10:13:35 -0700687 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800688 default:
689 handled = false;
690 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800691 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800692 return handled;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800693 }
694
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700695 @Override
696 public void updateConversation(Collection <Conversation> target, String columnName,
697 boolean value) {
698 mConversationListCursor.updateBoolean(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700699 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700700 }
701
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700702 @Override
703 public void updateConversation(Collection <Conversation> target, String columnName, int value) {
704 mConversationListCursor.updateInt(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700705 refreshConversationList();
Mindy Pereirac9d59182012-03-22 16:06:46 -0700706 }
707
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700708 @Override
709 public void updateConversation(Collection <Conversation> target, String columnName,
710 String value) {
711 mConversationListCursor.updateString(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700712 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700713 }
714
Mindy Pereira28e0c342012-02-17 15:05:13 -0800715 private void requestFolderRefresh() {
716 if (mFolder != null) {
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800717 if (mAsyncRefreshTask != null) {
718 mAsyncRefreshTask.cancel(true);
719 }
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800720 mAsyncRefreshTask = new AsyncRefreshTask(mContext, mFolder);
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800721 mAsyncRefreshTask.execute();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800722 }
723 }
724
Mindy Pereirafbe40192012-03-20 10:40:45 -0700725 /**
726 * Confirm (based on user's settings) and delete a conversation from the conversation list and
727 * from the database.
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700728 * @param target the conversations to act upon
729 * @param showDialog true if a confirmation dialog is to be shown, false otherwise.
730 * @param confirmResource the resource ID of the string that is shown in the confirmation dialog
731 * @param action the action to perform after animating the deletion of the conversations.
Mindy Pereirafbe40192012-03-20 10:40:45 -0700732 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700733 protected void confirmAndDelete(final Collection<Conversation> target, boolean showDialog,
734 int confirmResource, final DestructiveAction action) {
Mindy Pereirafbe40192012-03-20 10:40:45 -0700735 if (showDialog) {
736 final AlertDialog.OnClickListener onClick = new AlertDialog.OnClickListener() {
737 @Override
738 public void onClick(DialogInterface dialog, int which) {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700739 delete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -0700740 }
741 };
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700742 final CharSequence message = Utils.formatPlural(mContext, confirmResource,
743 target.size());
Mindy Pereirafbe40192012-03-20 10:40:45 -0700744 new AlertDialog.Builder(mActivity.getActivityContext()).setMessage(message)
745 .setPositiveButton(R.string.ok, onClick)
746 .setNegativeButton(R.string.cancel, null)
747 .create().show();
748 } else {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700749 delete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -0700750 }
751 }
752
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700753 @Override
754 public void delete(final Collection<Conversation> target, final DestructiveAction action) {
Vikram Aggarwald503df42012-05-11 10:13:35 -0700755 // The conversation list handles deletion if it exists.
756 final ConversationListFragment convList = getConversationListFragment();
757 if (convList != null) {
Vikram Aggarwalc30fe412012-05-18 11:58:58 -0700758 LogUtils.d(LOG_TAG, "AAC.requestDelete: ListFragment is handling delete.");
Vikram Aggarwald503df42012-05-11 10:13:35 -0700759 convList.requestDelete(target, action);
760 return;
761 }
762 // Update the conversation fragment if the current conversation is deleted.
763 if (getConversationViewFragment() != null &&
764 !Conversation.contains(target, mCurrentConversation)) {
765 final Conversation next = mTracker.getNextConversation(
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700766 Settings.getAutoAdvanceSetting(mAccount.settings), target,
767 mCurrentConversation);
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700768 LogUtils.d(LOG_TAG, "requestDelete: showing %s next.", next);
769 showConversation(next);
Vikram Aggarwald503df42012-05-11 10:13:35 -0700770 }
771 // No visible UI element handled it on our behalf. Perform the action ourself.
772 action.performAction();
773 }
774
775 /**
776 * Requests that the action be performed and the UI state is updated to reflect the new change.
777 * @param target
778 * @param action
779 */
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700780 private void requestUpdate(final Collection<Conversation> target,
Vikram Aggarwald503df42012-05-11 10:13:35 -0700781 final DestructiveAction action) {
782 action.performAction();
783 refreshConversationList();
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700784 }
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700785
786 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800787 public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
788 // TODO(viki): Auto-generated method stub
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800789 }
790
791 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800792 public boolean onPrepareOptionsMenu(Menu menu) {
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800793 mActionBarView.onPrepareOptionsMenu(menu);
Mindy Pereira363451a2012-02-22 14:14:46 -0800794 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800795 }
796
Mindy Pereira68f2e222012-03-07 10:36:54 -0800797 @Override
798 public void onPause() {
799 isLoaderInitialized = false;
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700800 enableNotifications();
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700801 commitLeaveBehindItems();
Paul Westbrook94e440d2012-02-24 11:03:47 -0800802 }
803
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800804 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800805 public void onResume() {
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700806 // Register the receiver that will prevent the status receiver from
807 // displaying its notification icon as long as we're running.
808 // The SupressNotificationReceiver will block the broadcast if we're looking at the folder
809 // that the notification was received for.
810 disableNotifications();
811
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800812 if (mActionBarView != null) {
813 mActionBarView.onResume();
814 }
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700815
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800816 }
817
818 @Override
819 public void onSaveInstanceState(Bundle outState) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800820 if (mAccount != null) {
821 LogUtils.d(LOG_TAG, "Saving the account now");
822 outState.putParcelable(SAVED_ACCOUNT, mAccount);
823 }
Mindy Pereira5e478d22012-03-26 18:04:58 -0700824 if (mFolder != null) {
825 outState.putParcelable(SAVED_FOLDER, mFolder);
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800826 }
Mindy Pereira9fa43ca2012-05-17 14:18:01 -0700827 if (mCurrentConversation != null
828 && (mViewMode.getMode() == ViewMode.CONVERSATION ||
829 mViewMode.getMode() == ViewMode.SEARCH_RESULTS_CONVERSATION)) {
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700830 outState.putParcelable(SAVED_CONVERSATION, mCurrentConversation);
831 }
Andy Huang4556a442012-03-30 16:42:05 -0700832 if (!mSelectedSet.isEmpty()) {
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700833 outState.putParcelable(SAVED_SELECTED_SET, mSelectedSet);
Andy Huang4556a442012-03-30 16:42:05 -0700834 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800835 }
836
837 @Override
Mindy Pereira68f2e222012-03-07 10:36:54 -0800838 public void onSearchRequested(String query) {
839 Intent intent = new Intent();
840 intent.setAction(Intent.ACTION_SEARCH);
841 intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query);
842 intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount);
843 intent.setComponent(mActivity.getComponentName());
Vikram Aggarwalb17cbc02012-04-06 15:41:46 -0700844 mActionBarView.collapseSearch();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800845 mActivity.startActivity(intent);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800846 }
847
848 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800849 public void onStop() {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800850 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800851 }
852
Andy Huang632721e2012-04-11 16:57:26 -0700853 @Override
854 public void onDestroy() {
855 // unregister the ViewPager's observer on the conversation cursor
856 mPagerController.onDestroy();
857 }
858
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800859 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800860 * {@inheritDoc} Subclasses must override this to listen to mode changes
861 * from the ViewMode. Subclasses <b>must</b> call the parent's
862 * onViewModeChanged since the parent will handle common state changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800863 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800864 @Override
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800865 public void onViewModeChanged(int newMode) {
866 // Perform any mode specific work here.
Mindy Pereira161f50d2012-02-28 15:47:19 -0800867 // reset the action bar icon based on the mode. Why don't the individual
868 // controllers do
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800869 // this themselves?
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800870
Mindy Pereira161f50d2012-02-28 15:47:19 -0800871 // We don't want to invalidate the options menu when switching to
872 // conversation
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800873 // mode, as it will happen when the conversation finishes loading.
874 if (newMode != ViewMode.CONVERSATION) {
875 mActivity.invalidateOptionsMenu();
876 }
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800877 }
878
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700879 protected void commitLeaveBehindItems() {
880 ConversationListFragment fragment = getConversationListFragment();
881 if (fragment != null) {
882 fragment.commitLeaveBehindItems();
883 }
884 }
885
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800886 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800887 public void onWindowFocusChanged(boolean hasFocus) {
Paul Westbrook9f119c72012-04-24 16:10:59 -0700888 ConversationListFragment convList = getConversationListFragment();
889 if (hasFocus && convList != null && convList.isVisible()) {
890 // The conversation list is visible.
891 Utils.setConversationCursorVisibility(mConversationListCursor, true);
892 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800893 }
894
Mindy Pereira75181e82012-04-18 08:17:13 -0700895 private void setAccount(Account account) {
Andy Huangb9ca9792012-05-18 15:31:49 -0700896 if (account == null) {
897 LogUtils.w(LOG_TAG, new Error(),
898 "AAC ignoring null (presumably invalid) account restoration");
899 return;
900 }
Andy Huangb1148412012-05-19 00:16:30 -0700901 LogUtils.d(LOG_TAG, "AbstractActivityController.setAccount(): account = %s", account.uri);
Vikram Aggarwal91e87372012-05-18 15:36:04 -0700902 mAccount = account;
Mindy Pereira75181e82012-04-18 08:17:13 -0700903 mActionBarView.setAccount(mAccount);
Vikram Aggarwal91e87372012-05-18 15:36:04 -0700904 if (account.settings == null) {
905 LogUtils.w(LOG_TAG, new Error(), "AAC ignoring account with null settings.");
906 return;
907 }
908 dispatchSettingsChange(mAccount.settings);
Mindy Pereira75181e82012-04-18 08:17:13 -0700909 }
910
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800911 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800912 * Restore the state from the previous bundle. Subclasses should call this
913 * method from the parent class, since it performs important UI
914 * initialization.
915 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800916 * @param savedState
917 */
Andy Huang632721e2012-04-11 16:57:26 -0700918 @Override
919 public void onRestoreInstanceState(Bundle savedState) {
920 LogUtils.d(LOG_TAG, "IN AAC.onRestoreInstanceState");
921 if (savedState.containsKey(SAVED_CONVERSATION)) {
922 // Open the conversation.
Paul Westbrook534e4a22012-04-25 03:46:29 -0700923 final Conversation conversation =
924 (Conversation)savedState.getParcelable(SAVED_CONVERSATION);
925 if (conversation != null && conversation.position < 0) {
926 // Set the position to 0 on this conversation, as we don't know where it is
927 // in the list
928 conversation.position = 0;
929 }
930 setCurrentConversation(conversation);
Andy Huang632721e2012-04-11 16:57:26 -0700931 showConversation(mCurrentConversation);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800932 }
Mindy Pereira967ede62012-03-22 09:29:09 -0700933
934 /**
935 * Restore the state of selected conversations. This needs to be done after the correct mode
936 * is set and the action bar is fully initialized. If not, several key pieces of state
937 * information will be missing, and the split views may not be initialized correctly.
938 * @param savedState
939 */
Andy Huang4556a442012-03-30 16:42:05 -0700940 restoreSelectedConversations(savedState);
Andy Huang632721e2012-04-11 16:57:26 -0700941 }
942
943 private void handleIntent(Intent intent) {
944 boolean handled = false;
945 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
946 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
947 setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
948 } else if (intent.hasExtra(Utils.EXTRA_ACCOUNT_STRING)) {
949 setAccount(Account.newinstance(intent
950 .getStringExtra(Utils.EXTRA_ACCOUNT_STRING)));
951 }
Andy Huangb9ca9792012-05-18 15:31:49 -0700952 if (mAccount == null) {
953 return;
Andy Huang632721e2012-04-11 16:57:26 -0700954 }
Andy Huangb9ca9792012-05-18 15:31:49 -0700955 mActivity.invalidateOptionsMenu();
Andy Huang632721e2012-04-11 16:57:26 -0700956
957 Folder folder = null;
958 if (intent.hasExtra(Utils.EXTRA_FOLDER)) {
959 // Open the folder.
960 LogUtils.d(LOG_TAG, "SHOW THE FOLDER at %s",
961 intent.getParcelableExtra(Utils.EXTRA_FOLDER));
962 folder = (Folder) intent.getParcelableExtra(Utils.EXTRA_FOLDER);
963
964 } else if (intent.hasExtra(Utils.EXTRA_FOLDER_STRING)) {
965 // Open the folder.
966 folder = new Folder(intent.getStringExtra(Utils.EXTRA_FOLDER_STRING));
967 }
968 if (folder != null) {
969 onFolderChanged(folder);
970 handled = true;
971 }
972
973 if (intent.hasExtra(Utils.EXTRA_CONVERSATION)) {
974 // Open the conversation.
975 LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
976 intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
Paul Westbrooka9161912012-04-24 10:10:04 -0700977 final Conversation conversation =
978 (Conversation)intent.getParcelableExtra(Utils.EXTRA_CONVERSATION);
979 if (conversation != null && conversation.position < 0) {
980 // Set the position to 0 on this conversation, as we don't know where it is
981 // in the list
982 conversation.position = 0;
983 }
984 setCurrentConversation(conversation);
Andy Huang632721e2012-04-11 16:57:26 -0700985 showConversation(mCurrentConversation);
986 handled = true;
987 }
988
989 if (!handled) {
990 // Nothing was saved; just load the account inbox.
991 loadAccountInbox();
992 }
993 } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
994 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
995 // Save this search query for future suggestions.
996 final String query = intent.getStringExtra(SearchManager.QUERY);
997 final String authority = mContext.getString(R.string.suggestions_authority);
998 SearchRecentSuggestions suggestions = new SearchRecentSuggestions(
999 mContext, authority, SuggestionsProvider.MODE);
1000 suggestions.saveRecentQuery(query, null);
1001
1002 mViewMode.enterSearchResultsListMode();
1003 setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
1004 mActivity.invalidateOptionsMenu();
1005 restartOptionalLoader(LOADER_RECENT_FOLDERS);
1006 mRecentFolderList.setCurrentAccount(mAccount);
1007 fetchSearchFolder(intent);
1008 } else {
1009 LogUtils.e(LOG_TAG, "Missing account extra from search intent. Finishing");
1010 mActivity.finish();
1011 }
1012 }
1013 if (mAccount != null) {
1014 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1015 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001016 }
1017
Andy Huang4556a442012-03-30 16:42:05 -07001018 /**
1019 * Copy any selected conversations stored in the saved bundle into our selection set,
1020 * triggering {@link ConversationSetObserver} callbacks as our selection set changes.
1021 *
1022 */
Mindy Pereira967ede62012-03-22 09:29:09 -07001023 private void restoreSelectedConversations(Bundle savedState) {
1024 if (savedState == null) {
Andy Huang4556a442012-03-30 16:42:05 -07001025 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001026 return;
1027 }
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001028 final ConversationSelectionSet selectedSet = savedState.getParcelable(SAVED_SELECTED_SET);
Andy Huang4556a442012-03-30 16:42:05 -07001029 if (selectedSet == null || selectedSet.isEmpty()) {
1030 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001031 return;
1032 }
Andy Huang632721e2012-04-11 16:57:26 -07001033
1034 // putAll will take care of calling our registered onSetPopulated method
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001035 mSelectedSet.putAll(selectedSet);
Mindy Pereira967ede62012-03-22 09:29:09 -07001036 }
1037
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001038 @Override
1039 public void setSubject(String subject) {
1040 // Do something useful with the subject. This requires changing the
1041 // conversation view's subject text.
1042 }
1043
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001044 /**
1045 * Children can override this method, but they must call super.showConversation().
1046 * {@inheritDoc}
1047 */
1048 @Override
1049 public void showConversation(Conversation conversation) {
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001050 // Set the current conversation just in case it wasn't already set.
1051 setCurrentConversation(conversation);
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001052 }
1053
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001054 /**
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001055 * Children can override this method, but they must call super.showWaitForInitialization().
1056 * {@inheritDoc}
1057 */
1058 @Override
1059 public void showWaitForInitialization() {
1060 mViewMode.enterWaitingForInitializationMode();
1061 }
1062
1063 @Override
1064 public void hideWaitForInitialization() {
1065 }
1066
1067 @Override
1068 public void updateWaitMode() {
1069 final FragmentManager manager = mActivity.getFragmentManager();
1070 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001071 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001072 if (waitFragment != null) {
1073 waitFragment.updateAccount(mAccount);
1074 }
1075 }
1076
Vikram Aggarwal48b2a6c2012-05-29 14:09:27 -07001077 /**
1078 * Returns true if we are waiting for the account to sync, and cannot show any folders or
1079 * conversation for the current account yet.
1080 * @return
1081 */
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001082 public boolean inWaitMode() {
1083 final FragmentManager manager = mActivity.getFragmentManager();
1084 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001085 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001086 if (waitFragment != null) {
1087 final Account fragmentAccount = waitFragment.getAccount();
1088 return fragmentAccount.uri.equals(mAccount.uri) &&
1089 mViewMode.getMode() == ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION;
1090 }
1091 return false;
1092 }
1093
1094 /**
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001095 * Children can override this method, but they must call super.showConversationList().
1096 * {@inheritDoc}
1097 */
1098 @Override
1099 public void showConversationList(ConversationListContext listContext) {
1100 }
1101
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001102 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -08001103 public void onConversationSelected(Conversation conversation) {
Vikram Aggarwal5b7b3ab2012-04-03 15:43:55 -07001104 showConversation(conversation);
Mindy Pereira9fa43ca2012-05-17 14:18:01 -07001105 if (Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())) {
Mindy Pereira68f2e222012-03-07 10:36:54 -08001106 mViewMode.enterSearchResultsConversationMode();
1107 } else {
1108 mViewMode.enterConversationMode();
1109 }
Vikram Aggarwal7d602882012-02-07 15:01:20 -08001110 }
Mindy Pereira555140c2012-02-15 14:55:29 -08001111
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001112 /**
1113 * Set the current conversation. This is the conversation on which all actions are performed.
1114 * Do not modify mCurrentConversation except through this method, which makes it easy to
1115 * perform common actions associated with changing the current conversation.
1116 * @param conversation
1117 */
Andy Huang632721e2012-04-11 16:57:26 -07001118 @Override
1119 public void setCurrentConversation(Conversation conversation) {
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001120 mCurrentConversation = conversation;
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001121 mTracker.initialize(mCurrentConversation);
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001122 }
1123
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001124 /**
1125 * {@inheritDoc}
1126 */
Mindy Pereira555140c2012-02-15 14:55:29 -08001127 @Override
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001128 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
1129 // Create a loader to listen in on account changes.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001130 switch (id) {
1131 case LOADER_ACCOUNT_CURSOR:
Paul Westbrookc2074c42012-03-22 15:26:58 -07001132 return new CursorLoader(mContext, MailAppProvider.getAccountsUri(),
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001133 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
1134 case LOADER_FOLDER_CURSOR:
Paul Westbrookc7a070f2012-04-12 01:46:41 -07001135 final CursorLoader loader = new CursorLoader(mContext, mFolder.uri,
1136 UIProvider.FOLDERS_PROJECTION, null, null, null);
1137 loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
1138 return loader;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001139 case LOADER_RECENT_FOLDERS:
Paul Westbrook91d10502012-04-13 12:01:39 -07001140 if (mAccount != null && mAccount.recentFolderListUri != null) {
Paul Westbrookea4ee202012-03-12 14:12:33 -07001141 return new CursorLoader(mContext, mAccount.recentFolderListUri,
1142 UIProvider.FOLDERS_PROJECTION, null, null, null);
1143 }
1144 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001145 case LOADER_ACCOUNT_INBOX:
Vikram Aggarwal025eba82012-05-08 10:45:30 -07001146 final Uri defaultInbox = Settings.getDefaultInboxUri(mAccount.settings);
Vikram Aggarwal1e57e672012-05-07 14:48:24 -07001147 final Uri inboxUri = defaultInbox.equals(Uri.EMPTY) ?
1148 mAccount.folderListUri : defaultInbox;
Paul Westbrook7496e822012-04-24 09:50:54 -07001149 LogUtils.d(LOG_TAG, "Loading the default inbox: %s", inboxUri);
Paul Westbrook1220b6d2012-04-10 00:48:00 -07001150 if (inboxUri != null) {
1151 return new CursorLoader(mContext, inboxUri, UIProvider.FOLDERS_PROJECTION, null,
1152 null, null);
1153 }
1154 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001155 case LOADER_SEARCH:
1156 return Folder.forSearchResults(mAccount,
1157 args.getString(ConversationListContext.EXTRA_SEARCH_QUERY),
1158 mActivity.getActivityContext());
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001159 case LOADER_ACCOUNT_UPDATE_CURSOR:
1160 return new CursorLoader(mContext, mAccount.uri, UIProvider.ACCOUNTS_PROJECTION,
1161 null, null, null);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001162 default:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001163 LogUtils.wtf(LOG_TAG, "Loader returned unexpected id: %d", id);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001164 }
1165 return null;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001166 }
1167
Paul Westbrookb1f573c2012-04-06 11:38:28 -07001168 @Override
1169 public void onLoaderReset(Loader<Cursor> loader) {
1170
1171 }
1172
Andy Huangf9a73482012-03-13 15:54:02 -07001173 /**
1174 * {@link LoaderManager} currently has a bug in
1175 * {@link LoaderManager#restartLoader(int, Bundle, android.app.LoaderManager.LoaderCallbacks)}
1176 * where, if a previous onCreateLoader returned a null loader, this method will NPE. Work around
1177 * this bug by destroying any loaders that may have been created as null (essentially because
1178 * they are optional loads, and may not apply to a particular account).
1179 * <p>
1180 * A simple null check before restarting a loader will not work, because that would not
1181 * give the controller a chance to invalidate UI corresponding the prior loader result.
1182 *
1183 * @param id loader ID to safely restart
Andy Huangf9a73482012-03-13 15:54:02 -07001184 */
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001185 private void restartOptionalLoader(int id) {
Andy Huangf9a73482012-03-13 15:54:02 -07001186 final LoaderManager lm = mActivity.getLoaderManager();
1187 lm.destroyLoader(id);
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001188 lm.restartLoader(id, Bundle.EMPTY, this);
1189 }
1190
Andy Huang632721e2012-04-11 16:57:26 -07001191 @Override
1192 public void registerConversationListObserver(DataSetObserver observer) {
1193 mConversationListObservable.registerObserver(observer);
1194 }
1195
1196 @Override
1197 public void unregisterConversationListObserver(DataSetObserver observer) {
1198 mConversationListObservable.unregisterObserver(observer);
1199 }
1200
Paul Westbrook23b74b92012-02-29 11:36:12 -08001201 private boolean accountsUpdated(Cursor accountCursor) {
1202 // Check to see if the current account hasn't been set, or the account cursor is empty
1203 if (mAccount == null || !accountCursor.moveToFirst()) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001204 return true;
Paul Westbrook23b74b92012-02-29 11:36:12 -08001205 }
1206
1207 // Check to see if the number of accounts are different, from the number we saw on the last
1208 // updated
1209 if (mCurrentAccountUris.size() != accountCursor.getCount()) {
1210 return true;
1211 }
1212
1213 // Check to see if the account list is different or if the current account is not found in
1214 // the cursor.
1215 boolean foundCurrentAccount = false;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001216 do {
Paul Westbrook23b74b92012-02-29 11:36:12 -08001217 final Uri accountUri =
1218 Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
1219 if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
1220 foundCurrentAccount = true;
1221 }
1222
1223 if (!mCurrentAccountUris.contains(accountUri)) {
1224 return true;
1225 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001226 } while (accountCursor.moveToNext());
Paul Westbrook23b74b92012-02-29 11:36:12 -08001227
1228 // As long as we found the current account, the list hasn't been updated
1229 return !foundCurrentAccount;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001230 }
1231
1232 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -08001233 * Update the accounts on the device. This currently loads the first account
1234 * in the list.
1235 *
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001236 * @param loader
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001237 * @param accounts cursor into the AccountCache
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001238 * @return true if the update was successful, false otherwise
1239 */
Paul Westbrook23b74b92012-02-29 11:36:12 -08001240 private boolean updateAccounts(Loader<Cursor> loader, Cursor accounts) {
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001241 if (accounts == null || !accounts.moveToFirst()) {
1242 return false;
1243 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001244
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001245 final Account[] allAccounts = Account.getAllAccounts(accounts);
Paul Westbrook23b74b92012-02-29 11:36:12 -08001246
1247 // Save the uris for the accounts
1248 mCurrentAccountUris.clear();
1249 for (Account account : allAccounts) {
1250 mCurrentAccountUris.add(account.uri);
1251 }
1252
Andy Huang0d647352012-03-21 21:48:16 -07001253 // 1. current account is already set and is in allAccounts -> no-op
1254 // 2. current account is set and is not in allAccounts -> pick first (acct was deleted?)
1255 // 3. saved pref has an account -> pick that one
1256 // 4. otherwise just pick first
1257
1258 Account newAccount = null;
1259
1260 if (mAccount != null) {
1261 if (!mCurrentAccountUris.contains(mAccount.uri)) {
1262 newAccount = allAccounts[0];
1263 } else {
1264 newAccount = mAccount;
1265 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001266 } else {
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001267 final String lastAccountUri = MailAppProvider.getInstance().getLastViewedAccount();
Andy Huang0d647352012-03-21 21:48:16 -07001268 if (lastAccountUri != null) {
1269 for (int i = 0; i < allAccounts.length; i++) {
1270 final Account acct = allAccounts[i];
1271 if (lastAccountUri.equals(acct.uri.toString())) {
1272 newAccount = acct;
1273 break;
1274 }
1275 }
1276 }
1277 if (newAccount == null) {
1278 newAccount = allAccounts[0];
1279 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001280 }
Andy Huang0d647352012-03-21 21:48:16 -07001281
Paul Westbrook23b74b92012-02-29 11:36:12 -08001282 onAccountChanged(newAccount);
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001283 mActionBarView.setAccounts(allAccounts);
1284 return (allAccounts.length > 0);
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001285 }
1286
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001287 private void disableNotifications() {
1288 mNewEmailReceiver.activate(mContext, this);
1289 }
1290
1291 private void enableNotifications() {
1292 mNewEmailReceiver.deactivate();
1293 }
1294
1295 private void disableNotificationsOnAccountChange(Account account) {
1296 // If the new mail suppression receiver is activated for a different account, we want to
1297 // activate it for the new account.
1298 if (mNewEmailReceiver.activated() &&
1299 !mNewEmailReceiver.notificationsDisabledForAccount(account)) {
1300 // Deactivate the current receiver, otherwise multiple receivers may be registered.
1301 mNewEmailReceiver.deactivate();
1302 mNewEmailReceiver.activate(mContext, this);
1303 }
1304 }
1305
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001306 /**
1307 * {@inheritDoc}
1308 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001309 @Override
1310 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001311 // We want to reinitialize only if we haven't ever been initialized, or
1312 // if the current account has vanished.
Paul Westbrooke3e84292012-03-05 16:19:30 -08001313 if (data == null) {
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001314 LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
Paul Westbrooke3e84292012-03-05 16:19:30 -08001315 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001316 switch (loader.getId()) {
1317 case LOADER_ACCOUNT_CURSOR:
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -07001318 // If the account list is not null, and the account list cursor is empty,
Paul Westbrook2388c5d2012-03-25 12:29:11 -07001319 // we need to start the specified activity.
1320 if (data != null && data.getCount() == 0) {
1321 // If an empty cursor is returned, the MailAppProvider is indicating that
1322 // no accounts have been specified. We want to navigate to the "add account"
1323 // activity that will handle the intent returned by the MailAppProvider
1324
1325 // If the MailAppProvider believes that all accounts have been loaded, and the
1326 // account list is still empty, we want to prompt the user to add an account
1327 final Bundle extras = data.getExtras();
1328 final boolean accountsLoaded =
1329 extras.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
1330
1331 if (accountsLoaded) {
1332 final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(mContext);
1333 if (noAccountIntent != null) {
1334 mActivity.startActivityForResult(noAccountIntent,
1335 ADD_ACCOUNT_REQUEST_CODE);
1336 }
1337 }
1338 } else {
1339 final boolean accountListUpdated = accountsUpdated(data);
1340 if (!isLoaderInitialized || accountListUpdated) {
1341 isLoaderInitialized = updateAccounts(loader, data);
1342 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001343 }
1344 break;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001345 case LOADER_ACCOUNT_UPDATE_CURSOR:
1346 // We have gotten an update for current account.
1347
1348 // Make sure that this is an update for what is the current account
1349 if (data != null && data.moveToFirst()) {
1350 final Account updatedAccount = new Account(data);
1351
1352 if (updatedAccount.uri.equals(mAccount.uri)) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001353 // Update the controller's reference to the current account
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001354 mAccount = updatedAccount;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -07001355 LogUtils.d(LOG_TAG, "AbstractActivityController.onLoadFinished(): "
1356 + "mAccount = %s", mAccount.uri);
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001357 dispatchSettingsChange(mAccount.settings);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001358
1359 // Got an update for the current account
1360 final boolean inWaitingMode = inWaitMode();
1361 if (!updatedAccount.isAccountIntialized() && !inWaitingMode) {
1362 // Transition to waiting mode
1363 showWaitForInitialization();
1364 } else if (updatedAccount.isAccountIntialized() && inWaitingMode) {
1365 // Dismiss waiting mode
1366 hideWaitForInitialization();
1367 } else if (!updatedAccount.isAccountIntialized() && inWaitingMode) {
1368 // Update the WaitFragment's account object
1369 updateWaitMode();
1370 }
1371 } else {
1372 LogUtils.e(LOG_TAG, "Got update for account: %s with current account: %s",
1373 updatedAccount.uri, mAccount.uri);
1374 // We need to restart the loader, so the correct account information will
1375 // be returned
1376 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1377 }
1378 }
1379 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001380 case LOADER_FOLDER_CURSOR:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001381 // Check status of the cursor.
Marc Blankfd9d0b82012-04-23 16:01:51 -07001382 if (data != null && data.moveToFirst()) {
1383 Folder folder = new Folder(data);
1384 if (folder.isSyncInProgress()) {
1385 mActionBarView.onRefreshStarted();
1386 } else {
1387 // Stop the spinner here.
1388 mActionBarView.onRefreshStopped(folder.lastSyncResult);
1389 }
1390 mActionBarView.onFolderUpdated(folder);
1391 final ConversationListFragment convList = getConversationListFragment();
1392 if (convList != null) {
1393 convList.onFolderUpdated(folder);
1394 }
1395 LogUtils.d(LOG_TAG, "FOLDER STATUS = %d", folder.syncStatus);
Paul Westbrookc808fac2012-02-22 16:42:18 -08001396 } else {
Marc Blankfd9d0b82012-04-23 16:01:51 -07001397 LogUtils.d(LOG_TAG, "Unable to get the folder %s",
1398 mFolder != null ? mAccount.name : "");
Mindy Pereira11dd5ef2012-03-10 15:10:18 -08001399 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001400 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001401 case LOADER_RECENT_FOLDERS:
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -07001402 // No recent folders and we are running on a phone? Populate the default recents.
1403 if (data != null && data.getCount() == 0 && !Utils.useTabletUI(mContext)) {
1404 final class PopulateDefault extends AsyncTask<Uri, Void, Void> {
1405 @Override
1406 protected Void doInBackground(Uri... uri) {
1407 // Asking for an update on the URI and ignore the result.
1408 final ContentResolver resolver = mContext.getContentResolver();
1409 resolver.update(uri[0], null, null, null);
1410 return null;
1411 }
1412 }
1413 final Uri uri = mAccount.defaultRecentFolderListUri;
1414 LogUtils.v(LOG_TAG, "Default recents at %s", uri);
1415 new PopulateDefault().execute(uri);
1416 break;
1417 }
1418 LogUtils.v(LOG_TAG, "Reading recent folders from the cursor.");
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001419 mRecentFolderList.loadFromUiProvider(data);
Vikram Aggarwal37263972012-04-17 15:51:14 -07001420 mActionBarView.requestRecentFoldersAndRedraw();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001421 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001422 case LOADER_ACCOUNT_INBOX:
Marc Blankfd9d0b82012-04-23 16:01:51 -07001423 if (data != null && !data.isClosed() && data.moveToFirst()) {
Mindy Pereira0e88e9f2012-03-25 13:47:41 -07001424 Folder inbox = new Folder(data);
1425 onFolderChanged(inbox);
Mindy Pereirab4a43282012-03-23 16:20:03 -07001426 // Just want to get the inbox, don't care about updates to it
1427 // as this will be tracked by the folder change listener.
1428 mActivity.getLoaderManager().destroyLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereira5ba33802012-03-26 16:30:11 -07001429 } else {
1430 LogUtils.d(LOG_TAG, "Unable to get the account inbox for account %s",
1431 mAccount != null ? mAccount.name : "");
Mindy Pereirab4a43282012-03-23 16:20:03 -07001432 }
Mindy Pereiraab486362012-03-21 18:18:53 -07001433 break;
1434 case LOADER_SEARCH:
1435 data.moveToFirst();
1436 Folder search = new Folder(data);
Mindy Pereira11e35962012-06-01 14:49:46 -07001437 updateFolder(search);
Mindy Pereiraab486362012-03-21 18:18:53 -07001438 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder,
Mindy Pereirad660d252012-03-26 11:48:43 -07001439 mActivity.getIntent()
1440 .getStringExtra(UIProvider.SearchQueryParameters.QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -07001441 showConversationList(mConvListContext);
1442 mActivity.invalidateOptionsMenu();
Mindy Pereira3b399222012-03-28 15:19:47 -07001443 mActivity.getLoaderManager().destroyLoader(LOADER_SEARCH);
Mindy Pereiraab486362012-03-21 18:18:53 -07001444 break;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001445 }
1446 }
1447
Vikram Aggarwalc7694222012-04-23 13:37:01 -07001448 /**
1449 * Destructive actions on Conversations. This class should only be created by controllers, and
1450 * clients should only require {@link DestructiveAction}s, not specific implementations of the.
1451 * Only the controllers should know what kind of destructive actions are being created.
1452 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001453 private class ConversationAction implements DestructiveAction {
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001454 /**
1455 * The action to be performed. This is specified as the resource ID of the menu item
1456 * corresponding to this action: R.id.delete, R.id.report_spam, etc.
1457 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001458 private final int mAction;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001459 /** The action will act upon these conversations */
1460 private final Collection<Conversation> mTarget = new ArrayList<Conversation>();
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001461 /** Whether this destructive action has already been performed */
1462 private boolean mCompleted;
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001463 /** Whether this is an action on the currently selected set. */
1464 private final boolean mIsSelectedSet;
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001465
Mindy Pereirafbe40192012-03-20 10:40:45 -07001466 /**
1467 * Create a listener object. action is one of four constants: R.id.y_button (archive),
1468 * R.id.delete , R.id.mute, and R.id.report_spam.
1469 * @param action
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001470 * @param target Conversation that we want to apply the action to.
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001471 * @param isBatch whether the conversations are in the currently selected batch set.
1472 */
1473 public ConversationAction(int action, Collection<Conversation> target, boolean isBatch) {
Mindy Pereirafbe40192012-03-20 10:40:45 -07001474 mAction = action;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001475 mTarget.addAll(target);
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001476 mIsSelectedSet = isBatch;
Mindy Pereirafbe40192012-03-20 10:40:45 -07001477 }
1478
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001479 /**
1480 * The action common to child classes. This performs the action specified in the constructor
1481 * on the conversations given here.
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001482 */
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001483 @Override
1484 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001485 if (isPerformed()) {
1486 return;
1487 }
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001488 // Certain actions force a return to list.
Mindy Pereira3cb28b52012-05-24 15:26:39 -07001489 boolean undoEnabled = mAccount.supportsCapability(AccountCapabilities.UNDO);
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001490
1491 // Are we destroying the currently shown conversation? Show the next one.
1492 if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)){
1493 LogUtils.d(LOG_TAG, "ConversationAction.performAction(): mIsConversationVisible=%b"
1494 + "\nmTarget=%s\nCurrent=%s", mIsConversationVisible,
1495 Conversation.toString(mTarget), mCurrentConversation);
1496 }
1497 if (mIsConversationVisible && Conversation.contains(mTarget, mCurrentConversation)) {
Mindy Pereiraa0c24442012-05-24 15:39:17 -07001498 int advance = Settings.getAutoAdvanceSetting(mAccount.settings);
1499 final Conversation next = advance == AutoAdvance.LIST ? null : mTracker
1500 .getNextConversation(advance, mTarget, mCurrentConversation);
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001501 LogUtils.d(LOG_TAG, "Next conversation is: %s", next);
1502 showConversation(next);
1503 }
1504
Mindy Pereirafbe40192012-03-20 10:40:45 -07001505 switch (mAction) {
Mindy Pereira0692baf2012-03-23 17:34:31 -07001506 case R.id.archive:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001507 LogUtils.d(LOG_TAG, "Archiving");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001508 mConversationListCursor.archive(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001509 break;
1510 case R.id.delete:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001511 LogUtils.d(LOG_TAG, "Deleting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001512 mConversationListCursor.delete(mContext, mTarget);
Marc Blank386243f2012-05-25 10:40:59 -07001513 if (!mFolder.supportsCapability(FolderCapabilities.DELETE_ACTION_FINAL)) {
1514 undoEnabled = false;
1515 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001516 break;
1517 case R.id.mute:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001518 LogUtils.d(LOG_TAG, "Muting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001519 if (mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE)) {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001520 for (Conversation c : mTarget) {
1521 c.localDeleteOnUpdate = true;
1522 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001523 }
1524 mConversationListCursor.mute(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001525 break;
1526 case R.id.report_spam:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001527 LogUtils.d(LOG_TAG, "Reporting spam");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001528 mConversationListCursor.reportSpam(mContext, mTarget);
1529 break;
1530 case R.id.remove_star:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001531 LogUtils.d(LOG_TAG, "Removing star");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001532 // Star removal is destructive in the Starred folder.
1533 mConversationListCursor.updateBoolean(mContext, mTarget,
1534 ConversationColumns.STARRED, false);
1535 break;
1536 case R.id.mark_not_important:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001537 LogUtils.d(LOG_TAG, "Marking not-important");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001538 // Marking not important is destructive in a mailbox containing only important
1539 // messages
1540 mConversationListCursor.updateInt(mContext, mTarget,
1541 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001542 break;
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001543 }
1544 if (undoEnabled) {
1545 onUndoAvailable(new UndoOperation(mTarget.size(), mAction));
1546 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001547 refreshConversationList();
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001548 if (mIsSelectedSet) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001549 mSelectedSet.clear();
1550 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001551 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001552
1553 /**
1554 * Returns true if this action has been performed, false otherwise.
1555 * @return
1556 */
1557 private synchronized boolean isPerformed() {
1558 if (mCompleted) {
1559 return true;
1560 }
1561 mCompleted = true;
1562 return false;
1563 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001564 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001565
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001566 /**
1567 * Get a destructive action for a menu action.
1568 * This is a temporary method, to control the profusion of {@link DestructiveAction} classes
1569 * that are created. Please do not copy this paradigm.
1570 * @param action the resource ID of the menu action: R.id.delete, for example
1571 * @param target the conversations to act upon.
1572 * @return a {@link DestructiveAction} that performs the specified action.
1573 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001574 private final DestructiveAction getAction(int action, Collection<Conversation> target) {
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001575 final DestructiveAction da = new ConversationAction(action, target, false);
1576 registerDestructiveAction(da);
1577 return da;
1578 }
1579
Vikram Aggarwald503df42012-05-11 10:13:35 -07001580 // Called from the FolderSelectionDialog after a user is done selecting folders to assign the
1581 // conversations to.
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001582 @Override
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001583 public final void assignFolder(
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001584 Collection<Folder> folders, Collection<Conversation> target, boolean batch) {
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001585 final boolean isDestructive = !Folder.containerIncludes(folders, mFolder);
Vikram Aggarwald503df42012-05-11 10:13:35 -07001586 LogUtils.d(LOG_TAG, "onFolderChangesCommit: isDestructive = %b", isDestructive);
1587 if (isDestructive) {
1588 for (final Conversation c : target) {
1589 c.localDeleteOnUpdate = true;
Mindy Pereira6778f462012-03-23 18:01:55 -07001590 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001591 }
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001592 final DestructiveAction folderChange = getFolderChange(target, folders, isDestructive,
1593 batch);
Vikram Aggarwald503df42012-05-11 10:13:35 -07001594 // Update the UI elements depending no their visibility and availability
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001595 // TODO(viki): Consolidate this into a single method requestDelete.
Vikram Aggarwald503df42012-05-11 10:13:35 -07001596 if (isDestructive) {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001597 delete(target, folderChange);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001598 } else {
Vikram Aggarwald503df42012-05-11 10:13:35 -07001599 requestUpdate(target, folderChange);
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001600 }
1601 }
1602
Mindy Pereira967ede62012-03-22 09:29:09 -07001603 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001604 public final void onRefreshRequired() {
Marc Blankbf128eb2012-04-18 15:58:45 -07001605 if (mIsConversationListScrolling) {
1606 LogUtils.d(LOG_TAG, "onRefreshRequired: delay until scrolling done");
1607 return;
1608 }
1609 // Refresh the query in the background
Vikram Aggarwalb9fb2c12012-05-11 14:25:25 -07001610 final long now = System.currentTimeMillis();
1611 final long sinceLastRefresh = now - mConversationListRefreshTime;
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001612 if (mConversationListCursor.isRefreshRequired()) {
1613 mConversationListCursor.refresh();
Marc Blankbf128eb2012-04-18 15:58:45 -07001614 mTracker.updateCursor(mConversationListCursor);
1615 mConversationListRefreshTime = now;
1616 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001617 }
1618
1619 /**
1620 * Called when the {@link ConversationCursor} is changed or has new data in it.
1621 * <p>
1622 * {@inheritDoc}
1623 */
1624 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001625 public final void onRefreshReady() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001626 if (!mIsConversationListScrolling) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001627 // Swap cursors
1628 mConversationListCursor.sync();
Marc Blankbf128eb2012-04-18 15:58:45 -07001629 }
1630 mTracker.updateCursor(mConversationListCursor);
1631 }
1632
1633 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001634 public final void onDataSetChanged() {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001635 updateConversationListFragment();
Andy Huang632721e2012-04-11 16:57:26 -07001636 mConversationListObservable.notifyChanged();
Marc Blankbf128eb2012-04-18 15:58:45 -07001637 }
1638
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001639 /**
1640 * If the Conversation List Fragment is visible, updates the fragment.
1641 */
1642 private final void updateConversationListFragment() {
Marc Blankbf128eb2012-04-18 15:58:45 -07001643 final ConversationListFragment convList = getConversationListFragment();
1644 if (convList != null) {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07001645 refreshConversationList();
Paul Westbrook9f119c72012-04-24 16:10:59 -07001646 if (convList.isVisible()) {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001647 Utils.setConversationCursorVisibility(mConversationListCursor, true);
1648 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001649 }
1650 }
1651
1652 /**
1653 * This class handles throttled refresh of the conversation list
1654 */
1655 static class RefreshTimerTask extends TimerTask {
1656 final Handler mHandler;
1657 final AbstractActivityController mController;
1658
1659 RefreshTimerTask(AbstractActivityController controller, Handler handler) {
1660 mHandler = handler;
1661 mController = controller;
1662 }
1663
1664 @Override
1665 public void run() {
1666 mHandler.post(new Runnable() {
1667 @Override
1668 public void run() {
1669 LogUtils.d(LOG_TAG, "Delay done... calling onRefreshRequired");
1670 mController.onRefreshRequired();
1671 }});
1672 }
1673 }
1674
1675 /**
1676 * Cancel the refresh task, if it's running
1677 */
1678 private void cancelRefreshTask () {
1679 if (mConversationListRefreshTask != null) {
1680 mConversationListRefreshTask.cancel();
1681 mConversationListRefreshTask = null;
1682 }
1683 }
1684
1685 @Override
1686 public void onScrollStateChanged(AbsListView view, int scrollState) {
1687 boolean isScrolling = (scrollState != OnScrollListener.SCROLL_STATE_IDLE);
1688 if (!isScrolling) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001689 if (mConversationListCursor.isRefreshRequired()) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001690 LogUtils.d(LOG_TAG, "Stop scrolling: refresh");
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001691 mConversationListCursor.refresh();
1692 } else if (mConversationListCursor.isRefreshReady()) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001693 LogUtils.d(LOG_TAG, "Stop scrolling: try sync");
1694 onRefreshReady();
1695 }
1696 }
1697 mIsConversationListScrolling = isScrolling;
1698 }
1699
1700 @Override
1701 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1702 int totalItemCount) {
1703 }
1704
1705 @Override
Mindy Pereira967ede62012-03-22 09:29:09 -07001706 public void onSetEmpty() {
Mindy Pereira967ede62012-03-22 09:29:09 -07001707 }
1708
1709 @Override
1710 public void onSetPopulated(ConversationSelectionSet set) {
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001711 final ConversationListFragment convList = getConversationListFragment();
1712 if (convList == null) {
1713 return;
1714 }
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001715 mCabActionMenu = new SelectedConversationsActionMenu(mActivity, set, mAccount, mFolder,
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001716 (SwipeableListView) convList.getListView());
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001717 enableCabMode();
Mindy Pereira967ede62012-03-22 09:29:09 -07001718 }
1719
Mindy Pereira967ede62012-03-22 09:29:09 -07001720 @Override
1721 public void onSetChanged(ConversationSelectionSet set) {
1722 // Do nothing. We don't care about changes to the set.
1723 }
1724
1725 @Override
1726 public ConversationSelectionSet getSelectedSet() {
1727 return mSelectedSet;
1728 }
1729
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001730 /**
1731 * Disable the Contextual Action Bar (CAB). The selected set is not changed.
1732 */
1733 protected void disableCabMode() {
1734 if (mCabActionMenu != null) {
1735 mCabActionMenu.deactivate();
1736 }
1737 }
1738
1739 /**
1740 * Re-enable the CAB menu if required. The selection set is not changed.
1741 */
1742 protected void enableCabMode() {
1743 if (mCabActionMenu != null) {
1744 mCabActionMenu.activate();
1745 }
1746 }
1747
Mindy Pereira967ede62012-03-22 09:29:09 -07001748 @Override
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001749 public void startSearch() {
Vikram Aggarwal35f19d72012-04-24 13:24:48 -07001750 if (mAccount == null) {
1751 // We cannot search if there is no account. Drop the request to the floor.
1752 LogUtils.d(LOG_TAG, "AbstractActivityController.startSearch(): null account");
1753 return;
1754 }
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001755 if (mAccount.supportsCapability(UIProvider.AccountCapabilities.LOCAL_SEARCH)
1756 | mAccount.supportsCapability(UIProvider.AccountCapabilities.SERVER_SEARCH)) {
Vikram Aggarwal70f130e2012-04-03 12:32:14 -07001757 onSearchRequested(mActionBarView.getQuery());
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001758 } else {
1759 Toast.makeText(mActivity.getActivityContext(), mActivity.getActivityContext()
Mindy Pereiraa46c2992012-03-27 14:12:39 -07001760 .getString(R.string.search_unsupported), Toast.LENGTH_SHORT).show();
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001761 }
1762 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07001763
Vikram Aggarwal0dda5732012-04-06 11:20:16 -07001764 @Override
1765 public void exitSearchMode() {
1766 if (mViewMode.getMode() == ViewMode.SEARCH_RESULTS_LIST) {
1767 mActivity.finish();
1768 }
1769 }
1770
Mindy Pereiraacf60392012-04-06 09:11:00 -07001771 /**
1772 * Supports dragging conversations to a folder.
1773 */
1774 @Override
1775 public boolean supportsDrag(DragEvent event, Folder folder) {
1776 return (folder != null
1777 && event != null
1778 && event.getClipDescription() != null
1779 && folder.supportsCapability
1780 (UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
1781 && folder.supportsCapability
1782 (UIProvider.FolderCapabilities.CAN_HOLD_MAIL)
1783 && !mFolder.uri.equals(folder.uri));
1784 }
1785
1786 /**
1787 * Handles dropping conversations to a label.
1788 */
1789 @Override
1790 public void handleDrop(DragEvent event, final Folder folder) {
Mindy Pereiraacf60392012-04-06 09:11:00 -07001791 if (!supportsDrag(event, folder)) {
1792 return;
1793 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07001794 final Collection<Conversation> conversations = mSelectedSet.values();
Vikram Aggarwal440fe792012-05-10 17:23:15 -07001795 final Collection<Folder> dropTarget = Folder.listOf(folder);
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001796 // Drag and drop is destructive: we remove conversations from the current folder.
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001797 final DestructiveAction action = getFolderChange(conversations, dropTarget, true, true);
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001798 delete(conversations, action);
Mindy Pereiraacf60392012-04-06 09:11:00 -07001799 }
Mindy Pereira0963ef82012-04-10 11:43:01 -07001800
1801 @Override
1802 public void onUndoCancel() {
1803 mUndoBarView.hide(false);
1804 }
1805
Paul Westbrookbf232c32012-04-18 03:17:41 -07001806
Mindy Pereira0963ef82012-04-10 11:43:01 -07001807 @Override
1808 public void onTouchEvent(MotionEvent event) {
1809 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1810 if (mUndoBarView != null && !mUndoBarView.isEventInUndo(event)) {
1811 mUndoBarView.hide(true);
1812 }
1813 }
1814 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001815
Andy Huang632721e2012-04-11 16:57:26 -07001816 @Override
1817 public void onConversationSeen(Conversation conv) {
1818 mPagerController.onConversationSeen(conv);
1819 }
1820
Andy Huangb1c34dc2012-04-17 16:36:19 -07001821 private class ConversationListLoaderCallbacks implements
1822 LoaderManager.LoaderCallbacks<ConversationCursor> {
1823
1824 @Override
1825 public Loader<ConversationCursor> onCreateLoader(int id, Bundle args) {
1826 Loader<ConversationCursor> result = new ConversationCursorLoader((Activity) mActivity,
Paul Westbrookbf232c32012-04-18 03:17:41 -07001827 mAccount, mFolder.conversationListUri, mFolder.name);
Andy Huangb1c34dc2012-04-17 16:36:19 -07001828 return result;
1829 }
1830
1831 @Override
1832 public void onLoadFinished(Loader<ConversationCursor> loader, ConversationCursor data) {
Andy Huang632721e2012-04-11 16:57:26 -07001833 LogUtils.d(LOG_TAG, "IN AAC.ConversationCursor.onLoadFinished, data=%s loader=%s",
1834 data, loader);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001835 // Clear our all pending destructive actions before swapping the conversation cursor
1836 destroyPending(null);
Andy Huangb1c34dc2012-04-17 16:36:19 -07001837 mConversationListCursor = data;
Paul Westbrookbf232c32012-04-18 03:17:41 -07001838 mConversationListCursor.addListener(AbstractActivityController.this);
Andy Huangb1c34dc2012-04-17 16:36:19 -07001839
Andy Huange3df1ad2012-04-24 17:15:23 -07001840 mConversationListObservable.notifyChanged();
1841
Andy Huangb1c34dc2012-04-17 16:36:19 -07001842 // Register the AbstractActivityController as a listener to changes in
1843 // data in the cursor.
1844 final ConversationListFragment convList = getConversationListFragment();
1845 if (convList != null) {
1846 convList.onCursorUpdated();
Paul Westbrookbf232c32012-04-18 03:17:41 -07001847 convList.getListView().setOnScrollListener(AbstractActivityController.this);
Paul Westbrook9f119c72012-04-24 16:10:59 -07001848
1849 if (convList.isVisible()) {
1850 // The conversation list is visible.
1851 Utils.setConversationCursorVisibility(mConversationListCursor, true);
1852 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001853 }
1854 // Shown for search results in two-pane mode only.
1855 if (shouldShowFirstConversation()) {
1856 if (mConversationListCursor.getCount() > 0) {
1857 mConversationListCursor.moveToPosition(0);
1858 if (convList != null) {
1859 convList.getListView().setItemChecked(0, true);
1860 }
1861 final Conversation conv = new Conversation(mConversationListCursor);
1862 conv.position = 0;
1863 onConversationSelected(conv);
1864 }
1865 }
1866 }
1867
1868 @Override
1869 public void onLoaderReset(Loader<ConversationCursor> loader) {
1870 final ConversationListFragment convList = getConversationListFragment();
1871 if (convList == null) {
1872 return;
1873 }
1874 convList.onCursorUpdated();
1875 }
Paul Westbrookbf232c32012-04-18 03:17:41 -07001876 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001877
Paul Westbrookbf232c32012-04-18 03:17:41 -07001878 @Override
1879 public void sendConversationRead(String toFragment, Conversation conversation, boolean state,
1880 boolean local) {
1881 if (toFragment.equals(TAG_CONVERSATION_LIST)) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001882 if (mConversationListCursor != null) {
Paul Westbrookbf232c32012-04-18 03:17:41 -07001883 if (local) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001884 mConversationListCursor.setConversationColumn(conversation.uri.toString(), ConversationColumns.READ,
Paul Westbrookbf232c32012-04-18 03:17:41 -07001885 state);
1886 } else {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001887 mConversationListCursor.markRead(mContext, state, conversation);
Paul Westbrookbf232c32012-04-18 03:17:41 -07001888 }
1889 }
1890 } else if (toFragment.equals(TAG_CONVERSATION)) {
1891 // TODO Handle setting read in conversation view
1892 }
1893 }
1894
1895 @Override
1896 public void sendConversationUriStarred(String toFragment, String conversationUri,
1897 boolean state, boolean local) {
1898 if (toFragment.equals(TAG_CONVERSATION_LIST)) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001899 if (mConversationListCursor != null) {
Paul Westbrookbf232c32012-04-18 03:17:41 -07001900 if (local) {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001901 mConversationListCursor.setConversationColumn(conversationUri, ConversationColumns.STARRED, state);
Paul Westbrookbf232c32012-04-18 03:17:41 -07001902 } else {
Vikram Aggarwalef7c9922012-04-23 12:35:04 -07001903 mConversationListCursor.updateBoolean(mContext, conversationUri, ConversationColumns.STARRED, state);
Paul Westbrookbf232c32012-04-18 03:17:41 -07001904 }
1905 }
1906 } else if (toFragment.equals(TAG_CONVERSATION)) {
1907 // TODO Handle setting starred in conversation view
1908 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001909 }
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001910
1911 /**
1912 * Destroy the pending {@link DestructiveAction} till now and assign the given action as the
1913 * next destructive action..
1914 * @param nextAction the next destructive action to be performed. This can be null.
1915 */
1916 private final void destroyPending(DestructiveAction nextAction) {
1917 // If there is a pending action, perform that first.
1918 if (mPendingDestruction != null) {
1919 mPendingDestruction.performAction();
1920 }
1921 mPendingDestruction = nextAction;
1922 }
1923
1924 /**
1925 * Register a destructive action with the controller. This performs the previous destructive
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001926 * 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 -07001927 * embellish this method any more.
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001928 * @param action
1929 */
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001930 private final void registerDestructiveAction(DestructiveAction action) {
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001931 // TODO(viki): This is not a good idea. The best solution is for clients to request a
1932 // destructive action from the controller and for the controller to own the action. This is
1933 // a half-way solution while refactoring DestructiveAction.
1934 destroyPending(action);
1935 return;
1936 }
1937
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001938 @Override
1939 public final DestructiveAction getBatchAction(int action) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001940 final DestructiveAction da = new ConversationAction(action, mSelectedSet.values(), true);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001941 registerDestructiveAction(da);
1942 return da;
1943 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001944
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001945 /**
1946 * Class to change the folders that are assigned to a set of conversations. This is destructive
1947 * because the user can remove the current folder from the conversation, in which case it has
1948 * to be animated away from the current folder.
1949 */
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001950 private class FolderDestruction implements DestructiveAction {
1951 private final Collection<Conversation> mTarget = new ArrayList<Conversation>();
1952 private final ArrayList<Folder> mFolderList = new ArrayList<Folder>();
1953 private final boolean mIsDestructive;
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001954 /** Whether this destructive action has already been performed */
1955 private boolean mCompleted;
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001956 private boolean mIsSelectedSet;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001957
1958 /**
1959 * Create a new folder destruction object to act on the given conversations.
1960 * @param target
1961 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001962 private FolderDestruction(final Collection<Conversation> target,
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001963 final Collection<Folder> folders, boolean isDestructive, boolean isBatch) {
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001964 mTarget.addAll(target);
1965 mFolderList.addAll(folders);
1966 mIsDestructive = isDestructive;
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001967 mIsSelectedSet = isBatch;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001968 }
1969
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001970 @Override
1971 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001972 if (isPerformed()) {
1973 return;
1974 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001975 if (mIsDestructive) {
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001976 UndoOperation undoOp = new UndoOperation(mTarget.size(), R.id.change_folder);
1977 onUndoAvailable(undoOp);
1978 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001979 mConversationListCursor.updateString(mContext, mTarget,
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001980 ConversationColumns.FOLDER_LIST, Folder.getUriString(mFolderList));
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001981 mConversationListCursor.updateString(mContext, mTarget,
1982 ConversationColumns.RAW_FOLDERS,
1983 Folder.getSerializedFolderString(mFolder, mFolderList));
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001984 refreshConversationList();
Mindy Pereiraf3a45562012-05-24 16:30:19 -07001985 if (mIsSelectedSet) {
1986 mSelectedSet.clear();
1987 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001988 }
1989 /**
1990 * Returns true if this action has been performed, false otherwise.
1991 * @return
1992 */
1993 private synchronized boolean isPerformed() {
1994 if (mCompleted) {
1995 return true;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001996 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001997 mCompleted = true;
1998 return false;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07001999 }
2000 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002001
Vikram Aggarwald503df42012-05-11 10:13:35 -07002002 private final DestructiveAction getFolderChange(Collection<Conversation> target,
Mindy Pereiraf3a45562012-05-24 16:30:19 -07002003 Collection<Folder> folders, boolean isDestructive, boolean isBatch){
2004 final DestructiveAction da = new FolderDestruction(target, folders, isDestructive, isBatch);
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002005 registerDestructiveAction(da);
2006 return da;
2007 }
Vikram Aggarwal75daee52012-04-30 11:13:09 -07002008
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07002009 @Override
2010 public final void refreshConversationList() {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07002011 final ConversationListFragment convList = getConversationListFragment();
2012 if (convList == null) {
2013 return;
2014 }
2015 convList.requestListRefresh();
2016 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08002017}