blob: 4b0d808f17669bfd5986d02fb9cb399894fa6f09 [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;
Andrew Sapperstein00179f12012-08-09 15:15:40 -070025import android.app.DialogFragment;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -070026import android.app.Fragment;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -070027import android.app.FragmentManager;
Andy Huangf9a73482012-03-13 15:54:02 -070028import android.app.LoaderManager;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070029import android.app.SearchManager;
Mindy Pereira830bdaf2012-07-11 12:59:55 -070030import android.content.ComponentName;
Andy Huang839ada22012-07-20 15:48:40 -070031import android.content.ContentProviderOperation;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080032import android.content.ContentResolver;
Mindy Pereira6c2663d2012-07-20 15:37:29 -070033import android.content.ContentValues;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080034import android.content.Context;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080035import android.content.CursorLoader;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070036import android.content.DialogInterface;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080037import android.content.Intent;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080038import android.content.Loader;
Mindy Pereira830bdaf2012-07-11 12:59:55 -070039import android.content.pm.PackageManager;
40import android.content.res.Resources;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080041import android.database.Cursor;
Andy Huang632721e2012-04-11 16:57:26 -070042import android.database.DataSetObservable;
43import android.database.DataSetObserver;
Paul Westbrook23b74b92012-02-29 11:36:12 -080044import android.net.Uri;
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -070045import android.os.AsyncTask;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080046import android.os.Bundle;
Mindy Pereira21ab4902012-03-19 18:48:03 -070047import android.os.Handler;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070048import android.provider.SearchRecentSuggestions;
Mindy Pereira830bdaf2012-07-11 12:59:55 -070049import android.text.TextUtils;
Mindy Pereiraacf60392012-04-06 09:11:00 -070050import android.view.DragEvent;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080051import android.view.KeyEvent;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080052import android.view.LayoutInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080053import android.view.Menu;
Mindy Pereira28d5f722012-02-15 12:32:40 -080054import android.view.MenuInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080055import android.view.MenuItem;
56import android.view.MotionEvent;
Mindy Pereirad33674992012-06-25 16:26:30 -070057import android.view.View;
Mindy Pereirafd0c2972012-03-27 13:50:39 -070058import android.widget.Toast;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080059
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080060import com.android.mail.ConversationListContext;
Andy Huangf9a73482012-03-13 15:54:02 -070061import com.android.mail.R;
Mindy Pereira967ede62012-03-22 09:29:09 -070062import com.android.mail.browse.ConversationCursor;
Paul Westbrookbf232c32012-04-18 03:17:41 -070063import com.android.mail.browse.ConversationPagerController;
Andrew Sapperstein00179f12012-08-09 15:15:40 -070064import com.android.mail.browse.SyncErrorDialogFragment;
Andy Huang839ada22012-07-20 15:48:40 -070065import com.android.mail.browse.MessageCursor.ConversationMessage;
Marc Blank7c9f6ac2012-04-02 13:27:19 -070066import com.android.mail.browse.SelectedConversationsActionMenu;
Mindy Pereira9b875682012-02-15 18:10:54 -080067import com.android.mail.compose.ComposeActivity;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080068import com.android.mail.providers.Account;
Mindy Pereira9b875682012-02-15 18:10:54 -080069import com.android.mail.providers.Conversation;
Andy Huang839ada22012-07-20 15:48:40 -070070import com.android.mail.providers.ConversationInfo;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080071import com.android.mail.providers.Folder;
Vikram Aggarwal7a5d95a2012-07-27 16:24:54 -070072import com.android.mail.providers.FolderWatcher;
Paul Westbrookc2074c42012-03-22 15:26:58 -070073import com.android.mail.providers.MailAppProvider;
Mindy Pereiradac00542012-03-01 10:50:33 -080074import com.android.mail.providers.Settings;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070075import com.android.mail.providers.SuggestionsProvider;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080076import com.android.mail.providers.UIProvider;
Mindy Pereira3cb28b52012-05-24 15:26:39 -070077import com.android.mail.providers.UIProvider.AccountCapabilities;
Paul Westbrook2388c5d2012-03-25 12:29:11 -070078import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
Mindy Pereirac9d59182012-03-22 16:06:46 -070079import com.android.mail.providers.UIProvider.ConversationColumns;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070080import com.android.mail.providers.UIProvider.FolderCapabilities;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -070081import com.android.mail.ui.ActionableToastBar.ActionClickedListener;
Andy Huang839ada22012-07-20 15:48:40 -070082import com.android.mail.utils.ContentProviderTask;
Paul Westbrookb334c902012-06-25 11:42:46 -070083import com.android.mail.utils.LogTag;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080084import com.android.mail.utils.LogUtils;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -080085import com.android.mail.utils.Utils;
Paul Westbrookca08fc12012-07-31 12:01:15 -070086import com.google.common.base.Objects;
Paul Westbrook77eee622012-07-10 13:41:57 -070087import com.google.common.collect.ImmutableList;
Mindy Pereiraacf60392012-04-06 09:11:00 -070088import com.google.common.collect.Lists;
Marc Blank167faa82012-03-21 13:11:53 -070089import com.google.common.collect.Sets;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080090
Marc Blank167faa82012-03-21 13:11:53 -070091import java.util.ArrayList;
Mindy Pereirafbe40192012-03-20 10:40:45 -070092import java.util.Collection;
Andy Huang839ada22012-07-20 15:48:40 -070093import java.util.Collections;
Mindy Pereira8db7e402012-07-13 10:32:47 -070094import java.util.HashMap;
Paul Westbrook23b74b92012-02-29 11:36:12 -080095import java.util.Set;
Marc Blankbf128eb2012-04-18 15:58:45 -070096import java.util.TimerTask;
Paul Westbrook23b74b92012-02-29 11:36:12 -080097
98
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080099/**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800100 * This is an abstract implementation of the Activity Controller. This class
101 * knows how to respond to menu items, state changes, layout changes, etc. It
102 * weaves together the views and listeners, dispatching actions to the
103 * respective underlying classes.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800104 * <p>
Mindy Pereira161f50d2012-02-28 15:47:19 -0800105 * Even though this class is abstract, it should provide default implementations
106 * for most, if not all the methods in the ActivityController interface. This
107 * makes the task of the subclasses easier: OnePaneActivityController and
108 * TwoPaneActivityController can be concise when the common functionality is in
109 * AbstractActivityController.
110 * </p>
111 * <p>
112 * In the Gmail codebase, this was called BaseActivityController
113 * </p>
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800114 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700115public abstract class AbstractActivityController implements ActivityController {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800116 // Keys for serialization of various information in Bundles.
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700117 /** Tag for {@link #mAccount} */
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800118 private static final String SAVED_ACCOUNT = "saved-account";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700119 /** Tag for {@link #mFolder} */
Mindy Pereira5e478d22012-03-26 18:04:58 -0700120 private static final String SAVED_FOLDER = "saved-folder";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700121 /** Tag for {@link #mCurrentConversation} */
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700122 private static final String SAVED_CONVERSATION = "saved-conversation";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700123 /** Tag for {@link #mSelectedSet} */
124 private static final String SAVED_SELECTED_SET = "saved-selected-set";
Mindy Pereirad33674992012-06-25 16:26:30 -0700125 private static final String SAVED_TOAST_BAR_OP = "saved-toast-bar-op";
Mindy Pereira148b1912012-07-17 13:39:56 -0700126 protected static final String SAVED_HIERARCHICAL_FOLDER = "saved-hierarchical-folder";
Vikram Aggarwalf3341402012-08-07 10:09:38 -0700127 /** Tag for {@link ConversationListContext#searchQuery} */
128 private static final String SAVED_QUERY = "saved-query";
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800129
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700130 /** Tag used when loading a wait fragment */
131 protected static final String TAG_WAIT = "wait-fragment";
132 /** Tag used when loading a conversation list fragment. */
Paul Westbrookbf232c32012-04-18 03:17:41 -0700133 public static final String TAG_CONVERSATION_LIST = "tag-conversation-list";
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700134 /** Tag used when loading a folder list fragment. */
135 protected static final String TAG_FOLDER_LIST = "tag-folder-list";
136
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800137 protected Account mAccount;
Mindy Pereira12a4d802012-06-22 09:24:43 -0700138 protected Folder mFolder;
Andy Huang6681e542012-06-14 14:36:45 -0700139 protected MailActionBarView mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800140 protected final RestrictedActivity mActivity;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800141 protected final Context mContext;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700142 private final FragmentManager mFragmentManager;
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800143 protected final RecentFolderList mRecentFolderList;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800144 protected ConversationListContext mConvListContext;
Mindy Pereira9b875682012-02-15 18:10:54 -0800145 protected Conversation mCurrentConversation;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800146
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700147 /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
148 private SuppressNotificationReceiver mNewEmailReceiver = null;
149
Mindy Pereirafbe40192012-03-20 10:40:45 -0700150 protected Handler mHandler = new Handler();
Mindy Pereirafa995b42012-07-25 12:06:13 -0700151
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800152 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800153 * The current mode of the application. All changes in mode are initiated by
154 * the activity controller. View mode changes are propagated to classes that
155 * attach themselves as listeners of view mode changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800156 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800157 protected final ViewMode mViewMode;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800158 protected ContentResolver mResolver;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800159 protected boolean isLoaderInitialized = false;
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800160 private AsyncRefreshTask mAsyncRefreshTask;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800161
Andy Huang4e0158f2012-08-07 21:06:01 -0700162 private boolean mDestroyed;
163
Paul Westbrook23b74b92012-02-29 11:36:12 -0800164 private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
Mindy Pereira967ede62012-03-22 09:29:09 -0700165 protected ConversationCursor mConversationListCursor;
Andy Huang632721e2012-04-11 16:57:26 -0700166 private final DataSetObservable mConversationListObservable = new DataSetObservable() {
167 @Override
168 public void registerObserver(DataSetObserver observer) {
169 final int count = mObservers.size();
170 super.registerObserver(observer);
171 LogUtils.d(LOG_TAG, "IN AAC.registerListObserver: %s before=%d after=%d", observer,
172 count, mObservers.size());
173 }
174 @Override
175 public void unregisterObserver(DataSetObserver observer) {
176 final int count = mObservers.size();
177 super.unregisterObserver(observer);
178 LogUtils.d(LOG_TAG, "IN AAC.unregisterListObserver: %s before=%d after=%d", observer,
179 count, mObservers.size());
180 }
181 };
Marc Blankbf128eb2012-04-18 15:58:45 -0700182
183 private boolean mIsConversationListScrolling = false;
Marc Blankbf128eb2012-04-18 15:58:45 -0700184 private RefreshTimerTask mConversationListRefreshTask;
Marc Blanke1d1b072012-04-13 17:29:16 -0700185
Vikram Aggarwalc7694222012-04-23 13:37:01 -0700186 /** Listeners that are interested in changes to current account settings. */
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700187 private final ArrayList<Settings.ChangeListener> mSettingsListeners = Lists.newArrayList();
188
Mindy Pereira967ede62012-03-22 09:29:09 -0700189 /**
190 * Selected conversations, if any.
191 */
Andy Huang4556a442012-03-30 16:42:05 -0700192 private final ConversationSelectionSet mSelectedSet = new ConversationSelectionSet();
Mindy Pereiradac00542012-03-01 10:50:33 -0800193
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700194 private final int mFolderItemUpdateDelayMs;
195
Vikram Aggarwal135fd022012-04-11 14:44:15 -0700196 /** Keeps track of selected and unselected conversations */
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700197 final protected ConversationPositionTracker mTracker = new ConversationPositionTracker();
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700198
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700199 /**
200 * Action menu associated with the selected set.
201 */
202 SelectedConversationsActionMenu mCabActionMenu;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700203 protected ActionableToastBar mToastBar;
Andy Huang632721e2012-04-11 16:57:26 -0700204 protected ConversationPagerController mPagerController;
Mindy Pereiraab486362012-03-21 18:18:53 -0700205
Andy Huangb1c34dc2012-04-17 16:36:19 -0700206 // this is split out from the general loader dispatcher because its loader doesn't return a
207 // basic Cursor
208 private final ConversationListLoaderCallbacks mListCursorCallbacks =
209 new ConversationListLoaderCallbacks();
210
Andy Huang090db1e2012-07-25 13:25:28 -0700211 private final DataSetObservable mFolderObservable = new DataSetObservable();
212
Paul Westbrookb334c902012-06-25 11:42:46 -0700213 protected static final String LOG_TAG = LogTag.getLogTag();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800214 /** Constants used to differentiate between the types of loaders. */
215 private static final int LOADER_ACCOUNT_CURSOR = 0;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800216 private static final int LOADER_FOLDER_CURSOR = 2;
217 private static final int LOADER_RECENT_FOLDERS = 3;
Mindy Pereira967ede62012-03-22 09:29:09 -0700218 private static final int LOADER_CONVERSATION_LIST = 4;
Mindy Pereiraab486362012-03-21 18:18:53 -0700219 private static final int LOADER_ACCOUNT_INBOX = 5;
220 private static final int LOADER_SEARCH = 6;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -0700221 private static final int LOADER_ACCOUNT_UPDATE_CURSOR = 7;
Vikram Aggarwal7a5d95a2012-07-27 16:24:54 -0700222 /**
223 * Guaranteed to be the last loader ID used by the activity. Loaders are owned by Activity or
224 * fragments, and within an activity, loader IDs need to be unique. A hack to ensure that the
225 * {@link FolderWatcher} can create its folder loaders without clashing with the IDs of those
226 * of the {@link AbstractActivityController}. Currently, the {@link FolderWatcher} is the only
227 * other class that uses this activity's LoaderManager. If another class needs activity-level
228 * loaders, consider consolidating the loaders in a central location: a UI-less fragment
229 * perhaps.
230 */
231 public static final int LAST_LOADER_ID = 100;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800232
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700233 private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
234
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700235 /** The pending destructive action to be carried out before swapping the conversation cursor.*/
236 private DestructiveAction mPendingDestruction;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700237 protected AsyncRefreshTask mFolderSyncTask;
Mindy Pereira49e5dbe2012-07-12 11:47:54 -0700238 // Task for setting any share intents for the account to enabled.
239 // This gets cancelled if the user kills the app before it finishes, and
240 // will just run the next time the user opens the app.
241 private AsyncTask<String, Void, Void> mEnableShareIntents;
Mindy Pereirac975e842012-07-16 09:15:00 -0700242 private Folder mFolderListFolder;
Andrew Sapperstein00179f12012-08-09 15:15:40 -0700243 public static final String SYNC_ERROR_DIALOG_FRAGMENT_TAG = "SyncErrorDialogFragment";
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700244
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800245 public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
246 mActivity = activity;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700247 mFragmentManager = mActivity.getFragmentManager();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800248 mViewMode = viewMode;
249 mContext = activity.getApplicationContext();
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700250 mRecentFolderList = new RecentFolderList(mContext);
Mindy Pereira967ede62012-03-22 09:29:09 -0700251 // Allow the fragment to observe changes to its own selection set. No other object is
252 // aware of the selected set.
253 mSelectedSet.addObserver(this);
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700254
255 mFolderItemUpdateDelayMs =
256 mContext.getResources().getInteger(R.integer.folder_item_refresh_delay_ms);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800257 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800258
259 @Override
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800260 public Account getCurrentAccount() {
261 return mAccount;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800262 }
263
264 @Override
265 public ConversationListContext getCurrentListContext() {
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800266 return mConvListContext;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800267 }
268
269 @Override
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800270 public String getHelpContext() {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800271 return "Mail";
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800272 }
273
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800274 @Override
Vikram Aggarwalef7c9922012-04-23 12:35:04 -0700275 public final ConversationCursor getConversationListCursor() {
Mindy Pereira967ede62012-03-22 09:29:09 -0700276 return mConversationListCursor;
277 }
278
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700279 /**
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700280 * Check if the fragment is attached to an activity and has a root view.
281 * @param in
282 * @return true if the fragment is valid, false otherwise
283 */
284 private static final boolean isValidFragment(Fragment in) {
285 if (in == null || in.getActivity() == null || in.getView() == null) {
286 return false;
287 }
288 return true;
289 }
290
291 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700292 * Get the conversation list fragment for this activity. If the conversation list fragment
293 * is not attached, this method returns null
Andy Huang839ada22012-07-20 15:48:40 -0700294 *
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700295 */
296 protected ConversationListFragment getConversationListFragment() {
297 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700298 if (isValidFragment(fragment)) {
299 return (ConversationListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700300 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700301 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700302 }
303
304 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700305 * Returns the folder list fragment attached with this activity. If no such fragment is attached
306 * this method returns null.
Andy Huang839ada22012-07-20 15:48:40 -0700307 *
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700308 */
309 protected FolderListFragment getFolderListFragment() {
310 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_FOLDER_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700311 if (isValidFragment(fragment)) {
312 return (FolderListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700313 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700314 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700315 }
316
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800317 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800318 * Initialize the action bar. This is not visible to OnePaneController and
319 * TwoPaneController so they cannot override this behavior.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800320 */
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700321 private void initializeActionBar() {
322 final ActionBar actionBar = mActivity.getActionBar();
Andy Huang5895f7b2012-06-01 17:07:20 -0700323 if (actionBar == null) {
324 return;
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700325 }
Andy Huang5895f7b2012-06-01 17:07:20 -0700326
327 // be sure to inherit from the ActionBar theme when inflating
328 final LayoutInflater inflater = LayoutInflater.from(actionBar.getThemedContext());
Mindy Pereira82faec72012-06-14 17:21:50 -0700329 final boolean isSearch = mActivity.getIntent() != null
330 && Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction());
331 mActionBarView = (MailActionBarView) inflater.inflate(
332 isSearch ? R.layout.search_actionbar_view : R.layout.actionbar_view, null);
Andy Huang5895f7b2012-06-01 17:07:20 -0700333 mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700334 }
335
336 /**
337 * Attach the action bar to the activity.
338 */
339 private void attachActionBar() {
340 final ActionBar actionBar = mActivity.getActionBar();
341 if (actionBar != null && mActionBarView != null) {
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800342 actionBar.setCustomView(mActionBarView, new ActionBar.LayoutParams(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800343 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
344 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
Vikram Aggarwal2a25d0c2012-02-21 16:43:10 -0800345 ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700346 mActionBarView.attach();
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800347 }
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700348 mViewMode.addListener(mActionBarView);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800349 }
350
351 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800352 * Returns whether the conversation list fragment is visible or not.
353 * Different layouts will have their own notion on the visibility of
354 * fragments, so this method needs to be overriden.
355 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800356 */
357 protected abstract boolean isConversationListVisible();
358
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700359 /**
360 * Switch the current account to the one provided as an argument to the method.
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700361 * @param account new account
362 * @param shouldReloadInbox whether the default inbox should be reloaded.
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700363 */
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700364 private void switchAccount(Account account, boolean shouldReloadInbox){
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700365 // Current account is different from the new account, restart loaders and show
366 // the account Inbox.
367 mAccount = account;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -0700368 LogUtils.d(LOG_TAG, "AbstractActivityController.switchAccount(): mAccount = %s",
369 mAccount.uri);
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700370 cancelRefreshTask();
Vikram Aggarwal60069912012-07-24 14:26:09 -0700371 updateSettings();
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700372 mActionBarView.setAccount(mAccount);
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700373 if (shouldReloadInbox) {
374 loadAccountInbox();
375 }
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700376 mRecentFolderList.setCurrentAccount(account);
377 restartOptionalLoader(LOADER_RECENT_FOLDERS);
378 mActivity.invalidateOptionsMenu();
379 disableNotificationsOnAccountChange(mAccount);
380 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
381 MailAppProvider.getInstance().setLastViewedAccount(mAccount.uri.toString());
382 }
383
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800384 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800385 public void onAccountChanged(Account account) {
Vikram Aggarwal60069912012-07-24 14:26:09 -0700386 LogUtils.d(LOG_TAG, "onAccountChanged (%s) called.", account);
387 // Is the account or account settings different from the existing account?
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700388 final boolean firstLoad = mAccount == null;
389 final boolean accountChanged = firstLoad || !account.uri.equals(mAccount.uri);
390 final boolean settingsChanged = firstLoad || !account.settings.equals(mAccount.settings);
391 if (accountChanged || settingsChanged) {
Mindy Pereirafa20c1a2012-07-23 13:00:02 -0700392 if (account != null) {
Mindy Pereirafa995b42012-07-25 12:06:13 -0700393 final String accountName = account.name;
394 mHandler.post(new Runnable() {
395 @Override
396 public void run() {
397 MailActivity.setForegroundNdef(MailActivity.getMailtoNdef(accountName));
398 }
399 });
Mindy Pereirafa20c1a2012-07-23 13:00:02 -0700400 }
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700401 switchAccount(account, accountChanged);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800402 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800403 }
404
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700405 /**
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700406 * Changes the settings for the current account. The new settings are provided as a parameter.
407 * @param settings
408 */
Vikram Aggarwal60069912012-07-24 14:26:09 -0700409 public void updateSettings() {
410 notifySettingsChanged();
Mindy Pereira12a676a2012-03-23 13:00:22 -0700411 resetActionBarIcon();
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700412 mActivity.invalidateOptionsMenu();
413 // If the user was viewing the default Inbox here, and the new setting contains a different
Vikram Aggarwal60069912012-07-24 14:26:09 -0700414 // default Inbox, we don't want to load a different folder here. So do not change the
415 // current folder.
Mindy Pereiradac00542012-03-01 10:50:33 -0800416 }
417
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800418 @Override
419 public Settings getSettings() {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700420 return mAccount.settings;
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800421 }
422
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700423 /**
424 * Adds a listener interested in change in settings. If a class is storing a reference to
425 * Settings, it should listen on changes, so it can receive updates to settings.
426 * Must happen in the UI thread.
427 */
428 public void addSettingsListener(Settings.ChangeListener listener) {
429 mSettingsListeners.add(listener);
430 }
431
432 /**
433 * Removes a listener from receiving settings changes.
434 * Must happen in the UI thread.
435 */
436 public void removeSettingsListener(Settings.ChangeListener listener) {
437 mSettingsListeners.remove(listener);
438 }
439
440 /**
441 * Method that lets the settings listeners know when the settings got changed.
442 */
Vikram Aggarwal60069912012-07-24 14:26:09 -0700443 private void notifySettingsChanged() {
444 final Settings updatedSettings = mAccount.settings;
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700445 // Copy the list of current listeners so that
446 final ArrayList<Settings.ChangeListener> allListeners =
447 new ArrayList<Settings.ChangeListener>(mSettingsListeners);
448 for (Settings.ChangeListener listener : allListeners) {
449 if (listener != null) {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700450 listener.onSettingsChanged(updatedSettings);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700451 }
452 }
453 // And we know that the ConversationListFragment is interested in changes to settings,
454 // though it hasn't registered itself with us.
455 final ConversationListFragment convList = getConversationListFragment();
456 if (convList != null) {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700457 convList.onSettingsChanged(updatedSettings);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700458 }
459 }
460
Mindy Pereirae0828392012-03-08 10:38:40 -0800461 private void fetchSearchFolder(Intent intent) {
Mindy Pereiraab486362012-03-21 18:18:53 -0700462 Bundle args = new Bundle();
463 args.putString(ConversationListContext.EXTRA_SEARCH_QUERY, intent
Mindy Pereirae0828392012-03-08 10:38:40 -0800464 .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -0700465 mActivity.getLoaderManager().restartLoader(LOADER_SEARCH, args, this);
Mindy Pereirae0828392012-03-08 10:38:40 -0800466 }
467
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800468 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800469 public void onFolderChanged(Folder folder) {
Vikram Aggarwalf3341402012-08-07 10:09:38 -0700470 changeFolder(folder, null);
471 }
472
473 /**
474 * Changes the folder to the value provided here. This causes the view mode to change.
475 * @param folder the folder to change to
476 * @param query if non-null, this represents the search string that the folder represents.
477 */
478 private void changeFolder(Folder folder, String query) {
Mindy Pereiraa1f99982012-07-16 16:32:15 -0700479 if (folder != null && !folder.equals(mFolder)
480 || (mViewMode.getMode() != ViewMode.CONVERSATION_LIST)) {
Mindy Pereira11e35962012-06-01 14:49:46 -0700481 updateFolder(folder);
Vikram Aggarwalf3341402012-08-07 10:09:38 -0700482 if (query != null) {
483 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder, query);
484 } else {
485 mConvListContext = ConversationListContext.forFolder(mAccount, mFolder);
486 }
Mindy Pereira28e0c342012-02-17 15:05:13 -0800487 showConversationList(mConvListContext);
Paul Westbrook9024b6d2012-03-19 13:57:55 -0700488
489 // Add the folder that we were viewing to the recent folders list.
490 // TODO: this may need to be fine tuned. If this is the signal that is indicating that
491 // the list is shown to the user, this could fire in one pane if the user goes directly
492 // to a conversation
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700493 updateRecentFolderList();
Marc Blankbf128eb2012-04-18 15:58:45 -0700494 cancelRefreshTask();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800495 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800496 }
497
Mindy Pereira13c12a62012-05-31 15:41:08 -0700498 @Override
Mindy Pereira505df5f2012-06-19 17:57:17 -0700499 public void onFolderSelected(Folder folder) {
Mindy Pereira13c12a62012-05-31 15:41:08 -0700500 onFolderChanged(folder);
501 }
502
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700503 /**
504 * Update the recent folders. This only needs to be done once when accessing a new folder.
505 */
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700506 private void updateRecentFolderList() {
Mindy Pereiraab486362012-03-21 18:18:53 -0700507 if (mFolder != null) {
Marc Blank2675dbc2012-04-03 10:17:13 -0700508 mRecentFolderList.touchFolder(mFolder, mAccount);
Mindy Pereiraab486362012-03-21 18:18:53 -0700509 }
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700510 }
511
Mindy Pereiraab486362012-03-21 18:18:53 -0700512 // TODO(mindyp): set this up to store a copy of the folder as a transient
513 // field in the account.
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700514 @Override
515 public void loadAccountInbox() {
Vikram Aggarwal94c94de2012-04-04 15:38:28 -0700516 restartOptionalLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700517 }
518
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800519 /** Set the current folder */
Mindy Pereira11e35962012-06-01 14:49:46 -0700520 private void updateFolder(Folder folder) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800521 // Start watching folder for sync status.
Mindy Pereirac975e842012-07-16 09:15:00 -0700522 boolean wasNull = mFolder == null;
Paul Westbrook4bfce9a2012-08-07 22:54:42 -0700523 if (folder != null && !folder.equals(mFolder) && folder.isInitialized()) {
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700524 LogUtils.d(LOG_TAG, "AbstractActivityController.setFolder(%s)", folder.name);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700525 final LoaderManager lm = mActivity.getLoaderManager();
Vikram Aggarwal7a5d95a2012-07-27 16:24:54 -0700526 mFolder = folder;
Mindy Pereiraf9323cd2012-02-29 13:47:09 -0800527 mActionBarView.setFolder(mFolder);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700528
529 // Only when we switch from one folder to another do we want to restart the
530 // folder and conversation list loaders (to trigger onCreateLoader).
531 // The first time this runs when the activity is [re-]initialized, we want to re-use the
532 // previous loader's instance and data upon configuration change (e.g. rotation).
Mindy Pereira11e35962012-06-01 14:49:46 -0700533 // If there was not already an instance of the loader, init it.
534 if (lm.getLoader(LOADER_FOLDER_CURSOR) == null) {
Andy Huangb1c34dc2012-04-17 16:36:19 -0700535 lm.initLoader(LOADER_FOLDER_CURSOR, null, this);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700536 } else {
537 lm.restartLoader(LOADER_FOLDER_CURSOR, null, this);
Mindy Pereirac975e842012-07-16 09:15:00 -0700538 }
539 // In this case, we are starting from no folder, which would occur
540 // the first time the app was launched or on orientation changes.
541 // We want to attach to an existing loader, if available.
542 if (wasNull || lm.getLoader(LOADER_CONVERSATION_LIST) == null) {
543 lm.initLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
544 } else {
545 // However, if there was an existing folder AND we have changed
546 // folders, we want to restart the loader to get the information
547 // for the newly selected folder
Andy Huangb1c34dc2012-04-17 16:36:19 -0700548 lm.restartLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
549 }
Paul Westbrook4bfce9a2012-08-07 22:54:42 -0700550 } else if (!folder.isInitialized()) {
Paul Westbrook3c0f4192012-08-09 12:15:42 -0700551 LogUtils.e(LOG_TAG, new Error(), "Uninitialized Folder %s in setFolder.", folder);
Mindy Pereirae0458e82012-03-06 11:54:55 -0800552 } else if (folder == null) {
553 LogUtils.wtf(LOG_TAG, "Folder in setFolder is null");
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800554 }
555 }
556
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800557 @Override
Andy Huang090db1e2012-07-25 13:25:28 -0700558 public Folder getFolder() {
559 return mFolder;
560 }
561
562 @Override
Mindy Pereirac975e842012-07-16 09:15:00 -0700563 public Folder getHierarchyFolder() {
564 return mFolderListFolder;
565 }
566
567 @Override
568 public void setHierarchyFolder(Folder folder) {
569 mFolderListFolder = folder;
Mindy Pereira23aadfd2012-05-25 11:24:33 -0700570 }
571
572 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800573 public void onActivityResult(int requestCode, int resultCode, Intent data) {
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700574 if (requestCode == ADD_ACCOUNT_REQUEST_CODE) {
575 // We were waiting for the user to create an account
576 if (resultCode == Activity.RESULT_OK) {
577 // restart the loader to get the updated list of accounts
578 mActivity.getLoaderManager().initLoader(
579 LOADER_ACCOUNT_CURSOR, null, this);
580 } else {
581 // The user failed to create an account, just exit the app
582 mActivity.finish();
583 }
584 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800585 }
586
587 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800588 public void onConversationListVisibilityChanged(boolean visible) {
Paul Westbrook9f119c72012-04-24 16:10:59 -0700589 if (mConversationListCursor != null) {
590 // The conversation list is visible.
591 Utils.setConversationCursorVisibility(mConversationListCursor, visible);
592 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800593 }
594
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800595 /**
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700596 * Called when a conversation is visible. Child classes must call the super class implementation
597 * before performing local computation.
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800598 */
599 @Override
600 public void onConversationVisibilityChanged(boolean visible) {
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800601 return;
602 }
603
604 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800605 public boolean onCreate(Bundle savedState) {
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700606 initializeActionBar();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800607 // Allow shortcut keys to function for the ActionBar and menus.
608 mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800609 mResolver = mActivity.getContentResolver();
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700610 mNewEmailReceiver = new SuppressNotificationReceiver();
611
Mindy Pereira161f50d2012-02-28 15:47:19 -0800612 // All the individual UI components listen for ViewMode changes. This
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800613 // simplifies the amount of logic in the AbstractActivityController, but increases the
614 // possibility of timing-related bugs.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800615 mViewMode.addListener(this);
Andy Huang632721e2012-04-11 16:57:26 -0700616 mPagerController = new ConversationPagerController(mActivity, this);
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700617 mToastBar = (ActionableToastBar) mActivity.findViewById(R.id.toast_bar);
Vikram Aggarwalada51782012-04-26 14:18:31 -0700618 attachActionBar();
Andy Huang632721e2012-04-11 16:57:26 -0700619
620 final Intent intent = mActivity.getIntent();
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700621 // Immediately handle a clean launch with intent, and any state restoration
Andy Huang632721e2012-04-11 16:57:26 -0700622 // that does not rely on restored fragments or loader data
623 // any state restoration that relies on those can be done later in
624 // onRestoreInstanceState, once fragments are up and loader data is re-delivered
625 if (savedState != null) {
626 if (savedState.containsKey(SAVED_ACCOUNT)) {
627 setAccount((Account) savedState.getParcelable(SAVED_ACCOUNT));
628 mActivity.invalidateOptionsMenu();
629 }
630 if (savedState.containsKey(SAVED_FOLDER)) {
Vikram Aggarwalf3341402012-08-07 10:09:38 -0700631 final Folder folder = (Folder) savedState.getParcelable(SAVED_FOLDER);
632 if (savedState.containsKey(SAVED_QUERY)) {
633 changeFolder(folder, savedState.getString(SAVED_QUERY));
634 } else {
635 onFolderChanged(folder);
636 }
Andy Huang632721e2012-04-11 16:57:26 -0700637 }
638 } else if (intent != null) {
639 handleIntent(intent);
640 }
Andy Huang632721e2012-04-11 16:57:26 -0700641 // Create the accounts loader; this loads the account switch spinner.
642 mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
Andy Huang632721e2012-04-11 16:57:26 -0700643 return true;
Andy Huangb1c34dc2012-04-17 16:36:19 -0700644 }
645
646 @Override
Andrew Sapperstein00179f12012-08-09 15:15:40 -0700647 public void onRestart() {
648 DialogFragment fragment = (DialogFragment)
649 mFragmentManager.findFragmentByTag(SYNC_ERROR_DIALOG_FRAGMENT_TAG);
650 if (fragment != null) {
651 fragment.dismiss();
652 }
653 }
654
655 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800656 public Dialog onCreateDialog(int id, Bundle bundle) {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800657 return null;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800658 }
659
660 @Override
Vikram Aggarwal4eb52712012-06-19 16:24:50 -0700661 public final boolean onCreateOptionsMenu(Menu menu) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800662 MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800663 inflater.inflate(mActionBarView.getOptionsMenuId(), menu);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800664 mActionBarView.onCreateOptionsMenu(menu);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800665 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800666 }
667
668 @Override
Vikram Aggarwal4eb52712012-06-19 16:24:50 -0700669 public final boolean onKeyDown(int keyCode, KeyEvent event) {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800670 // TODO(viki): Auto-generated method stub
671 return false;
672 }
673
674 @Override
Vikram Aggarwal4eb52712012-06-19 16:24:50 -0700675 public final boolean onOptionsItemSelected(MenuItem item) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700676 final int id = item.getItemId();
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700677 LogUtils.d(LOG_TAG, "AbstractController.onOptionsItemSelected(%d) called.", id);
Mindy Pereira9b875682012-02-15 18:10:54 -0800678 boolean handled = true;
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700679 final Collection<Conversation> target = Conversation.listOf(mCurrentConversation);
Vikram Aggarwal112cd162012-06-18 10:51:13 -0700680 final Settings settings = (mAccount == null) ? null : mAccount.settings;
Mindy Pereira8937bf12012-07-23 14:05:02 -0700681 // The user is choosing a new action; commit whatever they had been doing before.
682 commitDestructiveActions();
Mindy Pereira28d5f722012-02-15 12:32:40 -0800683 switch (id) {
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700684 case R.id.archive: {
685 final boolean showDialog = (settings != null && settings.confirmArchive);
686 confirmAndDelete(target, showDialog, R.plurals.confirm_archive_conversation,
687 getAction(R.id.archive, target));
688 break;
689 }
Mindy Pereira01f30502012-08-14 10:30:51 -0700690 case R.id.remove_folder:
691 delete(target, getRemoveFolder(target, mFolder, true, false, true));
692 break;
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700693 case R.id.delete: {
694 final boolean showDialog = (settings != null && settings.confirmDelete);
695 confirmAndDelete(target, showDialog, R.plurals.confirm_delete_conversation,
696 getAction(R.id.delete, target));
697 break;
698 }
699 case R.id.mark_important:
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700700 updateConversation(Conversation.listOf(mCurrentConversation),
701 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.HIGH);
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700702 break;
703 case R.id.mark_not_important:
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700704 updateConversation(Conversation.listOf(mCurrentConversation),
705 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700706 break;
707 case R.id.mute:
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700708 delete(target, getAction(R.id.mute, target));
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700709 break;
710 case R.id.report_spam:
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700711 delete(target, getAction(R.id.report_spam, target));
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700712 break;
Paul Westbrook77eee622012-07-10 13:41:57 -0700713 case R.id.mark_not_spam:
714 // Currently, since spam messages are only shown in list with other spam messages,
715 // marking a message not as spam is a destructive action
716 delete(target, getAction(R.id.mark_not_spam, target));
717 break;
Paul Westbrook76b20622012-07-12 11:45:43 -0700718 case R.id.report_phishing:
719 delete(target, getAction(R.id.report_phishing, target));
720 break;
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800721 case android.R.id.home:
722 onUpPressed();
723 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800724 case R.id.compose:
725 ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
726 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800727 case R.id.show_all_folders:
728 showFolderList();
729 break;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800730 case R.id.refresh:
731 requestFolderRefresh();
732 break;
Mindy Pereira1f936682012-03-02 11:30:33 -0800733 case R.id.settings:
734 Utils.showSettings(mActivity.getActivityContext(), mAccount);
Paul Westbrook2861b6a2012-02-15 15:25:34 -0800735 break;
Paul Westbrooke5503552012-03-28 00:35:57 -0700736 case R.id.folder_options:
737 Utils.showFolderSettings(mActivity.getActivityContext(), mAccount, mFolder);
738 break;
Paul Westbrook94e440d2012-02-24 11:03:47 -0800739 case R.id.help_info_menu_item:
740 // TODO: enable context sensitive help
Paul Westbrook498e76d2012-04-12 16:33:02 -0700741 Utils.showHelp(mActivity.getActivityContext(), mAccount, null);
Paul Westbrook94e440d2012-02-24 11:03:47 -0800742 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700743 case R.id.feedback_menu_item:
Mindy Pereirafbe40192012-03-20 10:40:45 -0700744 Utils.sendFeedback(mActivity.getActivityContext(), mAccount);
745 break;
Paul Westbrook18babd22012-04-09 22:17:08 -0700746 case R.id.manage_folders_item:
747 Utils.showManageFolder(mActivity.getActivityContext(), mAccount);
748 break;
Vikram Aggarwald503df42012-05-11 10:13:35 -0700749 case R.id.change_folder:
750 new FoldersSelectionDialog(mActivity.getActivityContext(), mAccount, this,
Mindy Pereiraa832d3d2012-07-27 11:19:50 -0700751 Conversation.listOf(mCurrentConversation), false, mFolder).show();
Vikram Aggarwald503df42012-05-11 10:13:35 -0700752 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800753 default:
754 handled = false;
755 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800756 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800757 return handled;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800758 }
759
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700760 @Override
Mindy Pereira6c2663d2012-07-20 15:37:29 -0700761 public void updateConversation(Collection<Conversation> target, ContentValues values) {
762 mConversationListCursor.updateValues(mContext, target, values);
763 refreshConversationList();
764 }
765
766 @Override
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700767 public void updateConversation(Collection <Conversation> target, String columnName,
768 boolean value) {
769 mConversationListCursor.updateBoolean(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700770 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700771 }
772
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700773 @Override
Mindy Pereira6c2663d2012-07-20 15:37:29 -0700774 public void updateConversation(Collection <Conversation> target, String columnName,
775 int value) {
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700776 mConversationListCursor.updateInt(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700777 refreshConversationList();
Mindy Pereirac9d59182012-03-22 16:06:46 -0700778 }
779
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700780 @Override
781 public void updateConversation(Collection <Conversation> target, String columnName,
782 String value) {
783 mConversationListCursor.updateString(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700784 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700785 }
786
Andy Huang839ada22012-07-20 15:48:40 -0700787 @Override
788 public void markConversationMessagesUnread(Conversation conv, Set<Uri> unreadMessageUris,
Vikram Aggarwal4a878b62012-07-31 15:09:25 -0700789 String originalConversationInfo) {
Andy Huang8f6b0062012-07-31 15:36:31 -0700790 // The only caller of this method is the conversation view, from where marking unread should
791 // *always* take you back to list mode.
792 showConversation(null);
793
Andy Huang839ada22012-07-20 15:48:40 -0700794 // locally mark conversation unread (the provider is supposed to propagate message unread
795 // to conversation unread)
796 conv.read = false;
797
Andy Huang28e31e22012-07-26 16:33:15 -0700798 // only do a granular 'mark unread' if a subset of messages are unread
799 final int unreadCount = (unreadMessageUris == null) ? 0 : unreadMessageUris.size();
Mindy Pereira0972e072012-08-01 17:43:06 -0700800 final int numMessages = conv.getNumMessages();
801 final boolean subsetIsUnread = (numMessages > 1 && unreadCount > 0
802 && unreadCount < numMessages);
Andy Huang28e31e22012-07-26 16:33:15 -0700803
804 if (!subsetIsUnread) {
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700805 // Conversations are neither marked read, nor viewed, and we don't want to show
806 // the next conversation.
807 markConversationsRead(Collections.singletonList(conv), false, false, false);
Andy Huang839ada22012-07-20 15:48:40 -0700808 } else {
Andy Huangdaa06ab2012-07-24 10:46:44 -0700809 mConversationListCursor.setConversationColumn(conv.uri, ConversationColumns.READ, 0);
Andy Huang839ada22012-07-20 15:48:40 -0700810
Mindy Pereira7b6d03d2012-07-30 13:03:41 -0700811 // locally update conversation's conversationInfo to revert to original version
Andy Huang28e31e22012-07-26 16:33:15 -0700812 if (originalConversationInfo != null) {
813 mConversationListCursor.setConversationColumn(conv.uri,
814 ConversationColumns.CONVERSATION_INFO, originalConversationInfo);
815 }
Andy Huang839ada22012-07-20 15:48:40 -0700816
817 // applyBatch with each CPO as an UPDATE op on each affected message uri
818 final ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
819 String authority = null;
820 for (Uri messageUri : unreadMessageUris) {
821 if (authority == null) {
822 authority = messageUri.getAuthority();
823 }
824 ops.add(ContentProviderOperation.newUpdate(messageUri)
825 .withValue(UIProvider.MessageColumns.READ, 0)
826 .build());
827 }
828
829 new ContentProviderTask() {
830 @Override
831 protected void onPostExecute(Result result) {
832 // TODO: handle errors?
833 }
834 }.run(mResolver, authority, ops);
Andy Huang839ada22012-07-20 15:48:40 -0700835 }
Andy Huang839ada22012-07-20 15:48:40 -0700836 }
837
838 @Override
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700839 public void markConversationsRead(Collection<Conversation> targets, boolean read,
840 boolean viewed) {
841 // We want to show the next conversation if we are marking unread.
842 markConversationsRead(targets, read, viewed, true);
Andy Huang8f6b0062012-07-31 15:36:31 -0700843 }
844
845 private void markConversationsRead(Collection<Conversation> targets, boolean read,
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700846 boolean markViewed, boolean showNext) {
Andy Huang8f6b0062012-07-31 15:36:31 -0700847 // auto-advance if requested and the current conversation is being marked unread
848 if (showNext && !read) {
849 showNextConversation(targets);
850 }
851
Andy Huang839ada22012-07-20 15:48:40 -0700852 for (Conversation target : targets) {
Vikram Aggarwal192fac12012-07-25 16:44:55 -0700853 final ContentValues values = new ContentValues();
Andy Huang839ada22012-07-20 15:48:40 -0700854 values.put(ConversationColumns.READ, read);
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700855 if (markViewed) {
856 values.put(ConversationColumns.VIEWED, true);
857 }
Vikram Aggarwal192fac12012-07-25 16:44:55 -0700858 final ConversationInfo info = target.conversationInfo;
Andy Huang839ada22012-07-20 15:48:40 -0700859 if (info != null) {
Mindy Pereira5f424372012-07-30 11:49:55 -0700860 info.markRead(read);
861 values.put(ConversationColumns.CONVERSATION_INFO, ConversationInfo.toString(info));
Andy Huang839ada22012-07-20 15:48:40 -0700862 }
863 updateConversation(Conversation.listOf(target), values);
864 }
865 // Update the conversations in the selection too.
866 for (final Conversation c : targets) {
867 c.read = read;
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700868 if (markViewed) {
869 c.markViewed();
870 }
Andy Huang839ada22012-07-20 15:48:40 -0700871 }
872 }
873
Andy Huang8f6b0062012-07-31 15:36:31 -0700874 /**
875 * Auto-advance to a different conversation if the currently visible conversation in
876 * conversation mode is affected (deleted, marked unread, etc.).
877 *
878 * <p>Does nothing if outside of conversation mode.
879 *
880 * @param target the set of conversations being deleted/marked unread
881 */
882 private void showNextConversation(Collection<Conversation> target) {
883 final boolean currentConversationInView = (mViewMode.getMode() == ViewMode.CONVERSATION)
884 && Conversation.contains(target, mCurrentConversation);
885 if (currentConversationInView) {
886 final Conversation next = mTracker.getNextConversation(
887 Settings.getAutoAdvanceSetting(mAccount.settings), target);
888 LogUtils.d(LOG_TAG, "showNextConversation: showing %s next.", next);
889 showConversation(next);
890 }
891 }
892
Andy Huang839ada22012-07-20 15:48:40 -0700893 @Override
894 public void starMessage(ConversationMessage msg, boolean starred) {
895 if (msg.starred == starred) {
896 return;
897 }
898
899 msg.starred = starred;
900
901 // locally propagate the change to the owning conversation
902 // (figure the provider will properly propagate the change when it commits it)
903 //
904 // when unstarring, only propagate the change if this was the only message starred
905 final boolean conversationStarred = starred || msg.isConversationStarred();
906 if (conversationStarred != msg.conversation.starred) {
907 msg.conversation.starred = conversationStarred;
Andy Huangdaa06ab2012-07-24 10:46:44 -0700908 mConversationListCursor.setConversationColumn(msg.conversation.uri,
Andy Huang839ada22012-07-20 15:48:40 -0700909 ConversationColumns.STARRED, conversationStarred);
910 }
911
912 final ContentValues values = new ContentValues(1);
913 values.put(UIProvider.MessageColumns.STARRED, starred ? 1 : 0);
914
915 new ContentProviderTask.UpdateTask() {
916 @Override
917 protected void onPostExecute(Result result) {
918 // TODO: handle errors?
919 }
920 }.run(mResolver, msg.uri, values, null /* selection*/, null /* selectionArgs */);
921 }
922
Mindy Pereira28e0c342012-02-17 15:05:13 -0800923 private void requestFolderRefresh() {
924 if (mFolder != null) {
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800925 if (mAsyncRefreshTask != null) {
926 mAsyncRefreshTask.cancel(true);
927 }
Paul Westbrook7e2a2a12012-06-27 13:52:40 -0700928 mAsyncRefreshTask = new AsyncRefreshTask(mContext, mFolder.refreshUri);
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800929 mAsyncRefreshTask.execute();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800930 }
931 }
932
Mindy Pereirafbe40192012-03-20 10:40:45 -0700933 /**
934 * Confirm (based on user's settings) and delete a conversation from the conversation list and
935 * from the database.
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700936 * @param target the conversations to act upon
937 * @param showDialog true if a confirmation dialog is to be shown, false otherwise.
938 * @param confirmResource the resource ID of the string that is shown in the confirmation dialog
939 * @param action the action to perform after animating the deletion of the conversations.
Mindy Pereirafbe40192012-03-20 10:40:45 -0700940 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700941 protected void confirmAndDelete(final Collection<Conversation> target, boolean showDialog,
942 int confirmResource, final DestructiveAction action) {
Mindy Pereirafbe40192012-03-20 10:40:45 -0700943 if (showDialog) {
944 final AlertDialog.OnClickListener onClick = new AlertDialog.OnClickListener() {
945 @Override
946 public void onClick(DialogInterface dialog, int which) {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700947 delete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -0700948 }
949 };
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700950 final CharSequence message = Utils.formatPlural(mContext, confirmResource,
951 target.size());
Mindy Pereirafbe40192012-03-20 10:40:45 -0700952 new AlertDialog.Builder(mActivity.getActivityContext()).setMessage(message)
953 .setPositiveButton(R.string.ok, onClick)
954 .setNegativeButton(R.string.cancel, null)
955 .create().show();
956 } else {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700957 delete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -0700958 }
959 }
960
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700961 @Override
962 public void delete(final Collection<Conversation> target, final DestructiveAction action) {
Vikram Aggarwal192fac12012-07-25 16:44:55 -0700963 // Order of events is critical! The Conversation View Fragment must be notified
964 // of the next conversation with showConversation(next) *before* the conversation list
965 // fragment has a chance to delete the conversation, animating it away.
966
Vikram Aggarwald503df42012-05-11 10:13:35 -0700967 // Update the conversation fragment if the current conversation is deleted.
Andy Huang8f6b0062012-07-31 15:36:31 -0700968 showNextConversation(target);
Vikram Aggarwal192fac12012-07-25 16:44:55 -0700969 // The conversation list deletes and performs the action if it exists.
970 final ConversationListFragment convListFragment = getConversationListFragment();
971 if (convListFragment != null) {
972 LogUtils.d(LOG_TAG, "AAC.requestDelete: ListFragment is handling delete.");
973 convListFragment.requestDelete(target, action);
974 return;
975 }
Vikram Aggarwald503df42012-05-11 10:13:35 -0700976 // No visible UI element handled it on our behalf. Perform the action ourself.
977 action.performAction();
978 }
979
980 /**
981 * Requests that the action be performed and the UI state is updated to reflect the new change.
982 * @param target
983 * @param action
984 */
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700985 private void requestUpdate(final Collection<Conversation> target,
Vikram Aggarwald503df42012-05-11 10:13:35 -0700986 final DestructiveAction action) {
987 action.performAction();
988 refreshConversationList();
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700989 }
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700990
991 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800992 public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
993 // TODO(viki): Auto-generated method stub
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800994 }
995
996 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800997 public boolean onPrepareOptionsMenu(Menu menu) {
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800998 mActionBarView.onPrepareOptionsMenu(menu);
Mindy Pereira363451a2012-02-22 14:14:46 -0800999 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001000 }
1001
Mindy Pereira68f2e222012-03-07 10:36:54 -08001002 @Override
1003 public void onPause() {
1004 isLoaderInitialized = false;
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001005 enableNotifications();
Paul Westbrook94e440d2012-02-24 11:03:47 -08001006 }
1007
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001008 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001009 public void onResume() {
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001010 // Register the receiver that will prevent the status receiver from
1011 // displaying its notification icon as long as we're running.
1012 // The SupressNotificationReceiver will block the broadcast if we're looking at the folder
1013 // that the notification was received for.
1014 disableNotifications();
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001015 }
1016
1017 @Override
1018 public void onSaveInstanceState(Bundle outState) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001019 if (mAccount != null) {
1020 LogUtils.d(LOG_TAG, "Saving the account now");
1021 outState.putParcelable(SAVED_ACCOUNT, mAccount);
1022 }
Mindy Pereira5e478d22012-03-26 18:04:58 -07001023 if (mFolder != null) {
1024 outState.putParcelable(SAVED_FOLDER, mFolder);
Vikram Aggarwal8b152632012-02-03 14:58:45 -08001025 }
Vikram Aggarwalf3341402012-08-07 10:09:38 -07001026 // If this is a search activity, let's store the search query term as well.
1027 if (ConversationListContext.isSearchResult(mConvListContext)) {
1028 outState.putString(SAVED_QUERY, mConvListContext.searchQuery);
1029 }
Mindy Pereira0f7ae7a2012-07-17 13:39:56 -07001030 int mode = mViewMode.getMode();
Mindy Pereira9fa43ca2012-05-17 14:18:01 -07001031 if (mCurrentConversation != null
Mindy Pereira0f7ae7a2012-07-17 13:39:56 -07001032 && (mode == ViewMode.CONVERSATION ||
Mindy Pereira9fa43ca2012-05-17 14:18:01 -07001033 mViewMode.getMode() == ViewMode.SEARCH_RESULTS_CONVERSATION)) {
Mindy Pereira26f23fc2012-03-27 10:26:04 -07001034 outState.putParcelable(SAVED_CONVERSATION, mCurrentConversation);
1035 }
Andy Huang4556a442012-03-30 16:42:05 -07001036 if (!mSelectedSet.isEmpty()) {
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001037 outState.putParcelable(SAVED_SELECTED_SET, mSelectedSet);
Andy Huang4556a442012-03-30 16:42:05 -07001038 }
Mindy Pereirad33674992012-06-25 16:26:30 -07001039 if (mToastBar.getVisibility() == View.VISIBLE) {
1040 outState.putParcelable(SAVED_TOAST_BAR_OP, mToastBar.getOperation());
1041 }
1042 ConversationListFragment convListFragment = getConversationListFragment();
1043 if (convListFragment != null) {
Andy Huang839ada22012-07-20 15:48:40 -07001044 convListFragment.getAnimatedAdapter()
Mindy Pereirad33674992012-06-25 16:26:30 -07001045 .onSaveInstanceState(outState);
1046 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001047 }
1048
1049 @Override
Mindy Pereira68f2e222012-03-07 10:36:54 -08001050 public void onSearchRequested(String query) {
1051 Intent intent = new Intent();
1052 intent.setAction(Intent.ACTION_SEARCH);
1053 intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query);
1054 intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount);
1055 intent.setComponent(mActivity.getComponentName());
Vikram Aggarwalb17cbc02012-04-06 15:41:46 -07001056 mActionBarView.collapseSearch();
Mindy Pereira68f2e222012-03-07 10:36:54 -08001057 mActivity.startActivity(intent);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001058 }
1059
1060 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001061 public void onStop() {
Mindy Pereira49e5dbe2012-07-12 11:47:54 -07001062 if (mEnableShareIntents != null) {
1063 mEnableShareIntents.cancel(true);
1064 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001065 }
1066
Andy Huang632721e2012-04-11 16:57:26 -07001067 @Override
1068 public void onDestroy() {
1069 // unregister the ViewPager's observer on the conversation cursor
1070 mPagerController.onDestroy();
Mindy Pereira641de652012-08-02 15:21:50 -07001071 mActionBarView.onDestroy();
Andy Huang4e0158f2012-08-07 21:06:01 -07001072
1073 mDestroyed = true;
Andy Huang632721e2012-04-11 16:57:26 -07001074 }
1075
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001076 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -08001077 * {@inheritDoc} Subclasses must override this to listen to mode changes
1078 * from the ViewMode. Subclasses <b>must</b> call the parent's
1079 * onViewModeChanged since the parent will handle common state changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001080 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001081 @Override
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001082 public void onViewModeChanged(int newMode) {
1083 // Perform any mode specific work here.
Mindy Pereira161f50d2012-02-28 15:47:19 -08001084 // reset the action bar icon based on the mode. Why don't the individual
1085 // controllers do
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001086 // this themselves?
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001087
Mindy Pereira8937bf12012-07-23 14:05:02 -07001088 // Commit any destructive undoable actions the user may have performed.
1089 commitDestructiveActions();
1090
Mindy Pereira161f50d2012-02-28 15:47:19 -08001091 // We don't want to invalidate the options menu when switching to
1092 // conversation
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001093 // mode, as it will happen when the conversation finishes loading.
1094 if (newMode != ViewMode.CONVERSATION) {
1095 mActivity.invalidateOptionsMenu();
1096 }
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001097 }
1098
Andy Huang4e0158f2012-08-07 21:06:01 -07001099 public boolean isDestroyed() {
1100 return mDestroyed;
1101 }
1102
Mindy Pereira8937bf12012-07-23 14:05:02 -07001103 private void commitDestructiveActions() {
1104 ConversationListFragment fragment = this.getConversationListFragment();
Mindy Pereira1e2573b2012-04-17 14:34:36 -07001105 if (fragment != null) {
Mindy Pereira8937bf12012-07-23 14:05:02 -07001106 fragment.commitDestructiveActions();
Mindy Pereira1e2573b2012-04-17 14:34:36 -07001107 }
1108 }
1109
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001110 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001111 public void onWindowFocusChanged(boolean hasFocus) {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001112 ConversationListFragment convList = getConversationListFragment();
1113 if (hasFocus && convList != null && convList.isVisible()) {
1114 // The conversation list is visible.
1115 Utils.setConversationCursorVisibility(mConversationListCursor, true);
1116 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001117 }
1118
Mindy Pereira75181e82012-04-18 08:17:13 -07001119 private void setAccount(Account account) {
Andy Huangb9ca9792012-05-18 15:31:49 -07001120 if (account == null) {
1121 LogUtils.w(LOG_TAG, new Error(),
1122 "AAC ignoring null (presumably invalid) account restoration");
1123 return;
1124 }
Andy Huangb1148412012-05-19 00:16:30 -07001125 LogUtils.d(LOG_TAG, "AbstractActivityController.setAccount(): account = %s", account.uri);
Vikram Aggarwal91e87372012-05-18 15:36:04 -07001126 mAccount = account;
Mindy Pereira75181e82012-04-18 08:17:13 -07001127 mActionBarView.setAccount(mAccount);
Vikram Aggarwal91e87372012-05-18 15:36:04 -07001128 if (account.settings == null) {
1129 LogUtils.w(LOG_TAG, new Error(), "AAC ignoring account with null settings.");
1130 return;
1131 }
Vikram Aggarwal60069912012-07-24 14:26:09 -07001132 notifySettingsChanged();
Mindy Pereira75181e82012-04-18 08:17:13 -07001133 }
1134
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001135 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -08001136 * Restore the state from the previous bundle. Subclasses should call this
1137 * method from the parent class, since it performs important UI
1138 * initialization.
1139 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001140 * @param savedState
1141 */
Andy Huang632721e2012-04-11 16:57:26 -07001142 @Override
1143 public void onRestoreInstanceState(Bundle savedState) {
1144 LogUtils.d(LOG_TAG, "IN AAC.onRestoreInstanceState");
1145 if (savedState.containsKey(SAVED_CONVERSATION)) {
1146 // Open the conversation.
Paul Westbrook534e4a22012-04-25 03:46:29 -07001147 final Conversation conversation =
1148 (Conversation)savedState.getParcelable(SAVED_CONVERSATION);
1149 if (conversation != null && conversation.position < 0) {
1150 // Set the position to 0 on this conversation, as we don't know where it is
1151 // in the list
1152 conversation.position = 0;
1153 }
Andy Huanged4fdf02012-07-26 17:12:50 -07001154 showConversation(conversation);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001155 }
Mindy Pereira967ede62012-03-22 09:29:09 -07001156
Mindy Pereirad33674992012-06-25 16:26:30 -07001157 if (savedState.containsKey(SAVED_TOAST_BAR_OP)) {
1158 ToastBarOperation op = ((ToastBarOperation) savedState
1159 .getParcelable(SAVED_TOAST_BAR_OP));
1160 if (op != null) {
1161 if (op.getType() == ToastBarOperation.UNDO) {
1162 onUndoAvailable(op);
Andrew Sapperstein9d7519d2012-07-16 14:03:53 -07001163 } else if (op.getType() == ToastBarOperation.ERROR) {
1164 onError(mFolder, true);
Mindy Pereirad33674992012-06-25 16:26:30 -07001165 }
1166 }
1167 }
Mindy Pereira0f7ae7a2012-07-17 13:39:56 -07001168
Mindy Pereirad33674992012-06-25 16:26:30 -07001169 ConversationListFragment convListFragment = getConversationListFragment();
1170 if (convListFragment != null) {
Andy Huang839ada22012-07-20 15:48:40 -07001171 convListFragment.getAnimatedAdapter()
Mindy Pereirad33674992012-06-25 16:26:30 -07001172 .onRestoreInstanceState(savedState);
1173 }
Mindy Pereira967ede62012-03-22 09:29:09 -07001174 /**
1175 * Restore the state of selected conversations. This needs to be done after the correct mode
1176 * is set and the action bar is fully initialized. If not, several key pieces of state
1177 * information will be missing, and the split views may not be initialized correctly.
1178 * @param savedState
1179 */
Andy Huang4556a442012-03-30 16:42:05 -07001180 restoreSelectedConversations(savedState);
Andy Huang632721e2012-04-11 16:57:26 -07001181 }
1182
1183 private void handleIntent(Intent intent) {
1184 boolean handled = false;
1185 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
1186 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
Andy Huang632721e2012-04-11 16:57:26 -07001187 setAccount(Account.newinstance(intent
Mindy Pereira5ad02912012-07-09 09:57:18 -07001188 .getStringExtra(Utils.EXTRA_ACCOUNT)));
Andy Huang632721e2012-04-11 16:57:26 -07001189 }
Andy Huangb9ca9792012-05-18 15:31:49 -07001190 if (mAccount == null) {
1191 return;
Andy Huang632721e2012-04-11 16:57:26 -07001192 }
Andy Huangb9ca9792012-05-18 15:31:49 -07001193 mActivity.invalidateOptionsMenu();
Vikram Aggarwal1a249e02012-08-03 16:19:33 -07001194 final boolean isConversationMode = intent.hasExtra(Utils.EXTRA_CONVERSATION);
1195 // TODO(viki): Allow the controller to set the mode instead of a mode transition.
1196 if (isConversationMode) {
1197 mViewMode.enterConversationMode();
1198 } else {
1199 mViewMode.enterConversationListMode();
1200 }
Andy Huang632721e2012-04-11 16:57:26 -07001201
1202 Folder folder = null;
1203 if (intent.hasExtra(Utils.EXTRA_FOLDER)) {
1204 // Open the folder.
Mindy Pereira7b6d03d2012-07-30 13:03:41 -07001205 folder = Folder.fromString(intent.getStringExtra(Utils.EXTRA_FOLDER));
Andy Huang632721e2012-04-11 16:57:26 -07001206 }
1207 if (folder != null) {
1208 onFolderChanged(folder);
1209 handled = true;
1210 }
1211
Vikram Aggarwal1a249e02012-08-03 16:19:33 -07001212 if (isConversationMode) {
Andy Huang632721e2012-04-11 16:57:26 -07001213 // Open the conversation.
1214 LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
1215 intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
Paul Westbrooka9161912012-04-24 10:10:04 -07001216 final Conversation conversation =
1217 (Conversation)intent.getParcelableExtra(Utils.EXTRA_CONVERSATION);
1218 if (conversation != null && conversation.position < 0) {
1219 // Set the position to 0 on this conversation, as we don't know where it is
1220 // in the list
1221 conversation.position = 0;
1222 }
Andy Huang980aaea2012-07-26 17:22:19 -07001223 showConversation(conversation);
Andy Huang632721e2012-04-11 16:57:26 -07001224 handled = true;
1225 }
1226
1227 if (!handled) {
1228 // Nothing was saved; just load the account inbox.
1229 loadAccountInbox();
1230 }
1231 } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
1232 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
1233 // Save this search query for future suggestions.
1234 final String query = intent.getStringExtra(SearchManager.QUERY);
1235 final String authority = mContext.getString(R.string.suggestions_authority);
1236 SearchRecentSuggestions suggestions = new SearchRecentSuggestions(
1237 mContext, authority, SuggestionsProvider.MODE);
1238 suggestions.saveRecentQuery(query, null);
Mindy Pereiraac254822012-06-18 10:46:43 -07001239 if (Utils.showTwoPaneSearchResults(mActivity.getActivityContext())) {
1240 mViewMode.enterSearchResultsConversationMode();
1241 } else {
1242 mViewMode.enterSearchResultsListMode();
1243 }
Andy Huang632721e2012-04-11 16:57:26 -07001244 setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
1245 mActivity.invalidateOptionsMenu();
1246 restartOptionalLoader(LOADER_RECENT_FOLDERS);
1247 mRecentFolderList.setCurrentAccount(mAccount);
1248 fetchSearchFolder(intent);
1249 } else {
1250 LogUtils.e(LOG_TAG, "Missing account extra from search intent. Finishing");
1251 mActivity.finish();
1252 }
1253 }
1254 if (mAccount != null) {
1255 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1256 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001257 }
1258
Andy Huang4556a442012-03-30 16:42:05 -07001259 /**
1260 * Copy any selected conversations stored in the saved bundle into our selection set,
1261 * triggering {@link ConversationSetObserver} callbacks as our selection set changes.
1262 *
1263 */
Vikram Aggarwal4eb52712012-06-19 16:24:50 -07001264 private final void restoreSelectedConversations(Bundle savedState) {
Mindy Pereira967ede62012-03-22 09:29:09 -07001265 if (savedState == null) {
Andy Huang4556a442012-03-30 16:42:05 -07001266 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001267 return;
1268 }
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001269 final ConversationSelectionSet selectedSet = savedState.getParcelable(SAVED_SELECTED_SET);
Andy Huang4556a442012-03-30 16:42:05 -07001270 if (selectedSet == null || selectedSet.isEmpty()) {
1271 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001272 return;
1273 }
Andy Huang632721e2012-04-11 16:57:26 -07001274
1275 // putAll will take care of calling our registered onSetPopulated method
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001276 mSelectedSet.putAll(selectedSet);
Mindy Pereira967ede62012-03-22 09:29:09 -07001277 }
1278
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001279 @Override
Andy Huang5895f7b2012-06-01 17:07:20 -07001280 public SubjectDisplayChanger getSubjectDisplayChanger() {
1281 return mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001282 }
1283
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001284 /**
1285 * Children can override this method, but they must call super.showConversation().
1286 * {@inheritDoc}
1287 */
1288 @Override
1289 public void showConversation(Conversation conversation) {
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001290 // Set the current conversation just in case it wasn't already set.
1291 setCurrentConversation(conversation);
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001292 }
1293
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001294 /**
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001295 * Children can override this method, but they must call super.showWaitForInitialization().
1296 * {@inheritDoc}
1297 */
1298 @Override
1299 public void showWaitForInitialization() {
1300 mViewMode.enterWaitingForInitializationMode();
1301 }
1302
1303 @Override
1304 public void hideWaitForInitialization() {
1305 }
1306
1307 @Override
1308 public void updateWaitMode() {
1309 final FragmentManager manager = mActivity.getFragmentManager();
1310 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001311 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001312 if (waitFragment != null) {
1313 waitFragment.updateAccount(mAccount);
1314 }
1315 }
1316
Vikram Aggarwal48b2a6c2012-05-29 14:09:27 -07001317 /**
1318 * Returns true if we are waiting for the account to sync, and cannot show any folders or
1319 * conversation for the current account yet.
Andy Huang839ada22012-07-20 15:48:40 -07001320 *
Vikram Aggarwal48b2a6c2012-05-29 14:09:27 -07001321 */
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001322 public boolean inWaitMode() {
1323 final FragmentManager manager = mActivity.getFragmentManager();
1324 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001325 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001326 if (waitFragment != null) {
1327 final Account fragmentAccount = waitFragment.getAccount();
1328 return fragmentAccount.uri.equals(mAccount.uri) &&
1329 mViewMode.getMode() == ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION;
1330 }
1331 return false;
1332 }
1333
1334 /**
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001335 * Children can override this method, but they must call super.showConversationList().
1336 * {@inheritDoc}
1337 */
1338 @Override
1339 public void showConversationList(ConversationListContext listContext) {
1340 }
1341
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001342 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -08001343 public void onConversationSelected(Conversation conversation) {
Vikram Aggarwal5b7b3ab2012-04-03 15:43:55 -07001344 showConversation(conversation);
Mindy Pereira9fa43ca2012-05-17 14:18:01 -07001345 if (Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())) {
Mindy Pereira68f2e222012-03-07 10:36:54 -08001346 mViewMode.enterSearchResultsConversationMode();
1347 } else {
1348 mViewMode.enterConversationMode();
1349 }
Vikram Aggarwal7d602882012-02-07 15:01:20 -08001350 }
Mindy Pereira555140c2012-02-15 14:55:29 -08001351
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001352 /**
1353 * Set the current conversation. This is the conversation on which all actions are performed.
1354 * Do not modify mCurrentConversation except through this method, which makes it easy to
1355 * perform common actions associated with changing the current conversation.
1356 * @param conversation
1357 */
Andy Huang632721e2012-04-11 16:57:26 -07001358 @Override
1359 public void setCurrentConversation(Conversation conversation) {
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001360 mCurrentConversation = conversation;
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001361 mTracker.initialize(mCurrentConversation);
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001362 }
1363
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001364 /**
1365 * {@inheritDoc}
1366 */
Mindy Pereira555140c2012-02-15 14:55:29 -08001367 @Override
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001368 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
1369 // Create a loader to listen in on account changes.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001370 switch (id) {
1371 case LOADER_ACCOUNT_CURSOR:
Paul Westbrookc2074c42012-03-22 15:26:58 -07001372 return new CursorLoader(mContext, MailAppProvider.getAccountsUri(),
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001373 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
1374 case LOADER_FOLDER_CURSOR:
Paul Westbrookc7a070f2012-04-12 01:46:41 -07001375 final CursorLoader loader = new CursorLoader(mContext, mFolder.uri,
1376 UIProvider.FOLDERS_PROJECTION, null, null, null);
1377 loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
1378 return loader;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001379 case LOADER_RECENT_FOLDERS:
Paul Westbrook91d10502012-04-13 12:01:39 -07001380 if (mAccount != null && mAccount.recentFolderListUri != null) {
Paul Westbrookea4ee202012-03-12 14:12:33 -07001381 return new CursorLoader(mContext, mAccount.recentFolderListUri,
1382 UIProvider.FOLDERS_PROJECTION, null, null, null);
1383 }
1384 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001385 case LOADER_ACCOUNT_INBOX:
Vikram Aggarwal025eba82012-05-08 10:45:30 -07001386 final Uri defaultInbox = Settings.getDefaultInboxUri(mAccount.settings);
Vikram Aggarwal1e57e672012-05-07 14:48:24 -07001387 final Uri inboxUri = defaultInbox.equals(Uri.EMPTY) ?
1388 mAccount.folderListUri : defaultInbox;
Paul Westbrook7496e822012-04-24 09:50:54 -07001389 LogUtils.d(LOG_TAG, "Loading the default inbox: %s", inboxUri);
Paul Westbrook1220b6d2012-04-10 00:48:00 -07001390 if (inboxUri != null) {
1391 return new CursorLoader(mContext, inboxUri, UIProvider.FOLDERS_PROJECTION, null,
1392 null, null);
1393 }
1394 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001395 case LOADER_SEARCH:
1396 return Folder.forSearchResults(mAccount,
1397 args.getString(ConversationListContext.EXTRA_SEARCH_QUERY),
1398 mActivity.getActivityContext());
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001399 case LOADER_ACCOUNT_UPDATE_CURSOR:
1400 return new CursorLoader(mContext, mAccount.uri, UIProvider.ACCOUNTS_PROJECTION,
1401 null, null, null);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001402 default:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001403 LogUtils.wtf(LOG_TAG, "Loader returned unexpected id: %d", id);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001404 }
1405 return null;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001406 }
1407
Paul Westbrookb1f573c2012-04-06 11:38:28 -07001408 @Override
1409 public void onLoaderReset(Loader<Cursor> loader) {
1410
1411 }
1412
Andy Huangf9a73482012-03-13 15:54:02 -07001413 /**
1414 * {@link LoaderManager} currently has a bug in
1415 * {@link LoaderManager#restartLoader(int, Bundle, android.app.LoaderManager.LoaderCallbacks)}
1416 * where, if a previous onCreateLoader returned a null loader, this method will NPE. Work around
1417 * this bug by destroying any loaders that may have been created as null (essentially because
1418 * they are optional loads, and may not apply to a particular account).
1419 * <p>
1420 * A simple null check before restarting a loader will not work, because that would not
1421 * give the controller a chance to invalidate UI corresponding the prior loader result.
1422 *
1423 * @param id loader ID to safely restart
Andy Huangf9a73482012-03-13 15:54:02 -07001424 */
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001425 private void restartOptionalLoader(int id) {
Andy Huangf9a73482012-03-13 15:54:02 -07001426 final LoaderManager lm = mActivity.getLoaderManager();
1427 lm.destroyLoader(id);
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001428 lm.restartLoader(id, Bundle.EMPTY, this);
1429 }
1430
Andy Huang632721e2012-04-11 16:57:26 -07001431 @Override
1432 public void registerConversationListObserver(DataSetObserver observer) {
1433 mConversationListObservable.registerObserver(observer);
1434 }
1435
1436 @Override
1437 public void unregisterConversationListObserver(DataSetObserver observer) {
1438 mConversationListObservable.unregisterObserver(observer);
1439 }
1440
Andy Huang090db1e2012-07-25 13:25:28 -07001441 @Override
1442 public void registerFolderObserver(DataSetObserver observer) {
1443 mFolderObservable.registerObserver(observer);
1444 }
1445
1446 @Override
1447 public void unregisterFolderObserver(DataSetObserver observer) {
1448 mFolderObservable.unregisterObserver(observer);
1449 }
1450
Vikram Aggarwal60069912012-07-24 14:26:09 -07001451 /**
1452 * Returns true if the number of accounts is different, or if the current account has been
1453 * removed from the device
1454 * @param accountCursor
1455 * @return
1456 */
Paul Westbrook23b74b92012-02-29 11:36:12 -08001457 private boolean accountsUpdated(Cursor accountCursor) {
1458 // Check to see if the current account hasn't been set, or the account cursor is empty
1459 if (mAccount == null || !accountCursor.moveToFirst()) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001460 return true;
Paul Westbrook23b74b92012-02-29 11:36:12 -08001461 }
1462
1463 // Check to see if the number of accounts are different, from the number we saw on the last
1464 // updated
1465 if (mCurrentAccountUris.size() != accountCursor.getCount()) {
1466 return true;
1467 }
1468
1469 // Check to see if the account list is different or if the current account is not found in
1470 // the cursor.
1471 boolean foundCurrentAccount = false;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001472 do {
Paul Westbrook23b74b92012-02-29 11:36:12 -08001473 final Uri accountUri =
1474 Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
1475 if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
1476 foundCurrentAccount = true;
1477 }
Vikram Aggarwal60069912012-07-24 14:26:09 -07001478 // Is there a new account that we do not know about?
Paul Westbrook23b74b92012-02-29 11:36:12 -08001479 if (!mCurrentAccountUris.contains(accountUri)) {
1480 return true;
1481 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001482 } while (accountCursor.moveToNext());
Paul Westbrook23b74b92012-02-29 11:36:12 -08001483
1484 // As long as we found the current account, the list hasn't been updated
1485 return !foundCurrentAccount;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001486 }
1487
1488 /**
Vikram Aggarwal60069912012-07-24 14:26:09 -07001489 * Updates accounts for the app. If the current account is missing, the first
1490 * account in the list is set to the current account (we <em>have</em> to choose something).
Mindy Pereira161f50d2012-02-28 15:47:19 -08001491 *
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001492 * @param accounts cursor into the AccountCache
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001493 * @return true if the update was successful, false otherwise
1494 */
Vikram Aggarwal60069912012-07-24 14:26:09 -07001495 private boolean updateAccounts(Cursor accounts) {
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001496 if (accounts == null || !accounts.moveToFirst()) {
1497 return false;
1498 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001499
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001500 final Account[] allAccounts = Account.getAllAccounts(accounts);
Vikram Aggarwal60069912012-07-24 14:26:09 -07001501 // A match for the current account's URI in the list of accounts.
1502 Account currentFromList = null;
Paul Westbrook23b74b92012-02-29 11:36:12 -08001503
1504 // Save the uris for the accounts
1505 mCurrentAccountUris.clear();
1506 for (Account account : allAccounts) {
Vikram Aggarwal60069912012-07-24 14:26:09 -07001507 LogUtils.d(LOG_TAG, "updateAccounts(%s)", account);
Paul Westbrook23b74b92012-02-29 11:36:12 -08001508 mCurrentAccountUris.add(account.uri);
Vikram Aggarwal60069912012-07-24 14:26:09 -07001509 if (mAccount != null && account.uri.equals(mAccount.uri)) {
1510 currentFromList = account;
1511 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001512 }
1513
Vikram Aggarwal60069912012-07-24 14:26:09 -07001514 // 1. current account is already set and is in allAccounts:
1515 // 1a. It has changed -> load the updated account.
1516 // 2b. It is unchanged -> no-op
Andy Huang0d647352012-03-21 21:48:16 -07001517 // 2. current account is set and is not in allAccounts -> pick first (acct was deleted?)
Vikram Aggarwal60069912012-07-24 14:26:09 -07001518 // 3. saved preference has an account -> pick that one
Andy Huang0d647352012-03-21 21:48:16 -07001519 // 4. otherwise just pick first
1520
Vikram Aggarwal60069912012-07-24 14:26:09 -07001521 boolean accountChanged = false;
1522 /// Assume case 4, initialize to first account, and see if we can find anything better.
1523 Account newAccount = allAccounts[0];
1524 if (currentFromList != null) {
1525 // Case 1: Current account exists but has changed
1526 if (!currentFromList.equals(mAccount)) {
1527 newAccount = currentFromList;
1528 accountChanged = true;
Andy Huang0d647352012-03-21 21:48:16 -07001529 }
Vikram Aggarwal60069912012-07-24 14:26:09 -07001530 // Case 1b: else, current account is unchanged: nothing to do.
Paul Westbrook23b74b92012-02-29 11:36:12 -08001531 } else {
Vikram Aggarwal60069912012-07-24 14:26:09 -07001532 // Case 2: Current account is not in allAccounts, the account needs to change.
1533 accountChanged = true;
1534 if (mAccount == null) {
1535 // Case 3: Check for last viewed account, and check if it exists in the list.
1536 final String lastAccountUri = MailAppProvider.getInstance().getLastViewedAccount();
1537 if (lastAccountUri != null) {
1538 for (final Account account : allAccounts) {
1539 if (lastAccountUri.equals(account.uri.toString())) {
1540 newAccount = account;
1541 break;
1542 }
Andy Huang0d647352012-03-21 21:48:16 -07001543 }
1544 }
1545 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001546 }
Vikram Aggarwal60069912012-07-24 14:26:09 -07001547 if (accountChanged) {
1548 onAccountChanged(newAccount);
1549 }
1550 // Whether we have updated the current account or not, we need to update the list of
1551 // accounts in the ActionBar.
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001552 mActionBarView.setAccounts(allAccounts);
1553 return (allAccounts.length > 0);
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001554 }
1555
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001556 private void disableNotifications() {
1557 mNewEmailReceiver.activate(mContext, this);
1558 }
1559
1560 private void enableNotifications() {
1561 mNewEmailReceiver.deactivate();
1562 }
1563
1564 private void disableNotificationsOnAccountChange(Account account) {
1565 // If the new mail suppression receiver is activated for a different account, we want to
1566 // activate it for the new account.
1567 if (mNewEmailReceiver.activated() &&
1568 !mNewEmailReceiver.notificationsDisabledForAccount(account)) {
1569 // Deactivate the current receiver, otherwise multiple receivers may be registered.
1570 mNewEmailReceiver.deactivate();
1571 mNewEmailReceiver.activate(mContext, this);
1572 }
1573 }
1574
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001575 /**
1576 * {@inheritDoc}
1577 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001578 @Override
1579 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001580 // We want to reinitialize only if we haven't ever been initialized, or
1581 // if the current account has vanished.
Paul Westbrooke3e84292012-03-05 16:19:30 -08001582 if (data == null) {
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001583 LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
Paul Westbrooke3e84292012-03-05 16:19:30 -08001584 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001585 switch (loader.getId()) {
1586 case LOADER_ACCOUNT_CURSOR:
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -07001587 // If the account list is not null, and the account list cursor is empty,
Paul Westbrook2388c5d2012-03-25 12:29:11 -07001588 // we need to start the specified activity.
1589 if (data != null && data.getCount() == 0) {
1590 // If an empty cursor is returned, the MailAppProvider is indicating that
1591 // no accounts have been specified. We want to navigate to the "add account"
1592 // activity that will handle the intent returned by the MailAppProvider
1593
1594 // If the MailAppProvider believes that all accounts have been loaded, and the
1595 // account list is still empty, we want to prompt the user to add an account
1596 final Bundle extras = data.getExtras();
1597 final boolean accountsLoaded =
1598 extras.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
1599
1600 if (accountsLoaded) {
1601 final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(mContext);
1602 if (noAccountIntent != null) {
1603 mActivity.startActivityForResult(noAccountIntent,
1604 ADD_ACCOUNT_REQUEST_CODE);
1605 }
1606 }
1607 } else {
1608 final boolean accountListUpdated = accountsUpdated(data);
1609 if (!isLoaderInitialized || accountListUpdated) {
Vikram Aggarwal60069912012-07-24 14:26:09 -07001610 isLoaderInitialized = updateAccounts(data);
Paul Westbrook2388c5d2012-03-25 12:29:11 -07001611 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001612 }
1613 break;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001614 case LOADER_ACCOUNT_UPDATE_CURSOR:
1615 // We have gotten an update for current account.
1616
Vikram Aggarwal60069912012-07-24 14:26:09 -07001617 // Make sure that this is an update for the current account
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001618 if (data != null && data.moveToFirst()) {
1619 final Account updatedAccount = new Account(data);
1620
1621 if (updatedAccount.uri.equals(mAccount.uri)) {
Paul Westbrookca08fc12012-07-31 12:01:15 -07001622 // Keep a reference to the previous settings object
1623 final Settings previousSettings = mAccount.settings;
1624
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001625 // Update the controller's reference to the current account
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001626 mAccount = updatedAccount;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -07001627 LogUtils.d(LOG_TAG, "AbstractActivityController.onLoadFinished(): "
1628 + "mAccount = %s", mAccount.uri);
Paul Westbrookca08fc12012-07-31 12:01:15 -07001629
1630 // Only notify about a settings change if something differs
1631 if (!Objects.equal(mAccount.settings, previousSettings)) {
1632 notifySettingsChanged();
1633 }
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001634
1635 // Got an update for the current account
1636 final boolean inWaitingMode = inWaitMode();
1637 if (!updatedAccount.isAccountIntialized() && !inWaitingMode) {
1638 // Transition to waiting mode
1639 showWaitForInitialization();
Mindy Pereira830bdaf2012-07-11 12:59:55 -07001640 } else if (updatedAccount.isAccountIntialized()) {
1641 if (inWaitingMode) {
1642 // Dismiss waiting mode
1643 hideWaitForInitialization();
1644 }
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001645 } else if (!updatedAccount.isAccountIntialized() && inWaitingMode) {
1646 // Update the WaitFragment's account object
1647 updateWaitMode();
1648 }
1649 } else {
1650 LogUtils.e(LOG_TAG, "Got update for account: %s with current account: %s",
1651 updatedAccount.uri, mAccount.uri);
1652 // We need to restart the loader, so the correct account information will
1653 // be returned
1654 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1655 }
1656 }
1657 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001658 case LOADER_FOLDER_CURSOR:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001659 // Check status of the cursor.
Marc Blankfd9d0b82012-04-23 16:01:51 -07001660 if (data != null && data.moveToFirst()) {
Andy Huang090db1e2012-07-25 13:25:28 -07001661 final Folder folder = new Folder(data);
Marc Blankfd9d0b82012-04-23 16:01:51 -07001662 LogUtils.d(LOG_TAG, "FOLDER STATUS = %d", folder.syncStatus);
Andy Huang090db1e2012-07-25 13:25:28 -07001663
1664 mFolder = folder;
1665 mFolderObservable.notifyChanged();
1666
Paul Westbrookc808fac2012-02-22 16:42:18 -08001667 } else {
Marc Blankfd9d0b82012-04-23 16:01:51 -07001668 LogUtils.d(LOG_TAG, "Unable to get the folder %s",
1669 mFolder != null ? mAccount.name : "");
Mindy Pereira11dd5ef2012-03-10 15:10:18 -08001670 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001671 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001672 case LOADER_RECENT_FOLDERS:
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -07001673 // No recent folders and we are running on a phone? Populate the default recents.
1674 if (data != null && data.getCount() == 0 && !Utils.useTabletUI(mContext)) {
1675 final class PopulateDefault extends AsyncTask<Uri, Void, Void> {
1676 @Override
1677 protected Void doInBackground(Uri... uri) {
1678 // Asking for an update on the URI and ignore the result.
1679 final ContentResolver resolver = mContext.getContentResolver();
1680 resolver.update(uri[0], null, null, null);
1681 return null;
1682 }
1683 }
1684 final Uri uri = mAccount.defaultRecentFolderListUri;
1685 LogUtils.v(LOG_TAG, "Default recents at %s", uri);
1686 new PopulateDefault().execute(uri);
1687 break;
1688 }
1689 LogUtils.v(LOG_TAG, "Reading recent folders from the cursor.");
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001690 mRecentFolderList.loadFromUiProvider(data);
Vikram Aggarwal37263972012-04-17 15:51:14 -07001691 mActionBarView.requestRecentFoldersAndRedraw();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001692 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001693 case LOADER_ACCOUNT_INBOX:
Marc Blankfd9d0b82012-04-23 16:01:51 -07001694 if (data != null && !data.isClosed() && data.moveToFirst()) {
Mindy Pereira0e88e9f2012-03-25 13:47:41 -07001695 Folder inbox = new Folder(data);
1696 onFolderChanged(inbox);
Mindy Pereirab4a43282012-03-23 16:20:03 -07001697 // Just want to get the inbox, don't care about updates to it
1698 // as this will be tracked by the folder change listener.
1699 mActivity.getLoaderManager().destroyLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereira5ba33802012-03-26 16:30:11 -07001700 } else {
1701 LogUtils.d(LOG_TAG, "Unable to get the account inbox for account %s",
1702 mAccount != null ? mAccount.name : "");
Mindy Pereirab4a43282012-03-23 16:20:03 -07001703 }
Mindy Pereiraab486362012-03-21 18:18:53 -07001704 break;
1705 case LOADER_SEARCH:
1706 data.moveToFirst();
1707 Folder search = new Folder(data);
Mindy Pereira11e35962012-06-01 14:49:46 -07001708 updateFolder(search);
Mindy Pereiraab486362012-03-21 18:18:53 -07001709 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder,
Mindy Pereirad660d252012-03-26 11:48:43 -07001710 mActivity.getIntent()
1711 .getStringExtra(UIProvider.SearchQueryParameters.QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -07001712 showConversationList(mConvListContext);
1713 mActivity.invalidateOptionsMenu();
Mindy Pereira3b399222012-03-28 15:19:47 -07001714 mActivity.getLoaderManager().destroyLoader(LOADER_SEARCH);
Mindy Pereiraab486362012-03-21 18:18:53 -07001715 break;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001716 }
1717 }
1718
Vikram Aggarwalc7694222012-04-23 13:37:01 -07001719 /**
1720 * Destructive actions on Conversations. This class should only be created by controllers, and
1721 * clients should only require {@link DestructiveAction}s, not specific implementations of the.
1722 * Only the controllers should know what kind of destructive actions are being created.
1723 */
Mindy Pereirade3e74a2012-07-24 09:43:10 -07001724 public class ConversationAction implements DestructiveAction {
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001725 /**
1726 * The action to be performed. This is specified as the resource ID of the menu item
1727 * corresponding to this action: R.id.delete, R.id.report_spam, etc.
1728 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001729 private final int mAction;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001730 /** The action will act upon these conversations */
Paul Westbrook77eee622012-07-10 13:41:57 -07001731 private final Collection<Conversation> mTarget;
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001732 /** Whether this destructive action has already been performed */
1733 private boolean mCompleted;
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001734 /** Whether this is an action on the currently selected set. */
1735 private final boolean mIsSelectedSet;
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001736
Mindy Pereirafbe40192012-03-20 10:40:45 -07001737 /**
1738 * Create a listener object. action is one of four constants: R.id.y_button (archive),
1739 * R.id.delete , R.id.mute, and R.id.report_spam.
1740 * @param action
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001741 * @param target Conversation that we want to apply the action to.
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001742 * @param isBatch whether the conversations are in the currently selected batch set.
1743 */
1744 public ConversationAction(int action, Collection<Conversation> target, boolean isBatch) {
Mindy Pereirafbe40192012-03-20 10:40:45 -07001745 mAction = action;
Paul Westbrook77eee622012-07-10 13:41:57 -07001746 mTarget = ImmutableList.copyOf(target);
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001747 mIsSelectedSet = isBatch;
Mindy Pereirafbe40192012-03-20 10:40:45 -07001748 }
1749
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001750 /**
1751 * The action common to child classes. This performs the action specified in the constructor
1752 * on the conversations given here.
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001753 */
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001754 @Override
1755 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001756 if (isPerformed()) {
1757 return;
1758 }
Mindy Pereira3cb28b52012-05-24 15:26:39 -07001759 boolean undoEnabled = mAccount.supportsCapability(AccountCapabilities.UNDO);
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001760
1761 // Are we destroying the currently shown conversation? Show the next one.
1762 if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)){
Vikram Aggarwal8742a612012-08-13 10:22:50 -07001763 LogUtils.d(LOG_TAG, "ConversationAction.performAction():"
1764 + "\nmTarget=%s\nCurrent=%s",
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001765 Conversation.toString(mTarget), mCurrentConversation);
1766 }
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001767
Mindy Pereirafbe40192012-03-20 10:40:45 -07001768 switch (mAction) {
Mindy Pereira0692baf2012-03-23 17:34:31 -07001769 case R.id.archive:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001770 LogUtils.d(LOG_TAG, "Archiving");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001771 mConversationListCursor.archive(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001772 break;
1773 case R.id.delete:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001774 LogUtils.d(LOG_TAG, "Deleting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001775 mConversationListCursor.delete(mContext, mTarget);
Mindy Pereira695d6962012-06-18 13:02:10 -07001776 if (mFolder.supportsCapability(FolderCapabilities.DELETE_ACTION_FINAL)) {
Marc Blank386243f2012-05-25 10:40:59 -07001777 undoEnabled = false;
1778 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001779 break;
1780 case R.id.mute:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001781 LogUtils.d(LOG_TAG, "Muting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001782 if (mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE)) {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001783 for (Conversation c : mTarget) {
1784 c.localDeleteOnUpdate = true;
1785 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001786 }
1787 mConversationListCursor.mute(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001788 break;
1789 case R.id.report_spam:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001790 LogUtils.d(LOG_TAG, "Reporting spam");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001791 mConversationListCursor.reportSpam(mContext, mTarget);
1792 break;
Paul Westbrook77eee622012-07-10 13:41:57 -07001793 case R.id.mark_not_spam:
1794 LogUtils.d(LOG_TAG, "Marking not spam");
1795 mConversationListCursor.reportNotSpam(mContext, mTarget);
1796 break;
Paul Westbrook76b20622012-07-12 11:45:43 -07001797 case R.id.report_phishing:
1798 LogUtils.d(LOG_TAG, "Reporting phishing");
1799 mConversationListCursor.reportPhishing(mContext, mTarget);
1800 break;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001801 case R.id.remove_star:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001802 LogUtils.d(LOG_TAG, "Removing star");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001803 // Star removal is destructive in the Starred folder.
1804 mConversationListCursor.updateBoolean(mContext, mTarget,
1805 ConversationColumns.STARRED, false);
1806 break;
1807 case R.id.mark_not_important:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001808 LogUtils.d(LOG_TAG, "Marking not-important");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001809 // Marking not important is destructive in a mailbox containing only important
1810 // messages
1811 mConversationListCursor.updateInt(mContext, mTarget,
1812 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001813 break;
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001814 }
1815 if (undoEnabled) {
Mindy Pereirad33674992012-06-25 16:26:30 -07001816 onUndoAvailable(new ToastBarOperation(mTarget.size(), mAction,
1817 ToastBarOperation.UNDO));
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001818 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001819 refreshConversationList();
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001820 if (mIsSelectedSet) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001821 mSelectedSet.clear();
1822 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001823 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001824
1825 /**
1826 * Returns true if this action has been performed, false otherwise.
Andy Huang839ada22012-07-20 15:48:40 -07001827 *
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001828 */
1829 private synchronized boolean isPerformed() {
1830 if (mCompleted) {
1831 return true;
1832 }
1833 mCompleted = true;
1834 return false;
1835 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001836 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001837
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001838 /**
1839 * Get a destructive action for a menu action.
1840 * This is a temporary method, to control the profusion of {@link DestructiveAction} classes
1841 * that are created. Please do not copy this paradigm.
1842 * @param action the resource ID of the menu action: R.id.delete, for example
1843 * @param target the conversations to act upon.
1844 * @return a {@link DestructiveAction} that performs the specified action.
1845 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001846 private final DestructiveAction getAction(int action, Collection<Conversation> target) {
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001847 final DestructiveAction da = new ConversationAction(action, target, false);
1848 registerDestructiveAction(da);
1849 return da;
1850 }
1851
Vikram Aggarwald503df42012-05-11 10:13:35 -07001852 // Called from the FolderSelectionDialog after a user is done selecting folders to assign the
1853 // conversations to.
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001854 @Override
Mindy Pereira8db7e402012-07-13 10:32:47 -07001855 public final void assignFolder(Collection<FolderOperation> folderOps,
1856 Collection<Conversation> target, boolean batch, boolean showUndo) {
1857 // Actions are destructive only when the current folder can be assigned
1858 // to (which is the same as being able to un-assign a conversation from the folder) and
1859 // when the list of folders contains the current folder.
1860 final boolean isDestructive = mFolder
1861 .supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
1862 && FolderOperation.isDestructive(folderOps, mFolder);
Vikram Aggarwald503df42012-05-11 10:13:35 -07001863 LogUtils.d(LOG_TAG, "onFolderChangesCommit: isDestructive = %b", isDestructive);
1864 if (isDestructive) {
1865 for (final Conversation c : target) {
1866 c.localDeleteOnUpdate = true;
Mindy Pereira6778f462012-03-23 18:01:55 -07001867 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001868 }
Mindy Pereira8db7e402012-07-13 10:32:47 -07001869 final DestructiveAction folderChange = getFolderChange(target, folderOps, isDestructive,
Mindy Pereira06642fa2012-07-12 16:23:27 -07001870 batch, showUndo);
Vikram Aggarwald503df42012-05-11 10:13:35 -07001871 // Update the UI elements depending no their visibility and availability
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001872 // TODO(viki): Consolidate this into a single method requestDelete.
Vikram Aggarwald503df42012-05-11 10:13:35 -07001873 if (isDestructive) {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001874 delete(target, folderChange);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001875 } else {
Vikram Aggarwald503df42012-05-11 10:13:35 -07001876 requestUpdate(target, folderChange);
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001877 }
1878 }
1879
Mindy Pereira967ede62012-03-22 09:29:09 -07001880 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001881 public final void onRefreshRequired() {
Mindy Pereirabcd784c2012-08-10 09:53:24 -07001882 if (isAnimating()) {
Mindy Pereira69e88dd2012-08-10 09:30:18 -07001883 LogUtils.d(LOG_TAG, "onRefreshRequired: delay until animating done");
1884 return;
1885 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001886 // Refresh the query in the background
Paul Westbrookcff1aea2012-08-10 11:51:00 -07001887 if (mConversationListCursor.isRefreshRequired()) {
1888 mConversationListCursor.refresh();
1889 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001890 }
1891
Mindy Pereira69e88dd2012-08-10 09:30:18 -07001892 private boolean isAnimating() {
1893 boolean isAnimating = false;
1894 ConversationListFragment convListFragment = getConversationListFragment();
1895 if (convListFragment != null) {
1896 AnimatedAdapter adapter = convListFragment.getAnimatedAdapter();
1897 if (adapter != null) {
1898 isAnimating = adapter.isAnimating();
1899 }
1900 }
1901 return isAnimating;
1902 }
1903
Marc Blankbf128eb2012-04-18 15:58:45 -07001904 /**
1905 * Called when the {@link ConversationCursor} is changed or has new data in it.
1906 * <p>
1907 * {@inheritDoc}
1908 */
1909 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001910 public final void onRefreshReady() {
Paul Westbrookcff1aea2012-08-10 11:51:00 -07001911 if (!isAnimating()) {
Marc Blankbf128eb2012-04-18 15:58:45 -07001912 // Swap cursors
1913 mConversationListCursor.sync();
Marc Blankbf128eb2012-04-18 15:58:45 -07001914 }
1915 mTracker.updateCursor(mConversationListCursor);
1916 }
1917
1918 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001919 public final void onDataSetChanged() {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001920 updateConversationListFragment();
Andy Huang632721e2012-04-11 16:57:26 -07001921 mConversationListObservable.notifyChanged();
Marc Blankbf128eb2012-04-18 15:58:45 -07001922 }
1923
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001924 /**
1925 * If the Conversation List Fragment is visible, updates the fragment.
1926 */
1927 private final void updateConversationListFragment() {
Marc Blankbf128eb2012-04-18 15:58:45 -07001928 final ConversationListFragment convList = getConversationListFragment();
1929 if (convList != null) {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07001930 refreshConversationList();
Paul Westbrook9f119c72012-04-24 16:10:59 -07001931 if (convList.isVisible()) {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001932 Utils.setConversationCursorVisibility(mConversationListCursor, true);
1933 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001934 }
1935 }
1936
1937 /**
1938 * This class handles throttled refresh of the conversation list
1939 */
1940 static class RefreshTimerTask extends TimerTask {
1941 final Handler mHandler;
1942 final AbstractActivityController mController;
1943
1944 RefreshTimerTask(AbstractActivityController controller, Handler handler) {
1945 mHandler = handler;
1946 mController = controller;
1947 }
1948
1949 @Override
1950 public void run() {
1951 mHandler.post(new Runnable() {
1952 @Override
1953 public void run() {
1954 LogUtils.d(LOG_TAG, "Delay done... calling onRefreshRequired");
1955 mController.onRefreshRequired();
1956 }});
1957 }
1958 }
1959
1960 /**
1961 * Cancel the refresh task, if it's running
1962 */
1963 private void cancelRefreshTask () {
1964 if (mConversationListRefreshTask != null) {
1965 mConversationListRefreshTask.cancel();
1966 mConversationListRefreshTask = null;
1967 }
1968 }
1969
1970 @Override
Paul Westbrookcff1aea2012-08-10 11:51:00 -07001971 public void onAnimationEnd(AnimatedAdapter animatedAdapter) {
1972 if (mConversationListCursor.isRefreshReady()) {
1973 LogUtils.d(LOG_TAG, "Stop scrolling: try sync");
1974 onRefreshReady();
Marc Blankbf128eb2012-04-18 15:58:45 -07001975 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001976
Paul Westbrookcff1aea2012-08-10 11:51:00 -07001977 if (mConversationListCursor.isRefreshRequired()) {
1978 LogUtils.d(LOG_TAG, "Stop scrolling: refresh");
1979 mConversationListCursor.refresh();
1980 }
Marc Blankbf128eb2012-04-18 15:58:45 -07001981 }
1982
1983 @Override
Mindy Pereira967ede62012-03-22 09:29:09 -07001984 public void onSetEmpty() {
Mindy Pereira967ede62012-03-22 09:29:09 -07001985 }
1986
1987 @Override
1988 public void onSetPopulated(ConversationSelectionSet set) {
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001989 final ConversationListFragment convList = getConversationListFragment();
1990 if (convList == null) {
1991 return;
1992 }
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001993 mCabActionMenu = new SelectedConversationsActionMenu(mActivity, set, mAccount, mFolder,
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001994 (SwipeableListView) convList.getListView());
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001995 enableCabMode();
Mindy Pereira967ede62012-03-22 09:29:09 -07001996 }
1997
Mindy Pereira967ede62012-03-22 09:29:09 -07001998 @Override
1999 public void onSetChanged(ConversationSelectionSet set) {
2000 // Do nothing. We don't care about changes to the set.
2001 }
2002
2003 @Override
2004 public ConversationSelectionSet getSelectedSet() {
2005 return mSelectedSet;
2006 }
2007
Vikram Aggarwale128fc22012-04-04 12:33:34 -07002008 /**
2009 * Disable the Contextual Action Bar (CAB). The selected set is not changed.
2010 */
2011 protected void disableCabMode() {
Mindy Pereira8937bf12012-07-23 14:05:02 -07002012 // Commit any previous destructive actions when entering/ exiting CAB mode.
2013 commitDestructiveActions();
Vikram Aggarwale128fc22012-04-04 12:33:34 -07002014 if (mCabActionMenu != null) {
2015 mCabActionMenu.deactivate();
2016 }
2017 }
2018
2019 /**
2020 * Re-enable the CAB menu if required. The selection set is not changed.
2021 */
2022 protected void enableCabMode() {
Mindy Pereira8937bf12012-07-23 14:05:02 -07002023 // Commit any previous destructive actions when entering/ exiting CAB mode.
2024 commitDestructiveActions();
Vikram Aggarwale128fc22012-04-04 12:33:34 -07002025 if (mCabActionMenu != null) {
2026 mCabActionMenu.activate();
2027 }
2028 }
2029
Vikram Aggarwal4eb52712012-06-19 16:24:50 -07002030 /**
2031 * Unselect conversations and exit CAB mode.
2032 */
2033 protected final void exitCabMode() {
2034 mSelectedSet.clear();
2035 }
2036
Mindy Pereira967ede62012-03-22 09:29:09 -07002037 @Override
Mindy Pereirafd0c2972012-03-27 13:50:39 -07002038 public void startSearch() {
Vikram Aggarwal35f19d72012-04-24 13:24:48 -07002039 if (mAccount == null) {
2040 // We cannot search if there is no account. Drop the request to the floor.
2041 LogUtils.d(LOG_TAG, "AbstractActivityController.startSearch(): null account");
2042 return;
2043 }
Mindy Pereirafd0c2972012-03-27 13:50:39 -07002044 if (mAccount.supportsCapability(UIProvider.AccountCapabilities.LOCAL_SEARCH)
2045 | mAccount.supportsCapability(UIProvider.AccountCapabilities.SERVER_SEARCH)) {
Vikram Aggarwal70f130e2012-04-03 12:32:14 -07002046 onSearchRequested(mActionBarView.getQuery());
Mindy Pereirafd0c2972012-03-27 13:50:39 -07002047 } else {
2048 Toast.makeText(mActivity.getActivityContext(), mActivity.getActivityContext()
Mindy Pereiraa46c2992012-03-27 14:12:39 -07002049 .getString(R.string.search_unsupported), Toast.LENGTH_SHORT).show();
Mindy Pereirafd0c2972012-03-27 13:50:39 -07002050 }
2051 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07002052
Vikram Aggarwal0dda5732012-04-06 11:20:16 -07002053 @Override
2054 public void exitSearchMode() {
2055 if (mViewMode.getMode() == ViewMode.SEARCH_RESULTS_LIST) {
2056 mActivity.finish();
2057 }
2058 }
2059
Mindy Pereiraacf60392012-04-06 09:11:00 -07002060 /**
2061 * Supports dragging conversations to a folder.
2062 */
2063 @Override
2064 public boolean supportsDrag(DragEvent event, Folder folder) {
2065 return (folder != null
2066 && event != null
2067 && event.getClipDescription() != null
2068 && folder.supportsCapability
2069 (UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
2070 && folder.supportsCapability
2071 (UIProvider.FolderCapabilities.CAN_HOLD_MAIL)
2072 && !mFolder.uri.equals(folder.uri));
2073 }
2074
2075 /**
Mindy Pereira6c2663d2012-07-20 15:37:29 -07002076 * Handles dropping conversations to a folder.
Mindy Pereiraacf60392012-04-06 09:11:00 -07002077 */
2078 @Override
2079 public void handleDrop(DragEvent event, final Folder folder) {
Mindy Pereiraacf60392012-04-06 09:11:00 -07002080 if (!supportsDrag(event, folder)) {
2081 return;
2082 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07002083 final Collection<Conversation> conversations = mSelectedSet.values();
Mindy Pereira8db7e402012-07-13 10:32:47 -07002084 final Collection<FolderOperation> dropTarget = FolderOperation.listOf(new FolderOperation(
2085 folder, true));
2086 // Drag and drop is destructive: we remove conversations from the
2087 // current folder.
Mindy Pereira06642fa2012-07-12 16:23:27 -07002088 final DestructiveAction action = getFolderChange(conversations, dropTarget, true, true,
2089 true);
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07002090 delete(conversations, action);
Mindy Pereiraacf60392012-04-06 09:11:00 -07002091 }
Mindy Pereira0963ef82012-04-10 11:43:01 -07002092
2093 @Override
Mindy Pereira0963ef82012-04-10 11:43:01 -07002094 public void onTouchEvent(MotionEvent event) {
2095 if (event.getAction() == MotionEvent.ACTION_DOWN) {
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002096 if (mToastBar != null && !mToastBar.isEventInToastBar(event)) {
2097 mToastBar.hide(true);
Mindy Pereira0963ef82012-04-10 11:43:01 -07002098 }
2099 }
2100 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07002101
Andy Huang632721e2012-04-11 16:57:26 -07002102 @Override
2103 public void onConversationSeen(Conversation conv) {
2104 mPagerController.onConversationSeen(conv);
2105 }
2106
Andy Huangb1c34dc2012-04-17 16:36:19 -07002107 private class ConversationListLoaderCallbacks implements
2108 LoaderManager.LoaderCallbacks<ConversationCursor> {
2109
2110 @Override
2111 public Loader<ConversationCursor> onCreateLoader(int id, Bundle args) {
2112 Loader<ConversationCursor> result = new ConversationCursorLoader((Activity) mActivity,
Marc Blank741846b2012-08-01 15:51:45 -07002113 mAccount, mFolder.conversationListUri, mFolder.name, mListCursorCallbacks);
Andy Huangb1c34dc2012-04-17 16:36:19 -07002114 return result;
2115 }
2116
2117 @Override
2118 public void onLoadFinished(Loader<ConversationCursor> loader, ConversationCursor data) {
Andy Huang632721e2012-04-11 16:57:26 -07002119 LogUtils.d(LOG_TAG, "IN AAC.ConversationCursor.onLoadFinished, data=%s loader=%s",
2120 data, loader);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002121 // Clear our all pending destructive actions before swapping the conversation cursor
2122 destroyPending(null);
Andy Huangb1c34dc2012-04-17 16:36:19 -07002123 mConversationListCursor = data;
Paul Westbrookbf232c32012-04-18 03:17:41 -07002124 mConversationListCursor.addListener(AbstractActivityController.this);
Andy Huangb1c34dc2012-04-17 16:36:19 -07002125
Andy Huange3df1ad2012-04-24 17:15:23 -07002126 mConversationListObservable.notifyChanged();
Andy Huangb1c34dc2012-04-17 16:36:19 -07002127 // Register the AbstractActivityController as a listener to changes in
2128 // data in the cursor.
2129 final ConversationListFragment convList = getConversationListFragment();
2130 if (convList != null) {
2131 convList.onCursorUpdated();
Paul Westbrook9f119c72012-04-24 16:10:59 -07002132
2133 if (convList.isVisible()) {
2134 // The conversation list is visible.
2135 Utils.setConversationCursorVisibility(mConversationListCursor, true);
2136 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07002137 }
2138 // Shown for search results in two-pane mode only.
2139 if (shouldShowFirstConversation()) {
2140 if (mConversationListCursor.getCount() > 0) {
2141 mConversationListCursor.moveToPosition(0);
2142 if (convList != null) {
2143 convList.getListView().setItemChecked(0, true);
2144 }
2145 final Conversation conv = new Conversation(mConversationListCursor);
2146 conv.position = 0;
2147 onConversationSelected(conv);
2148 }
2149 }
2150 }
2151
2152 @Override
2153 public void onLoaderReset(Loader<ConversationCursor> loader) {
2154 final ConversationListFragment convList = getConversationListFragment();
2155 if (convList == null) {
2156 return;
2157 }
2158 convList.onCursorUpdated();
2159 }
Paul Westbrookbf232c32012-04-18 03:17:41 -07002160 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07002161
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002162 /**
2163 * Destroy the pending {@link DestructiveAction} till now and assign the given action as the
2164 * next destructive action..
2165 * @param nextAction the next destructive action to be performed. This can be null.
2166 */
2167 private final void destroyPending(DestructiveAction nextAction) {
2168 // If there is a pending action, perform that first.
2169 if (mPendingDestruction != null) {
2170 mPendingDestruction.performAction();
2171 }
2172 mPendingDestruction = nextAction;
2173 }
2174
2175 /**
2176 * Register a destructive action with the controller. This performs the previous destructive
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07002177 * 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 -07002178 * embellish this method any more.
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002179 * @param action
2180 */
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07002181 private final void registerDestructiveAction(DestructiveAction action) {
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002182 // TODO(viki): This is not a good idea. The best solution is for clients to request a
2183 // destructive action from the controller and for the controller to own the action. This is
2184 // a half-way solution while refactoring DestructiveAction.
2185 destroyPending(action);
2186 return;
2187 }
2188
Vikram Aggarwal531488e2012-05-29 16:36:52 -07002189 @Override
2190 public final DestructiveAction getBatchAction(int action) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07002191 final DestructiveAction da = new ConversationAction(action, mSelectedSet.values(), true);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002192 registerDestructiveAction(da);
2193 return da;
2194 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002195
Mindy Pereirade3e74a2012-07-24 09:43:10 -07002196 @Override
2197 public final DestructiveAction getDeferredBatchAction(int action) {
2198 final DestructiveAction da = new ConversationAction(action, mSelectedSet.values(), true);
2199 return da;
2200 }
2201
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07002202 /**
2203 * Class to change the folders that are assigned to a set of conversations. This is destructive
2204 * because the user can remove the current folder from the conversation, in which case it has
2205 * to be animated away from the current folder.
2206 */
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002207 private class FolderDestruction implements DestructiveAction {
Paul Westbrook77eee622012-07-10 13:41:57 -07002208 private final Collection<Conversation> mTarget;
Mindy Pereira8db7e402012-07-13 10:32:47 -07002209 private final ArrayList<FolderOperation> mFolderOps = new ArrayList<FolderOperation>();
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002210 private final boolean mIsDestructive;
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002211 /** Whether this destructive action has already been performed */
2212 private boolean mCompleted;
Mindy Pereiraf3a45562012-05-24 16:30:19 -07002213 private boolean mIsSelectedSet;
Mindy Pereira06642fa2012-07-12 16:23:27 -07002214 private boolean mShowUndo;
Mindy Pereira01f30502012-08-14 10:30:51 -07002215 private int mAction;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002216
2217 /**
2218 * Create a new folder destruction object to act on the given conversations.
2219 * @param target
2220 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002221 private FolderDestruction(final Collection<Conversation> target,
Mindy Pereira8db7e402012-07-13 10:32:47 -07002222 final Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
Mindy Pereira01f30502012-08-14 10:30:51 -07002223 boolean showUndo, int action) {
Paul Westbrook77eee622012-07-10 13:41:57 -07002224 mTarget = ImmutableList.copyOf(target);
Mindy Pereira8db7e402012-07-13 10:32:47 -07002225 mFolderOps.addAll(folders);
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002226 mIsDestructive = isDestructive;
Mindy Pereiraf3a45562012-05-24 16:30:19 -07002227 mIsSelectedSet = isBatch;
Mindy Pereira06642fa2012-07-12 16:23:27 -07002228 mShowUndo = showUndo;
Mindy Pereira01f30502012-08-14 10:30:51 -07002229 mAction = action;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002230 }
2231
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002232 @Override
2233 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002234 if (isPerformed()) {
2235 return;
2236 }
Mindy Pereira06642fa2012-07-12 16:23:27 -07002237 if (mIsDestructive && mShowUndo) {
Mindy Pereirad33674992012-06-25 16:26:30 -07002238 ToastBarOperation undoOp = new ToastBarOperation(mTarget.size(),
Mindy Pereira01f30502012-08-14 10:30:51 -07002239 mAction, ToastBarOperation.UNDO);
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002240 onUndoAvailable(undoOp);
2241 }
Mindy Pereira8db7e402012-07-13 10:32:47 -07002242 // For each conversation, for each operation, add/ remove the
2243 // appropriate folders.
2244 for (Conversation target : mTarget) {
2245 HashMap<Uri, Folder> targetFolders = Folder
Mindy Pereira68f83842012-07-27 09:43:31 -07002246 .hashMapForFolders(target.getRawFolders());
Mindy Pereira01f30502012-08-14 10:30:51 -07002247 if (mIsDestructive) {
2248 target.localDeleteOnUpdate = true;
2249 }
Mindy Pereira8db7e402012-07-13 10:32:47 -07002250 for (FolderOperation op : mFolderOps) {
2251 if (op.mAdd) {
2252 targetFolders.put(op.mFolder.uri, op.mFolder);
2253 } else {
2254 targetFolders.remove(op.mFolder.uri);
2255 }
2256 }
Mindy Pereira85c4a772012-07-30 10:47:26 -07002257 target.setRawFolders(Folder.getSerializedFolderString(targetFolders.values()));
Mindy Pereira00ffece2012-07-27 08:49:56 -07002258 mConversationListCursor.updateString(mContext, Conversation.listOf(target),
Mindy Pereira85c4a772012-07-30 10:47:26 -07002259 Conversation.UPDATE_FOLDER_COLUMN, target.getRawFoldersString());
Mindy Pereira8db7e402012-07-13 10:32:47 -07002260 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002261 refreshConversationList();
Mindy Pereiraf3a45562012-05-24 16:30:19 -07002262 if (mIsSelectedSet) {
2263 mSelectedSet.clear();
2264 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002265 }
Mindy Pereira8db7e402012-07-13 10:32:47 -07002266
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002267 /**
2268 * Returns true if this action has been performed, false otherwise.
Andy Huang839ada22012-07-20 15:48:40 -07002269 *
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002270 */
2271 private synchronized boolean isPerformed() {
2272 if (mCompleted) {
2273 return true;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002274 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002275 mCompleted = true;
2276 return false;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002277 }
2278 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002279
Vikram Aggarwald503df42012-05-11 10:13:35 -07002280 private final DestructiveAction getFolderChange(Collection<Conversation> target,
Mindy Pereira8db7e402012-07-13 10:32:47 -07002281 Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
2282 boolean showUndo) {
Mindy Pereira06642fa2012-07-12 16:23:27 -07002283 final DestructiveAction da = new FolderDestruction(target, folders, isDestructive, isBatch,
Mindy Pereira01f30502012-08-14 10:30:51 -07002284 showUndo, R.id.change_folder);
2285 registerDestructiveAction(da);
2286 return da;
2287 }
2288
2289 @Override
2290 public final DestructiveAction getDeferredRemoveFolder(Collection<Conversation> target,
2291 Folder toRemove, boolean isDestructive, boolean isBatch,
2292 boolean showUndo) {
2293 Collection<FolderOperation> folderOps = new ArrayList<FolderOperation>();
2294 folderOps.add(new FolderOperation(toRemove, false));
2295 return new FolderDestruction(target, folderOps, isDestructive, isBatch,
2296 showUndo, R.id.remove_folder);
2297 }
2298
2299 private final DestructiveAction getRemoveFolder(Collection<Conversation> target,
2300 Folder toRemove, boolean isDestructive, boolean isBatch,
2301 boolean showUndo) {
2302 Collection<FolderOperation> folderOps = new ArrayList<FolderOperation>();
2303 folderOps.add(new FolderOperation(toRemove, false));
2304 DestructiveAction da = new FolderDestruction(target, folderOps, isDestructive, isBatch,
2305 showUndo, R.id.remove_folder);
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002306 registerDestructiveAction(da);
2307 return da;
2308 }
Vikram Aggarwal75daee52012-04-30 11:13:09 -07002309
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07002310 @Override
2311 public final void refreshConversationList() {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07002312 final ConversationListFragment convList = getConversationListFragment();
2313 if (convList == null) {
2314 return;
2315 }
2316 convList.requestListRefresh();
2317 }
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002318
2319 protected final ActionClickedListener getUndoClickedListener(
2320 final AnimatedAdapter listAdapter) {
2321 return new ActionClickedListener() {
2322 @Override
2323 public void onActionClicked() {
2324 if (mAccount.undoUri != null) {
2325 // NOTE: We might want undo to return the messages affected, in which case
2326 // the resulting cursor might be interesting...
2327 // TODO: Use UIProvider.SEQUENCE_QUERY_PARAMETER to indicate the set of
2328 // commands to undo
2329 if (mConversationListCursor != null) {
2330 mConversationListCursor.undo(
2331 mActivity.getActivityContext(), mAccount.undoUri);
2332 }
2333 if (listAdapter != null) {
2334 listAdapter.setUndo(true);
2335 }
2336 }
2337 }
2338 };
2339 }
2340
Andrew Sapperstein9d7519d2012-07-16 14:03:53 -07002341 protected final void showErrorToast(final Folder folder, boolean replaceVisibleToast) {
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002342 mToastBar.setConversationMode(false);
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002343
2344 ActionClickedListener listener = null;
2345 int actionTextResourceId;
2346 final int lastSyncResult = folder.lastSyncResult;
2347 switch (lastSyncResult) {
2348 case UIProvider.LastSyncResult.CONNECTION_ERROR:
2349 listener = getRetryClickedListener(folder);
2350 actionTextResourceId = R.string.retry;
2351 break;
2352 case UIProvider.LastSyncResult.AUTH_ERROR:
2353 listener = getSignInClickedListener();
2354 actionTextResourceId = R.string.signin;
2355 break;
2356 case UIProvider.LastSyncResult.SECURITY_ERROR:
2357 return; // Currently we do nothing for security errors.
2358 case UIProvider.LastSyncResult.STORAGE_ERROR:
2359 listener = getStorageErrorClickedListener();
2360 actionTextResourceId = R.string.info;
2361 break;
2362 case UIProvider.LastSyncResult.INTERNAL_ERROR:
2363 listener = getInternalErrorClickedListener();
2364 actionTextResourceId = R.string.report;
2365 break;
2366 default:
2367 return;
2368 }
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002369 mToastBar.show(
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002370 listener,
Andrew Sapperstein5d420962012-07-12 16:43:10 -07002371 R.drawable.ic_alert_white,
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002372 Utils.getSyncStatusText(mActivity.getActivityContext(),
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002373 lastSyncResult),
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002374 false, /* showActionIcon */
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002375 actionTextResourceId,
Andrew Sapperstein9d7519d2012-07-16 14:03:53 -07002376 replaceVisibleToast,
2377 new ToastBarOperation(1, 0, ToastBarOperation.ERROR));
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002378 }
2379
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002380 private ActionClickedListener getRetryClickedListener(final Folder folder) {
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002381 return new ActionClickedListener() {
2382 @Override
2383 public void onActionClicked() {
2384 final Uri uri = folder.refreshUri;
2385
2386 if (uri != null) {
2387 if (mFolderSyncTask != null) {
2388 mFolderSyncTask.cancel(true);
2389 }
2390 mFolderSyncTask = new AsyncRefreshTask(mActivity.getActivityContext(), uri);
2391 mFolderSyncTask.execute();
2392 }
2393 }
2394 };
2395 }
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002396
2397 private ActionClickedListener getSignInClickedListener() {
2398 return new ActionClickedListener() {
2399 @Override
2400 public void onActionClicked() {
2401 // TODO - have pressing Sign-in actually
2402 // allow the user to update their credentials
2403 // Needs to also be done in ConversationListFooterView
2404 }
2405 };
2406 }
2407
2408 private ActionClickedListener getStorageErrorClickedListener() {
2409 return new ActionClickedListener() {
2410 @Override
2411 public void onActionClicked() {
2412 DialogFragment fragment = (DialogFragment)
2413 mFragmentManager.findFragmentByTag(SYNC_ERROR_DIALOG_FRAGMENT_TAG);
2414 if (fragment == null) {
2415 fragment = SyncErrorDialogFragment.newInstance();
2416 }
2417 fragment.show(mFragmentManager, SYNC_ERROR_DIALOG_FRAGMENT_TAG);
2418 }
2419 };
2420 }
2421
2422 private ActionClickedListener getInternalErrorClickedListener() {
2423 return new ActionClickedListener() {
2424 @Override
2425 public void onActionClicked() {
2426 // TODO - have pressing report actually do something
2427 // Needs to also be done in ConversationListFooterView
2428 }
2429 };
2430 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08002431}