blob: 8a6f79e8a661b164d72e59f20ac78f36bd941045 [file] [log] [blame]
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08001/*******************************************************************************
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *******************************************************************************/
17
18package com.android.mail.ui;
19
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080020import android.app.ActionBar;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080021import android.app.ActionBar.LayoutParams;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080022import android.app.Activity;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070023import android.app.AlertDialog;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080024import android.app.Dialog;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -070025import android.app.Fragment;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -070026import android.app.FragmentManager;
Andy Huangf9a73482012-03-13 15:54:02 -070027import android.app.LoaderManager;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070028import android.app.SearchManager;
Mindy Pereiraacf60392012-04-06 09:11:00 -070029import android.content.ClipData;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080030import android.content.ContentResolver;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080031import android.content.Context;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080032import android.content.CursorLoader;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070033import android.content.DialogInterface;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080034import android.content.Intent;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080035import android.content.Loader;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080036import android.database.Cursor;
Paul Westbrook23b74b92012-02-29 11:36:12 -080037import android.net.Uri;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080038import android.os.Bundle;
Mindy Pereira21ab4902012-03-19 18:48:03 -070039import android.os.Handler;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070040import android.provider.SearchRecentSuggestions;
Mindy Pereiraacf60392012-04-06 09:11:00 -070041import android.view.DragEvent;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080042import android.view.KeyEvent;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080043import android.view.LayoutInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080044import android.view.Menu;
Mindy Pereira28d5f722012-02-15 12:32:40 -080045import android.view.MenuInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080046import android.view.MenuItem;
47import android.view.MotionEvent;
Marc Blankbf128eb2012-04-18 15:58:45 -070048import android.widget.AbsListView;
49import android.widget.AbsListView.OnScrollListener;
Mindy Pereirafd0c2972012-03-27 13:50:39 -070050import android.widget.Toast;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080051
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080052import com.android.mail.ConversationListContext;
Andy Huangf9a73482012-03-13 15:54:02 -070053import com.android.mail.R;
Mindy Pereira967ede62012-03-22 09:29:09 -070054import com.android.mail.browse.ConversationCursor;
Marc Blankbf128eb2012-04-18 15:58:45 -070055import com.android.mail.browse.ConversationCursor.ConversationListener;
Marc Blank7c9f6ac2012-04-02 13:27:19 -070056import com.android.mail.browse.SelectedConversationsActionMenu;
Mindy Pereira9b875682012-02-15 18:10:54 -080057import com.android.mail.compose.ComposeActivity;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080058import com.android.mail.providers.Account;
Mindy Pereira9b875682012-02-15 18:10:54 -080059import com.android.mail.providers.Conversation;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080060import com.android.mail.providers.Folder;
Paul Westbrookc2074c42012-03-22 15:26:58 -070061import com.android.mail.providers.MailAppProvider;
Mindy Pereiradac00542012-03-01 10:50:33 -080062import com.android.mail.providers.Settings;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070063import com.android.mail.providers.SuggestionsProvider;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080064import com.android.mail.providers.UIProvider;
Paul Westbrook2388c5d2012-03-25 12:29:11 -070065import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070066import com.android.mail.providers.UIProvider.AutoAdvance;
Mindy Pereirac9d59182012-03-22 16:06:46 -070067import com.android.mail.providers.UIProvider.ConversationColumns;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070068import com.android.mail.providers.UIProvider.FolderCapabilities;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080069import com.android.mail.utils.LogUtils;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -080070import com.android.mail.utils.Utils;
Marc Blank167faa82012-03-21 13:11:53 -070071import com.google.common.collect.ImmutableList;
Mindy Pereiraacf60392012-04-06 09:11:00 -070072import com.google.common.collect.Lists;
Marc Blank167faa82012-03-21 13:11:53 -070073import com.google.common.collect.Sets;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080074
Marc Blank167faa82012-03-21 13:11:53 -070075import java.util.ArrayList;
Mindy Pereirafbe40192012-03-20 10:40:45 -070076import java.util.Collection;
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -070077import java.util.HashSet;
Paul Westbrook23b74b92012-02-29 11:36:12 -080078import java.util.Set;
Marc Blankbf128eb2012-04-18 15:58:45 -070079import java.util.Timer;
80import java.util.TimerTask;
Paul Westbrook23b74b92012-02-29 11:36:12 -080081
82
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080083/**
Mindy Pereira161f50d2012-02-28 15:47:19 -080084 * This is an abstract implementation of the Activity Controller. This class
85 * knows how to respond to menu items, state changes, layout changes, etc. It
86 * weaves together the views and listeners, dispatching actions to the
87 * respective underlying classes.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080088 * <p>
Mindy Pereira161f50d2012-02-28 15:47:19 -080089 * Even though this class is abstract, it should provide default implementations
90 * for most, if not all the methods in the ActivityController interface. This
91 * makes the task of the subclasses easier: OnePaneActivityController and
92 * TwoPaneActivityController can be concise when the common functionality is in
93 * AbstractActivityController.
94 * </p>
95 * <p>
96 * In the Gmail codebase, this was called BaseActivityController
97 * </p>
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080098 */
Marc Blankbf128eb2012-04-18 15:58:45 -070099public abstract class AbstractActivityController implements ActivityController,
100 ConversationListener, OnScrollListener {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800101 // Keys for serialization of various information in Bundles.
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700102 /** Tag for {@link #mAccount} */
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800103 private static final String SAVED_ACCOUNT = "saved-account";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700104 /** Tag for {@link #mFolder} */
Mindy Pereira5e478d22012-03-26 18:04:58 -0700105 private static final String SAVED_FOLDER = "saved-folder";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700106 /** Tag for {@link #mCurrentConversation} */
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700107 private static final String SAVED_CONVERSATION = "saved-conversation";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700108 /** Tag for {@link #mSelectedSet} */
109 private static final String SAVED_SELECTED_SET = "saved-selected-set";
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800110
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700111 /** Tag used when loading a wait fragment */
112 protected static final String TAG_WAIT = "wait-fragment";
113 /** Tag used when loading a conversation list fragment. */
114 protected static final String TAG_CONVERSATION_LIST = "tag-conversation-list";
115 /** Tag used when loading a conversation fragment. */
116 protected static final String TAG_CONVERSATION = "tag-conversation";
117 /** Tag used when loading a folder list fragment. */
118 protected static final String TAG_FOLDER_LIST = "tag-folder-list";
119
Marc Blankbf128eb2012-04-18 15:58:45 -0700120 private static final long CONVERSATION_LIST_THROTTLE_MS = 4000L;
121
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700122 /** Are we on a tablet device or not. */
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800123 public final boolean IS_TABLET_DEVICE;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800124
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800125 protected Account mAccount;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800126 protected Folder mFolder;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800127 protected ActionBarView mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800128 protected final RestrictedActivity mActivity;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800129 protected final Context mContext;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700130 private final FragmentManager mFragmentManager;
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800131 protected final RecentFolderList mRecentFolderList;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800132 protected ConversationListContext mConvListContext;
Mindy Pereira9b875682012-02-15 18:10:54 -0800133 protected Conversation mCurrentConversation;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800134
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700135 /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
136 private SuppressNotificationReceiver mNewEmailReceiver = null;
137
Mindy Pereirafbe40192012-03-20 10:40:45 -0700138 protected Handler mHandler = new Handler();
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800139 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800140 * The current mode of the application. All changes in mode are initiated by
141 * the activity controller. View mode changes are propagated to classes that
142 * attach themselves as listeners of view mode changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800143 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800144 protected final ViewMode mViewMode;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800145 protected ContentResolver mResolver;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800146 protected boolean isLoaderInitialized = false;
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800147 private AsyncRefreshTask mAsyncRefreshTask;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800148
Paul Westbrook23b74b92012-02-29 11:36:12 -0800149 private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
Mindy Pereira559c3b32012-03-01 13:17:47 -0800150 protected Settings mCachedSettings;
Mindy Pereira967ede62012-03-22 09:29:09 -0700151 protected ConversationCursor mConversationListCursor;
Marc Blankbf128eb2012-04-18 15:58:45 -0700152 protected boolean mConversationListenerAdded = false;
153
154 private boolean mIsConversationListScrolling = false;
155 private long mConversationListRefreshTime = 0;
156 private Timer mConversationListTimer = new Timer();
157 private RefreshTimerTask mConversationListRefreshTask;
Marc Blanke1d1b072012-04-13 17:29:16 -0700158
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700159 /** Listeners that are intersted in changes to current account settings. */
160 private final ArrayList<Settings.ChangeListener> mSettingsListeners = Lists.newArrayList();
161
Mindy Pereira967ede62012-03-22 09:29:09 -0700162 /**
163 * Selected conversations, if any.
164 */
Andy Huang4556a442012-03-30 16:42:05 -0700165 private final ConversationSelectionSet mSelectedSet = new ConversationSelectionSet();
Mindy Pereiradac00542012-03-01 10:50:33 -0800166
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700167 private final int mFolderItemUpdateDelayMs;
168
Vikram Aggarwal135fd022012-04-11 14:44:15 -0700169 /** Keeps track of selected and unselected conversations */
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700170 private final ConversationPositionTracker mTracker =
171 new ConversationPositionTracker(mSelectedSet);
172
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700173 /**
174 * Action menu associated with the selected set.
175 */
176 SelectedConversationsActionMenu mCabActionMenu;
Mindy Pereira0963ef82012-04-10 11:43:01 -0700177 protected UndoBarView mUndoBarView;
Mindy Pereiraab486362012-03-21 18:18:53 -0700178
Andy Huangb1c34dc2012-04-17 16:36:19 -0700179 // this is split out from the general loader dispatcher because its loader doesn't return a
180 // basic Cursor
181 private final ConversationListLoaderCallbacks mListCursorCallbacks =
182 new ConversationListLoaderCallbacks();
183
Vikram Aggarwal04ff99c2012-02-28 15:29:13 -0800184 protected static final String LOG_TAG = new LogUtils().getLogTag();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800185 /** Constants used to differentiate between the types of loaders. */
186 private static final int LOADER_ACCOUNT_CURSOR = 0;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800187 private static final int LOADER_FOLDER_CURSOR = 2;
188 private static final int LOADER_RECENT_FOLDERS = 3;
Mindy Pereira967ede62012-03-22 09:29:09 -0700189 private static final int LOADER_CONVERSATION_LIST = 4;
Mindy Pereiraab486362012-03-21 18:18:53 -0700190 private static final int LOADER_ACCOUNT_INBOX = 5;
191 private static final int LOADER_SEARCH = 6;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -0700192 private static final int LOADER_ACCOUNT_UPDATE_CURSOR = 7;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800193
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700194 private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
195
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800196 public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
197 mActivity = activity;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700198 mFragmentManager = mActivity.getFragmentManager();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800199 mViewMode = viewMode;
200 mContext = activity.getApplicationContext();
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800201 IS_TABLET_DEVICE = Utils.useTabletUI(mContext);
Marc Blank167faa82012-03-21 13:11:53 -0700202 mRecentFolderList = new RecentFolderList(mContext, this);
Mindy Pereira967ede62012-03-22 09:29:09 -0700203 // Allow the fragment to observe changes to its own selection set. No other object is
204 // aware of the selected set.
205 mSelectedSet.addObserver(this);
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700206
207 mFolderItemUpdateDelayMs =
208 mContext.getResources().getInteger(R.integer.folder_item_refresh_delay_ms);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800209 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800210
211 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800212 public void clearSubject() {
213 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800214 }
215
216 @Override
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800217 public Account getCurrentAccount() {
218 return mAccount;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800219 }
220
221 @Override
222 public ConversationListContext getCurrentListContext() {
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800223 return mConvListContext;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800224 }
225
226 @Override
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800227 public String getHelpContext() {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800228 return "Mail";
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800229 }
230
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800231 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800232 public int getMode() {
233 return mViewMode.getMode();
234 }
235
236 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800237 public String getUnshownSubject(String subject) {
238 // Calculate how much of the subject is shown, and return the remaining.
239 return null;
240 }
241
242 @Override
243 public void handleConversationLoadError() {
244 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800245 }
246
Mindy Pereira967ede62012-03-22 09:29:09 -0700247 @Override
248 public ConversationCursor getConversationListCursor() {
249 return mConversationListCursor;
250 }
251
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700252 /**
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700253 * Check if the fragment is attached to an activity and has a root view.
254 * @param in
255 * @return true if the fragment is valid, false otherwise
256 */
257 private static final boolean isValidFragment(Fragment in) {
258 if (in == null || in.getActivity() == null || in.getView() == null) {
259 return false;
260 }
261 return true;
262 }
263
264 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700265 * Get the conversation list fragment for this activity. If the conversation list fragment
266 * is not attached, this method returns null
267 * @return
268 */
269 protected ConversationListFragment getConversationListFragment() {
270 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700271 if (isValidFragment(fragment)) {
272 return (ConversationListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700273 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700274 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700275 }
276
277 /**
278 * Returns the conversation view fragment attached with this activity. If no such fragment
279 * is attached, this method returns null.
280 * @return
281 */
282 protected ConversationViewFragment getConversationViewFragment() {
283 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700284 if (isValidFragment(fragment)) {
285 return (ConversationViewFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700286 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700287 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700288 }
289
290 /**
291 * Returns the folder list fragment attached with this activity. If no such fragment is attached
292 * this method returns null.
293 * @return
294 */
295 protected FolderListFragment getFolderListFragment() {
296 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_FOLDER_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700297 if (isValidFragment(fragment)) {
298 return (FolderListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700299 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700300 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700301 }
302
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800303 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800304 * Initialize the action bar. This is not visible to OnePaneController and
305 * TwoPaneController so they cannot override this behavior.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800306 */
307 private void initCustomActionBarView() {
308 ActionBar actionBar = mActivity.getActionBar();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800309 mActionBarView = (ActionBarView) LayoutInflater.from(mContext).inflate(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800310 R.layout.actionbar_view, null);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800311 if (actionBar != null && mActionBarView != null) {
Mindy Pereira161f50d2012-02-28 15:47:19 -0800312 // Why have a different variable for the same thing? We should apply
313 // the same actions
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800314 // on mActionBarView instead.
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800315 mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
316 actionBar.setCustomView(mActionBarView, new ActionBar.LayoutParams(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800317 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
318 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
Vikram Aggarwal2a25d0c2012-02-21 16:43:10 -0800319 ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800320 }
321 }
322
323 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800324 * Returns whether the conversation list fragment is visible or not.
325 * Different layouts will have their own notion on the visibility of
326 * fragments, so this method needs to be overriden.
327 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800328 * @return
329 */
330 protected abstract boolean isConversationListVisible();
331
332 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800333 public void onAccountChanged(Account account) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800334 if (!account.equals(mAccount)) {
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700335 // Current account is different from the new account, restart loaders and show
336 // the account Inbox.
Mindy Pereira28d5f722012-02-15 12:32:40 -0800337 mAccount = account;
Marc Blankbf128eb2012-04-18 15:58:45 -0700338 cancelRefreshTask();
Paul Westbrookb1f573c2012-04-06 11:38:28 -0700339 onSettingsChanged(mAccount.settings);
Mindy Pereira5ba33802012-03-26 16:30:11 -0700340 mActionBarView.setAccount(mAccount);
Mindy Pereira5ba33802012-03-26 16:30:11 -0700341 loadAccountInbox();
342
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800343 mRecentFolderList.setCurrentAccount(account);
Vikram Aggarwal94c94de2012-04-04 15:38:28 -0700344 restartOptionalLoader(LOADER_RECENT_FOLDERS);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800345 mActivity.invalidateOptionsMenu();
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700346
347 disableNotificationsOnAccountChange(mAccount);
348
Paul Westbrook2d50bcd2012-04-10 11:53:47 -0700349 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
350
Paul Westbrookc2074c42012-03-22 15:26:58 -0700351 MailAppProvider.getInstance().setLastViewedAccount(mAccount.uri.toString());
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700352 } else {
353 // Current account is the same as the new account. Load the default inbox if the
354 // current inbox is not the same as the default inbox.
355 final Uri oldUri = mFolder != null ? mFolder.uri : Uri.EMPTY;
356 final Uri newUri = getDefaultInboxUri(mCachedSettings);
Mindy Pereirab283d802012-03-27 16:05:39 -0700357 if ((mFolder == null || mFolder.type == UIProvider.FolderType.INBOX)
358 && !oldUri.equals(newUri)) {
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700359 loadAccountInbox();
360 }
Mindy Pereira28d5f722012-02-15 12:32:40 -0800361 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800362 }
363
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700364 /**
365 * Returns the URI of the current account's default inbox if available, otherwise
366 * returns the empty URI {@link Uri#EMPTY}
367 * @return
368 */
369 private Uri getDefaultInboxUri(Settings settings) {
370 if (settings != null && settings.defaultInbox != null) {
371 return settings.defaultInbox;
372 }
373 return Uri.EMPTY;
374 }
375
Mindy Pereiradac00542012-03-01 10:50:33 -0800376 public void onSettingsChanged(Settings settings) {
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700377 final Uri oldUri = getDefaultInboxUri(mCachedSettings);
378 final Uri newUri = getDefaultInboxUri(settings);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700379 dispatchSettingsChange(settings);
Mindy Pereira12a676a2012-03-23 13:00:22 -0700380 resetActionBarIcon();
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700381
Mindy Pereira12a676a2012-03-23 13:00:22 -0700382 // Only restart the loader if the defaultInboxUri is not the same as
383 // the folder we are already loading.
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700384 final boolean changed = !oldUri.equals(newUri);
Mindy Pereira4efdbe62012-04-03 11:18:04 -0700385 if (settings != null
386 && settings.defaultInbox != null
387 && (mFolder == null
388 // we really only want CHANGES to the inbox setting, not just
389 // the first setting of it.
390 || (mFolder.type == UIProvider.FolderType.INBOX && !oldUri.equals(Uri.EMPTY))
391 && changed)) {
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700392 loadAccountInbox();
Mindy Pereiraab486362012-03-21 18:18:53 -0700393 }
Mindy Pereiradac00542012-03-01 10:50:33 -0800394 }
395
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800396 @Override
397 public Settings getSettings() {
398 return mCachedSettings;
399 }
400
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700401 /**
402 * Adds a listener interested in change in settings. If a class is storing a reference to
403 * Settings, it should listen on changes, so it can receive updates to settings.
404 * Must happen in the UI thread.
405 */
406 public void addSettingsListener(Settings.ChangeListener listener) {
407 mSettingsListeners.add(listener);
408 }
409
410 /**
411 * Removes a listener from receiving settings changes.
412 * Must happen in the UI thread.
413 */
414 public void removeSettingsListener(Settings.ChangeListener listener) {
415 mSettingsListeners.remove(listener);
416 }
417
418 /**
419 * Method that lets the settings listeners know when the settings got changed.
420 */
421 private void dispatchSettingsChange(Settings updatedSettings) {
422 mCachedSettings = updatedSettings;
423 // Copy the list of current listeners so that
424 final ArrayList<Settings.ChangeListener> allListeners =
425 new ArrayList<Settings.ChangeListener>(mSettingsListeners);
426 for (Settings.ChangeListener listener : allListeners) {
427 if (listener != null) {
428 listener.onSettingsChanged(mCachedSettings);
429 }
430 }
431 // And we know that the ConversationListFragment is interested in changes to settings,
432 // though it hasn't registered itself with us.
433 final ConversationListFragment convList = getConversationListFragment();
434 if (convList != null) {
435 convList.onSettingsChanged(mCachedSettings);
436 }
437 }
438
Mindy Pereirae0828392012-03-08 10:38:40 -0800439 private void fetchSearchFolder(Intent intent) {
Mindy Pereiraab486362012-03-21 18:18:53 -0700440 Bundle args = new Bundle();
441 args.putString(ConversationListContext.EXTRA_SEARCH_QUERY, intent
Mindy Pereirae0828392012-03-08 10:38:40 -0800442 .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -0700443 mActivity.getLoaderManager().restartLoader(LOADER_SEARCH, args, this);
Mindy Pereirae0828392012-03-08 10:38:40 -0800444 }
445
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800446 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800447 public void onFolderChanged(Folder folder) {
Mindy Pereira7a3471f2012-03-06 12:23:41 -0800448 if (folder != null && !folder.equals(mFolder)) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800449 setFolder(folder);
Mindy Pereira161f50d2012-02-28 15:47:19 -0800450 mConvListContext = ConversationListContext.forFolder(mContext, mAccount, mFolder);
Mindy Pereira28e0c342012-02-17 15:05:13 -0800451 showConversationList(mConvListContext);
Paul Westbrook9024b6d2012-03-19 13:57:55 -0700452
453 // Add the folder that we were viewing to the recent folders list.
454 // TODO: this may need to be fine tuned. If this is the signal that is indicating that
455 // the list is shown to the user, this could fire in one pane if the user goes directly
456 // to a conversation
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700457 updateRecentFolderList();
Marc Blankbf128eb2012-04-18 15:58:45 -0700458 cancelRefreshTask();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800459 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800460 }
461
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700462 /**
463 * Update the recent folders. This only needs to be done once when accessing a new folder.
464 */
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700465 private void updateRecentFolderList() {
Mindy Pereiraab486362012-03-21 18:18:53 -0700466 if (mFolder != null) {
Marc Blank2675dbc2012-04-03 10:17:13 -0700467 mRecentFolderList.touchFolder(mFolder, mAccount);
Mindy Pereiraab486362012-03-21 18:18:53 -0700468 }
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700469 }
470
Mindy Pereiraab486362012-03-21 18:18:53 -0700471 // TODO(mindyp): set this up to store a copy of the folder as a transient
472 // field in the account.
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700473 protected void loadAccountInbox() {
Vikram Aggarwal94c94de2012-04-04 15:38:28 -0700474 restartOptionalLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700475 }
476
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800477 /** Set the current folder */
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800478 private void setFolder(Folder folder) {
479 // Start watching folder for sync status.
Mindy Pereirae0458e82012-03-06 11:54:55 -0800480 if (folder != null && !folder.equals(mFolder)) {
Andy Huangb1c34dc2012-04-17 16:36:19 -0700481 final boolean folderWasNull = (mFolder == null);
482 final LoaderManager lm = mActivity.getLoaderManager();
Mindy Pereira9b623802012-03-07 17:15:49 -0800483 mActionBarView.setRefreshInProgress(false);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800484 mFolder = folder;
Mindy Pereiraf9323cd2012-02-29 13:47:09 -0800485 mActionBarView.setFolder(mFolder);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700486
487 // Only when we switch from one folder to another do we want to restart the
488 // folder and conversation list loaders (to trigger onCreateLoader).
489 // The first time this runs when the activity is [re-]initialized, we want to re-use the
490 // previous loader's instance and data upon configuration change (e.g. rotation).
491 if (folderWasNull) {
492 lm.initLoader(LOADER_FOLDER_CURSOR, null, this);
493 lm.initLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
494 } else {
495 lm.restartLoader(LOADER_FOLDER_CURSOR, null, this);
496 lm.restartLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
497 }
Mindy Pereirae0458e82012-03-06 11:54:55 -0800498 } else if (folder == null) {
499 LogUtils.wtf(LOG_TAG, "Folder in setFolder is null");
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800500 }
501 }
502
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800503 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800504 public void onActivityResult(int requestCode, int resultCode, Intent data) {
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700505 if (requestCode == ADD_ACCOUNT_REQUEST_CODE) {
506 // We were waiting for the user to create an account
507 if (resultCode == Activity.RESULT_OK) {
508 // restart the loader to get the updated list of accounts
509 mActivity.getLoaderManager().initLoader(
510 LOADER_ACCOUNT_CURSOR, null, this);
511 } else {
512 // The user failed to create an account, just exit the app
513 mActivity.finish();
514 }
515 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800516 }
517
518 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800519 public void onConversationListVisibilityChanged(boolean visible) {
520 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800521 }
522
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800523 /**
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800524 * By default, doing nothing is right. A two-pane controller will need to
525 * override this.
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800526 */
527 @Override
528 public void onConversationVisibilityChanged(boolean visible) {
529 // Do nothing.
530 return;
531 }
532
533 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800534 public boolean onCreate(Bundle savedState) {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800535 // Initialize the action bar view.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800536 initCustomActionBarView();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800537 // Allow shortcut keys to function for the ActionBar and menus.
538 mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800539 mResolver = mActivity.getContentResolver();
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800540
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700541 mNewEmailReceiver = new SuppressNotificationReceiver();
542
Mindy Pereira161f50d2012-02-28 15:47:19 -0800543 // All the individual UI components listen for ViewMode changes. This
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800544 // simplifies the amount of logic in the AbstractActivityController, but increases the
545 // possibility of timing-related bugs.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800546 mViewMode.addListener(this);
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800547 assert (mActionBarView != null);
548 mViewMode.addListener(mActionBarView);
549
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800550 restoreState(savedState);
Mindy Pereira0963ef82012-04-10 11:43:01 -0700551 mUndoBarView = (UndoBarView) mActivity.findViewById(R.id.undo_view);
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800552 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800553 }
554
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800555 @Override
Andy Huangb1c34dc2012-04-17 16:36:19 -0700556 public void onRestoreInstanceState(Bundle savedInstanceState) {
557 }
558
559 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800560 public Dialog onCreateDialog(int id, Bundle bundle) {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800561 return null;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800562 }
563
564 @Override
565 public boolean onCreateOptionsMenu(Menu menu) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800566 MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800567 inflater.inflate(mActionBarView.getOptionsMenuId(), menu);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800568 mActionBarView.onCreateOptionsMenu(menu);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800569 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800570 }
571
572 @Override
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800573 public boolean onKeyDown(int keyCode, KeyEvent event) {
574 // TODO(viki): Auto-generated method stub
575 return false;
576 }
577
578 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800579 public boolean onOptionsItemSelected(MenuItem item) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700580 final int id = item.getItemId();
Mindy Pereira9b875682012-02-15 18:10:54 -0800581 boolean handled = true;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800582 switch (id) {
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800583 case android.R.id.home:
584 onUpPressed();
585 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800586 case R.id.compose:
587 ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
588 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800589 case R.id.show_all_folders:
590 showFolderList();
591 break;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800592 case R.id.refresh:
593 requestFolderRefresh();
594 break;
Mindy Pereira1f936682012-03-02 11:30:33 -0800595 case R.id.settings:
596 Utils.showSettings(mActivity.getActivityContext(), mAccount);
Paul Westbrook2861b6a2012-02-15 15:25:34 -0800597 break;
Paul Westbrooke5503552012-03-28 00:35:57 -0700598 case R.id.folder_options:
599 Utils.showFolderSettings(mActivity.getActivityContext(), mAccount, mFolder);
600 break;
Paul Westbrook94e440d2012-02-24 11:03:47 -0800601 case R.id.help_info_menu_item:
602 // TODO: enable context sensitive help
Paul Westbrook498e76d2012-04-12 16:33:02 -0700603 Utils.showHelp(mActivity.getActivityContext(), mAccount, null);
Paul Westbrook94e440d2012-02-24 11:03:47 -0800604 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700605 case R.id.feedback_menu_item:
Mindy Pereirafbe40192012-03-20 10:40:45 -0700606 Utils.sendFeedback(mActivity.getActivityContext(), mAccount);
607 break;
Paul Westbrook18babd22012-04-09 22:17:08 -0700608 case R.id.manage_folders_item:
609 Utils.showManageFolder(mActivity.getActivityContext(), mAccount);
610 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800611 default:
612 handled = false;
613 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800614 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800615 return handled;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800616 }
617
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700618 /**
Mindy Pereira21ab4902012-03-19 18:48:03 -0700619 * Return the auto advance setting for the current account.
620 * @param activity
621 * @return the autoadvance setting, a constant from {@link AutoAdvance}
622 */
Vikram Aggarwalcfa50042012-04-13 13:15:39 -0700623 static int getAutoAdvanceSetting(Settings settings) {
Mindy Pereira21ab4902012-03-19 18:48:03 -0700624 // TODO(mindyp): if this isn't set, then show the dialog telling the user to set it.
625 // Remove defaulting to AutoAdvance.LIST.
626 final int autoAdvance = (settings != null) ?
627 (settings.autoAdvance == AutoAdvance.UNSET ?
628 AutoAdvance.LIST : settings.autoAdvance)
629 : AutoAdvance.LIST;
630 return autoAdvance;
631 }
632
633 /**
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700634 * Implements folder changes. This class is a listener because folder changes need to be
635 * performed <b>after</b> the ConversationListFragment has finished animating away the
636 * removal of the conversation.
637 *
638 */
Mindy Pereirae5f4dc02012-03-21 16:08:53 -0700639 protected abstract class FolderChangeListener implements ActionCompleteListener {
640 protected final String mFolderChangeList;
641 protected final boolean mDestructiveChange;
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700642
643 public FolderChangeListener(String changeList, boolean destructive) {
644 mFolderChangeList = changeList;
645 mDestructiveChange = destructive;
646 }
647
Vikram Aggarwal08935f52012-03-26 16:28:59 -0700648 @Override
Mindy Pereirae5f4dc02012-03-21 16:08:53 -0700649 public abstract void onActionComplete();
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700650 }
651
652 /**
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700653 * Update the specified column name in conversation for a boolean value.
654 * @param columnName
655 * @param value
656 */
Mindy Pereirafbe40192012-03-20 10:40:45 -0700657 protected void updateCurrentConversation(String columnName, boolean value) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700658 Conversation.updateBoolean(mContext, ImmutableList.of(mCurrentConversation), columnName,
659 value);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700660 final ConversationListFragment convList = getConversationListFragment();
661 if (convList != null) {
662 convList.requestListRefresh();
Mindy Pereirac9d59182012-03-22 16:06:46 -0700663 }
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700664 }
665
666 /**
667 * Update the specified column name in conversation for an integer value.
668 * @param columnName
669 * @param value
670 */
Mindy Pereirafbe40192012-03-20 10:40:45 -0700671 protected void updateCurrentConversation(String columnName, int value) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700672 Conversation.updateInt(mContext, ImmutableList.of(mCurrentConversation), columnName, value);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700673 final ConversationListFragment convList = getConversationListFragment();
674 if (convList != null) {
675 convList.requestListRefresh();
Mindy Pereirac9d59182012-03-22 16:06:46 -0700676 }
677 }
678
679 protected void updateCurrentConversation(String columnName, String value) {
680 Conversation.updateString(mContext, ImmutableList.of(mCurrentConversation), columnName,
681 value);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700682 final ConversationListFragment convList = getConversationListFragment();
683 if (convList != null) {
684 convList.requestListRefresh();
Mindy Pereirac9d59182012-03-22 16:06:46 -0700685 }
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700686 }
687
Mindy Pereira28e0c342012-02-17 15:05:13 -0800688 private void requestFolderRefresh() {
689 if (mFolder != null) {
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800690 if (mAsyncRefreshTask != null) {
691 mAsyncRefreshTask.cancel(true);
692 }
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800693 mAsyncRefreshTask = new AsyncRefreshTask(mContext, mFolder);
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800694 mAsyncRefreshTask.execute();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800695 }
696 }
697
Mindy Pereirafbe40192012-03-20 10:40:45 -0700698 /**
699 * Confirm (based on user's settings) and delete a conversation from the conversation list and
700 * from the database.
701 * @param showDialog
702 * @param confirmResource
703 * @param listener
704 */
705 protected void confirmAndDelete(boolean showDialog, int confirmResource,
706 final ActionCompleteListener listener) {
707 final ArrayList<Conversation> single = new ArrayList<Conversation>();
708 single.add(mCurrentConversation);
709 if (showDialog) {
710 final AlertDialog.OnClickListener onClick = new AlertDialog.OnClickListener() {
711 @Override
712 public void onClick(DialogInterface dialog, int which) {
713 requestDelete(listener);
714 }
715 };
716 final CharSequence message = Utils.formatPlural(mContext, confirmResource, 1);
717 new AlertDialog.Builder(mActivity.getActivityContext()).setMessage(message)
718 .setPositiveButton(R.string.ok, onClick)
719 .setNegativeButton(R.string.cancel, null)
720 .create().show();
721 } else {
722 requestDelete(listener);
723 }
724 }
725
726
727 protected abstract void requestDelete(ActionCompleteListener listener);
728
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700729
730 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800731 public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
732 // TODO(viki): Auto-generated method stub
733
734 }
735
736 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800737 public boolean onPrepareOptionsMenu(Menu menu) {
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800738 mActionBarView.onPrepareOptionsMenu(menu);
Mindy Pereira363451a2012-02-22 14:14:46 -0800739 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800740 }
741
Mindy Pereira68f2e222012-03-07 10:36:54 -0800742 @Override
743 public void onPause() {
744 isLoaderInitialized = false;
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700745
746 enableNotifications();
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700747 commitLeaveBehindItems();
Paul Westbrook94e440d2012-02-24 11:03:47 -0800748 }
749
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800750 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800751 public void onResume() {
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700752 // Register the receiver that will prevent the status receiver from
753 // displaying its notification icon as long as we're running.
754 // The SupressNotificationReceiver will block the broadcast if we're looking at the folder
755 // that the notification was received for.
756 disableNotifications();
757
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800758 if (mActionBarView != null) {
759 mActionBarView.onResume();
760 }
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700761
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800762 }
763
764 @Override
765 public void onSaveInstanceState(Bundle outState) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800766 if (mAccount != null) {
767 LogUtils.d(LOG_TAG, "Saving the account now");
768 outState.putParcelable(SAVED_ACCOUNT, mAccount);
769 }
Mindy Pereira5e478d22012-03-26 18:04:58 -0700770 if (mFolder != null) {
771 outState.putParcelable(SAVED_FOLDER, mFolder);
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800772 }
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700773 if (mCurrentConversation != null && mViewMode.getMode() == ViewMode.CONVERSATION) {
774 outState.putParcelable(SAVED_CONVERSATION, mCurrentConversation);
775 }
Andy Huang4556a442012-03-30 16:42:05 -0700776 if (!mSelectedSet.isEmpty()) {
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700777 outState.putParcelable(SAVED_SELECTED_SET, mSelectedSet);
Andy Huang4556a442012-03-30 16:42:05 -0700778 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800779 }
780
781 @Override
Mindy Pereira68f2e222012-03-07 10:36:54 -0800782 public void onSearchRequested(String query) {
783 Intent intent = new Intent();
784 intent.setAction(Intent.ACTION_SEARCH);
785 intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query);
786 intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount);
787 intent.setComponent(mActivity.getComponentName());
Vikram Aggarwalb17cbc02012-04-06 15:41:46 -0700788 mActionBarView.collapseSearch();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800789 mActivity.startActivity(intent);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800790 }
791
792 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800793 public void onStartDragMode() {
794 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800795 }
796
797 @Override
798 public void onStop() {
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700799 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800800 }
801
802 @Override
803 public void onStopDragMode() {
804 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800805 }
806
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800807 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800808 * {@inheritDoc} Subclasses must override this to listen to mode changes
809 * from the ViewMode. Subclasses <b>must</b> call the parent's
810 * onViewModeChanged since the parent will handle common state changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800811 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800812 @Override
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800813 public void onViewModeChanged(int newMode) {
814 // Perform any mode specific work here.
Mindy Pereira161f50d2012-02-28 15:47:19 -0800815 // reset the action bar icon based on the mode. Why don't the individual
816 // controllers do
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800817 // this themselves?
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800818
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800819 // In conversation list mode, clean up the conversation.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800820 if (newMode == ViewMode.CONVERSATION_LIST) {
821 // Clean up the conversation here.
822 }
823
Mindy Pereira161f50d2012-02-28 15:47:19 -0800824 // We don't want to invalidate the options menu when switching to
825 // conversation
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800826 // mode, as it will happen when the conversation finishes loading.
827 if (newMode != ViewMode.CONVERSATION) {
828 mActivity.invalidateOptionsMenu();
829 }
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800830 }
831
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700832 protected void commitLeaveBehindItems() {
833 ConversationListFragment fragment = getConversationListFragment();
834 if (fragment != null) {
835 fragment.commitLeaveBehindItems();
836 }
837 }
838
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800839 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800840 public void onWindowFocusChanged(boolean hasFocus) {
841 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800842 }
843
Mindy Pereira75181e82012-04-18 08:17:13 -0700844 private void setAccount(Account account) {
845 mAccount = account;
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700846 dispatchSettingsChange(mAccount.settings);
Mindy Pereira75181e82012-04-18 08:17:13 -0700847 mActionBarView.setAccount(mAccount);
848 }
849
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800850 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800851 * Restore the state from the previous bundle. Subclasses should call this
852 * method from the parent class, since it performs important UI
853 * initialization.
854 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800855 * @param savedState
856 */
857 protected void restoreState(Bundle savedState) {
Mindy Pereira68f2e222012-03-07 10:36:54 -0800858 final Intent intent = mActivity.getIntent();
Mindy Pereira4efdbe62012-04-03 11:18:04 -0700859 boolean handled = false;
Mindy Pereira5e478d22012-03-26 18:04:58 -0700860 if (savedState != null) {
861 if (savedState.containsKey(SAVED_ACCOUNT)) {
Mindy Pereira75181e82012-04-18 08:17:13 -0700862 setAccount((Account) savedState.getParcelable(SAVED_ACCOUNT));
863 mActivity.invalidateOptionsMenu();
Mindy Pereira5e478d22012-03-26 18:04:58 -0700864 }
865 if (savedState.containsKey(SAVED_FOLDER)) {
866 // Open the folder.
Mindy Pereira5e478d22012-03-26 18:04:58 -0700867 onFolderChanged((Folder) savedState.getParcelable(SAVED_FOLDER));
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700868 handled = true;
869 }
870 if (savedState.containsKey(SAVED_CONVERSATION)) {
871 // Open the conversation.
872 setCurrentConversation((Conversation) savedState.getParcelable(SAVED_CONVERSATION));
873 showConversation(mCurrentConversation);
874 handled = true;
875 }
Mindy Pereirab283d802012-03-27 16:05:39 -0700876 } else if (intent != null) {
Mindy Pereira68f2e222012-03-07 10:36:54 -0800877 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Mindy Pereirad8542ad2012-02-29 13:54:17 -0800878 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
Mindy Pereira75181e82012-04-18 08:17:13 -0700879 setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
Mindy Pereirac7968872012-04-02 13:53:35 -0700880 } else if (intent.hasExtra(Utils.EXTRA_ACCOUNT_STRING)) {
Mindy Pereira75181e82012-04-18 08:17:13 -0700881 setAccount(Account.newinstance(intent
Vikram Aggarwale1448372012-04-11 14:52:48 -0700882 .getStringExtra(Utils.EXTRA_ACCOUNT_STRING)));
Mindy Pereirad8542ad2012-02-29 13:54:17 -0800883 }
Mindy Pereira75181e82012-04-18 08:17:13 -0700884 if (mAccount != null) {
885 mActivity.invalidateOptionsMenu();
886 }
Mindy Pereirac7968872012-04-02 13:53:35 -0700887
Mindy Pereira4efdbe62012-04-03 11:18:04 -0700888 Folder folder = null;
Mindy Pereirad8542ad2012-02-29 13:54:17 -0800889 if (intent.hasExtra(Utils.EXTRA_FOLDER)) {
890 // Open the folder.
891 LogUtils.d(LOG_TAG, "SHOW THE FOLDER at %s",
892 intent.getParcelableExtra(Utils.EXTRA_FOLDER));
Mindy Pereira4efdbe62012-04-03 11:18:04 -0700893 folder = (Folder) intent.getParcelableExtra(Utils.EXTRA_FOLDER);
894
Mindy Pereirac7968872012-04-02 13:53:35 -0700895 } else if (intent.hasExtra(Utils.EXTRA_FOLDER_STRING)) {
896 // Open the folder.
Mindy Pereira4efdbe62012-04-03 11:18:04 -0700897 folder = new Folder(intent.getStringExtra(Utils.EXTRA_FOLDER_STRING));
898 }
899 if (folder != null) {
900 onFolderChanged(folder);
901 handled = true;
Mindy Pereirad8542ad2012-02-29 13:54:17 -0800902 }
Mindy Pereirac7968872012-04-02 13:53:35 -0700903
Mindy Pereira161f50d2012-02-28 15:47:19 -0800904 if (intent.hasExtra(Utils.EXTRA_CONVERSATION)) {
905 // Open the conversation.
906 LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
907 intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
Mindy Pereira5040f1a2012-03-20 10:14:06 -0700908 setCurrentConversation((Conversation) intent
Mindy Pereira161f50d2012-02-28 15:47:19 -0800909 .getParcelableExtra(Utils.EXTRA_CONVERSATION));
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700910 showConversation(mCurrentConversation);
Mindy Pereira4efdbe62012-04-03 11:18:04 -0700911 handled = true;
Mindy Pereira161f50d2012-02-28 15:47:19 -0800912 }
Mindy Pereira4efdbe62012-04-03 11:18:04 -0700913
914 if (!handled) {
915 // Nothing was saved; just load the account inbox.
916 loadAccountInbox();
917 }
Mindy Pereira68f2e222012-03-07 10:36:54 -0800918 } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
Paul Westbrook91d10502012-04-13 12:01:39 -0700919 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
920 // Save this search query for future suggestions.
921 final String query = intent.getStringExtra(SearchManager.QUERY);
922 final String authority = mContext.getString(R.string.suggestions_authority);
923 SearchRecentSuggestions suggestions = new SearchRecentSuggestions(
924 mContext, authority, SuggestionsProvider.MODE);
925 suggestions.saveRecentQuery(query, null);
Vikram Aggarwale620a7a2012-03-28 13:16:14 -0700926
Paul Westbrook91d10502012-04-13 12:01:39 -0700927 mViewMode.enterSearchResultsListMode();
Mindy Pereira75181e82012-04-18 08:17:13 -0700928 setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
Paul Westbrook91d10502012-04-13 12:01:39 -0700929 mActivity.invalidateOptionsMenu();
930 restartOptionalLoader(LOADER_RECENT_FOLDERS);
931 mRecentFolderList.setCurrentAccount(mAccount);
932 fetchSearchFolder(intent);
933 } else {
934 LogUtils.e(LOG_TAG, "Missing account extra from search intent. Finishing");
935 mActivity.finish();
936 }
Mindy Pereira161f50d2012-02-28 15:47:19 -0800937 }
Paul Westbrook2d50bcd2012-04-10 11:53:47 -0700938 if (mAccount != null) {
939 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
940 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800941 }
Mindy Pereira967ede62012-03-22 09:29:09 -0700942
943 /**
944 * Restore the state of selected conversations. This needs to be done after the correct mode
945 * is set and the action bar is fully initialized. If not, several key pieces of state
946 * information will be missing, and the split views may not be initialized correctly.
947 * @param savedState
948 */
Andy Huang4556a442012-03-30 16:42:05 -0700949 restoreSelectedConversations(savedState);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800950 // Create the accounts loader; this loads the account switch spinner.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800951 mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800952 }
953
Andy Huang4556a442012-03-30 16:42:05 -0700954 /**
955 * Copy any selected conversations stored in the saved bundle into our selection set,
956 * triggering {@link ConversationSetObserver} callbacks as our selection set changes.
957 *
958 */
Mindy Pereira967ede62012-03-22 09:29:09 -0700959 private void restoreSelectedConversations(Bundle savedState) {
960 if (savedState == null) {
Andy Huang4556a442012-03-30 16:42:05 -0700961 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -0700962 return;
963 }
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700964 final ConversationSelectionSet selectedSet = savedState.getParcelable(SAVED_SELECTED_SET);
Andy Huang4556a442012-03-30 16:42:05 -0700965 if (selectedSet == null || selectedSet.isEmpty()) {
966 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -0700967 return;
968 }
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700969 mSelectedSet.putAll(selectedSet);
Mindy Pereira967ede62012-03-22 09:29:09 -0700970 }
971
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800972 @Override
973 public void setSubject(String subject) {
974 // Do something useful with the subject. This requires changing the
975 // conversation view's subject text.
976 }
977
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800978 /**
979 * Children can override this method, but they must call super.showConversation().
980 * {@inheritDoc}
981 */
982 @Override
983 public void showConversation(Conversation conversation) {
Vikram Aggarwalc67182d2012-04-03 14:35:06 -0700984 // Set the current conversation just in case it wasn't already set.
985 setCurrentConversation(conversation);
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800986 }
987
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700988 /**
Paul Westbrook2d50bcd2012-04-10 11:53:47 -0700989 * Children can override this method, but they must call super.showWaitForInitialization().
990 * {@inheritDoc}
991 */
992 @Override
993 public void showWaitForInitialization() {
994 mViewMode.enterWaitingForInitializationMode();
995 }
996
997 @Override
998 public void hideWaitForInitialization() {
999 }
1000
1001 @Override
1002 public void updateWaitMode() {
1003 final FragmentManager manager = mActivity.getFragmentManager();
1004 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001005 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001006 if (waitFragment != null) {
1007 waitFragment.updateAccount(mAccount);
1008 }
1009 }
1010
1011 @Override
1012 public boolean inWaitMode() {
1013 final FragmentManager manager = mActivity.getFragmentManager();
1014 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001015 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001016 if (waitFragment != null) {
1017 final Account fragmentAccount = waitFragment.getAccount();
1018 return fragmentAccount.uri.equals(mAccount.uri) &&
1019 mViewMode.getMode() == ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION;
1020 }
1021 return false;
1022 }
1023
1024 /**
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001025 * Children can override this method, but they must call super.showConversationList().
1026 * {@inheritDoc}
1027 */
1028 @Override
1029 public void showConversationList(ConversationListContext listContext) {
1030 }
1031
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001032 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -08001033 public void onConversationSelected(Conversation conversation) {
Vikram Aggarwal5b7b3ab2012-04-03 15:43:55 -07001034 showConversation(conversation);
Mindy Pereira68f2e222012-03-07 10:36:54 -08001035 if (mConvListContext != null && mConvListContext.isSearchResult()) {
1036 mViewMode.enterSearchResultsConversationMode();
1037 } else {
1038 mViewMode.enterConversationMode();
1039 }
Vikram Aggarwal7d602882012-02-07 15:01:20 -08001040 }
Mindy Pereira555140c2012-02-15 14:55:29 -08001041
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001042 /**
1043 * Set the current conversation. This is the conversation on which all actions are performed.
1044 * Do not modify mCurrentConversation except through this method, which makes it easy to
1045 * perform common actions associated with changing the current conversation.
1046 * @param conversation
1047 */
1048 protected void setCurrentConversation(Conversation conversation) {
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001049 mCurrentConversation = conversation;
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001050 mTracker.initialize(mCurrentConversation);
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001051 }
1052
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001053 /**
1054 * {@inheritDoc}
1055 */
Mindy Pereira555140c2012-02-15 14:55:29 -08001056 @Override
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001057 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
1058 // Create a loader to listen in on account changes.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001059 switch (id) {
1060 case LOADER_ACCOUNT_CURSOR:
Paul Westbrookc2074c42012-03-22 15:26:58 -07001061 return new CursorLoader(mContext, MailAppProvider.getAccountsUri(),
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001062 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
1063 case LOADER_FOLDER_CURSOR:
Paul Westbrookc7a070f2012-04-12 01:46:41 -07001064 final CursorLoader loader = new CursorLoader(mContext, mFolder.uri,
1065 UIProvider.FOLDERS_PROJECTION, null, null, null);
1066 loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
1067 return loader;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001068 case LOADER_RECENT_FOLDERS:
Paul Westbrook91d10502012-04-13 12:01:39 -07001069 if (mAccount != null && mAccount.recentFolderListUri != null) {
Paul Westbrookea4ee202012-03-12 14:12:33 -07001070 return new CursorLoader(mContext, mAccount.recentFolderListUri,
1071 UIProvider.FOLDERS_PROJECTION, null, null, null);
1072 }
1073 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001074 case LOADER_ACCOUNT_INBOX:
Paul Westbrook1220b6d2012-04-10 00:48:00 -07001075 final Uri inboxUri;
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001076 if (mCachedSettings != null) {
1077 inboxUri = mCachedSettings.defaultInbox;
Mindy Pereiraab486362012-03-21 18:18:53 -07001078 } else {
1079 inboxUri = mAccount.folderListUri;
1080 }
Paul Westbrook1220b6d2012-04-10 00:48:00 -07001081 if (inboxUri != null) {
1082 return new CursorLoader(mContext, inboxUri, UIProvider.FOLDERS_PROJECTION, null,
1083 null, null);
1084 }
1085 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001086 case LOADER_SEARCH:
1087 return Folder.forSearchResults(mAccount,
1088 args.getString(ConversationListContext.EXTRA_SEARCH_QUERY),
1089 mActivity.getActivityContext());
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001090 case LOADER_ACCOUNT_UPDATE_CURSOR:
1091 return new CursorLoader(mContext, mAccount.uri, UIProvider.ACCOUNTS_PROJECTION,
1092 null, null, null);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001093 default:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001094 LogUtils.wtf(LOG_TAG, "Loader returned unexpected id: %d", id);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001095 }
1096 return null;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001097 }
1098
Paul Westbrookb1f573c2012-04-06 11:38:28 -07001099 @Override
1100 public void onLoaderReset(Loader<Cursor> loader) {
1101
1102 }
1103
Andy Huangf9a73482012-03-13 15:54:02 -07001104 /**
1105 * {@link LoaderManager} currently has a bug in
1106 * {@link LoaderManager#restartLoader(int, Bundle, android.app.LoaderManager.LoaderCallbacks)}
1107 * where, if a previous onCreateLoader returned a null loader, this method will NPE. Work around
1108 * this bug by destroying any loaders that may have been created as null (essentially because
1109 * they are optional loads, and may not apply to a particular account).
1110 * <p>
1111 * A simple null check before restarting a loader will not work, because that would not
1112 * give the controller a chance to invalidate UI corresponding the prior loader result.
1113 *
1114 * @param id loader ID to safely restart
Andy Huangf9a73482012-03-13 15:54:02 -07001115 */
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001116 private void restartOptionalLoader(int id) {
Andy Huangf9a73482012-03-13 15:54:02 -07001117 final LoaderManager lm = mActivity.getLoaderManager();
1118 lm.destroyLoader(id);
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001119 lm.restartLoader(id, Bundle.EMPTY, this);
1120 }
1121
1122 /**
1123 * Start a loader with the given id. This should be called when we know that the previous
1124 * state of the application matches this state, and we are happy if we get the previously
1125 * created loader with this id. If that is not true, consider calling
1126 * {@link #restartOptionalLoader(int)} instead.
1127 * @param id
1128 */
1129 private void startLoader(int id) {
1130 final LoaderManager lm = mActivity.getLoaderManager();
1131 lm.initLoader(id, Bundle.EMPTY, this);
Andy Huangf9a73482012-03-13 15:54:02 -07001132 }
1133
Paul Westbrook23b74b92012-02-29 11:36:12 -08001134 private boolean accountsUpdated(Cursor accountCursor) {
1135 // Check to see if the current account hasn't been set, or the account cursor is empty
1136 if (mAccount == null || !accountCursor.moveToFirst()) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001137 return true;
Paul Westbrook23b74b92012-02-29 11:36:12 -08001138 }
1139
1140 // Check to see if the number of accounts are different, from the number we saw on the last
1141 // updated
1142 if (mCurrentAccountUris.size() != accountCursor.getCount()) {
1143 return true;
1144 }
1145
1146 // Check to see if the account list is different or if the current account is not found in
1147 // the cursor.
1148 boolean foundCurrentAccount = false;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001149 do {
Paul Westbrook23b74b92012-02-29 11:36:12 -08001150 final Uri accountUri =
1151 Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
1152 if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
1153 foundCurrentAccount = true;
1154 }
1155
1156 if (!mCurrentAccountUris.contains(accountUri)) {
1157 return true;
1158 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001159 } while (accountCursor.moveToNext());
Paul Westbrook23b74b92012-02-29 11:36:12 -08001160
1161 // As long as we found the current account, the list hasn't been updated
1162 return !foundCurrentAccount;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001163 }
1164
1165 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -08001166 * Update the accounts on the device. This currently loads the first account
1167 * in the list.
1168 *
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001169 * @param loader
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001170 * @param accounts cursor into the AccountCache
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001171 * @return true if the update was successful, false otherwise
1172 */
Paul Westbrook23b74b92012-02-29 11:36:12 -08001173 private boolean updateAccounts(Loader<Cursor> loader, Cursor accounts) {
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001174 if (accounts == null || !accounts.moveToFirst()) {
1175 return false;
1176 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001177
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001178 final Account[] allAccounts = Account.getAllAccounts(accounts);
Paul Westbrook23b74b92012-02-29 11:36:12 -08001179
1180 // Save the uris for the accounts
1181 mCurrentAccountUris.clear();
1182 for (Account account : allAccounts) {
1183 mCurrentAccountUris.add(account.uri);
1184 }
1185
Andy Huang0d647352012-03-21 21:48:16 -07001186 // 1. current account is already set and is in allAccounts -> no-op
1187 // 2. current account is set and is not in allAccounts -> pick first (acct was deleted?)
1188 // 3. saved pref has an account -> pick that one
1189 // 4. otherwise just pick first
1190
1191 Account newAccount = null;
1192
1193 if (mAccount != null) {
1194 if (!mCurrentAccountUris.contains(mAccount.uri)) {
1195 newAccount = allAccounts[0];
1196 } else {
1197 newAccount = mAccount;
1198 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001199 } else {
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001200 final String lastAccountUri = MailAppProvider.getInstance().getLastViewedAccount();
Andy Huang0d647352012-03-21 21:48:16 -07001201 if (lastAccountUri != null) {
1202 for (int i = 0; i < allAccounts.length; i++) {
1203 final Account acct = allAccounts[i];
1204 if (lastAccountUri.equals(acct.uri.toString())) {
1205 newAccount = acct;
1206 break;
1207 }
1208 }
1209 }
1210 if (newAccount == null) {
1211 newAccount = allAccounts[0];
1212 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001213 }
Andy Huang0d647352012-03-21 21:48:16 -07001214
Paul Westbrook23b74b92012-02-29 11:36:12 -08001215 onAccountChanged(newAccount);
1216
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001217 mActionBarView.setAccounts(allAccounts);
1218 return (allAccounts.length > 0);
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001219 }
1220
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001221 private void disableNotifications() {
1222 mNewEmailReceiver.activate(mContext, this);
1223 }
1224
1225 private void enableNotifications() {
1226 mNewEmailReceiver.deactivate();
1227 }
1228
1229 private void disableNotificationsOnAccountChange(Account account) {
1230 // If the new mail suppression receiver is activated for a different account, we want to
1231 // activate it for the new account.
1232 if (mNewEmailReceiver.activated() &&
1233 !mNewEmailReceiver.notificationsDisabledForAccount(account)) {
1234 // Deactivate the current receiver, otherwise multiple receivers may be registered.
1235 mNewEmailReceiver.deactivate();
1236 mNewEmailReceiver.activate(mContext, this);
1237 }
1238 }
1239
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001240 /**
1241 * {@inheritDoc}
1242 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001243 @Override
1244 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001245 // We want to reinitialize only if we haven't ever been initialized, or
1246 // if the current account has vanished.
Paul Westbrooke3e84292012-03-05 16:19:30 -08001247 if (data == null) {
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001248 LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
Paul Westbrooke3e84292012-03-05 16:19:30 -08001249 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001250 switch (loader.getId()) {
1251 case LOADER_ACCOUNT_CURSOR:
Paul Westbrook2388c5d2012-03-25 12:29:11 -07001252 // If the account list is not not null, and the account list cursor is empty,
1253 // we need to start the specified activity.
1254 if (data != null && data.getCount() == 0) {
1255 // If an empty cursor is returned, the MailAppProvider is indicating that
1256 // no accounts have been specified. We want to navigate to the "add account"
1257 // activity that will handle the intent returned by the MailAppProvider
1258
1259 // If the MailAppProvider believes that all accounts have been loaded, and the
1260 // account list is still empty, we want to prompt the user to add an account
1261 final Bundle extras = data.getExtras();
1262 final boolean accountsLoaded =
1263 extras.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
1264
1265 if (accountsLoaded) {
1266 final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(mContext);
1267 if (noAccountIntent != null) {
1268 mActivity.startActivityForResult(noAccountIntent,
1269 ADD_ACCOUNT_REQUEST_CODE);
1270 }
1271 }
1272 } else {
1273 final boolean accountListUpdated = accountsUpdated(data);
1274 if (!isLoaderInitialized || accountListUpdated) {
1275 isLoaderInitialized = updateAccounts(loader, data);
1276 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001277 }
1278 break;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001279 case LOADER_ACCOUNT_UPDATE_CURSOR:
1280 // We have gotten an update for current account.
1281
1282 // Make sure that this is an update for what is the current account
1283 if (data != null && data.moveToFirst()) {
1284 final Account updatedAccount = new Account(data);
1285
1286 if (updatedAccount.uri.equals(mAccount.uri)) {
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001287 // Update the controller's reference to the current account
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001288 mAccount = updatedAccount;
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001289 dispatchSettingsChange(mAccount.settings);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001290
1291 // Got an update for the current account
1292 final boolean inWaitingMode = inWaitMode();
1293 if (!updatedAccount.isAccountIntialized() && !inWaitingMode) {
1294 // Transition to waiting mode
1295 showWaitForInitialization();
1296 } else if (updatedAccount.isAccountIntialized() && inWaitingMode) {
1297 // Dismiss waiting mode
1298 hideWaitForInitialization();
1299 } else if (!updatedAccount.isAccountIntialized() && inWaitingMode) {
1300 // Update the WaitFragment's account object
1301 updateWaitMode();
1302 }
1303 } else {
1304 LogUtils.e(LOG_TAG, "Got update for account: %s with current account: %s",
1305 updatedAccount.uri, mAccount.uri);
1306 // We need to restart the loader, so the correct account information will
1307 // be returned
1308 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1309 }
1310 }
1311 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001312 case LOADER_FOLDER_CURSOR:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001313 // Check status of the cursor.
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001314 data.moveToFirst();
1315 Folder folder = new Folder(data);
Mindy Pereira11dd5ef2012-03-10 15:10:18 -08001316 if (folder.isSyncInProgress()) {
1317 mActionBarView.onRefreshStarted();
Paul Westbrookc808fac2012-02-22 16:42:18 -08001318 } else {
Mindy Pereira11dd5ef2012-03-10 15:10:18 -08001319 // Stop the spinner here.
1320 mActionBarView.onRefreshStopped(folder.lastSyncResult);
1321 }
Mindy Pereiraa46a57f2012-04-03 17:19:31 -07001322 mActionBarView.onFolderUpdated(folder);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001323 final ConversationListFragment convList = getConversationListFragment();
1324 if (convList != null) {
1325 convList.onFolderUpdated(folder);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001326 }
Marc Blanke1d1b072012-04-13 17:29:16 -07001327 LogUtils.d(LOG_TAG, "FOLDER STATUS = %d", folder.syncStatus);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001328 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001329 case LOADER_RECENT_FOLDERS:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001330 mRecentFolderList.loadFromUiProvider(data);
Vikram Aggarwal37263972012-04-17 15:51:14 -07001331 mActionBarView.requestRecentFoldersAndRedraw();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001332 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001333 case LOADER_ACCOUNT_INBOX:
Mindy Pereira0e88e9f2012-03-25 13:47:41 -07001334 if (data.moveToFirst() && !data.isClosed()) {
1335 Folder inbox = new Folder(data);
1336 onFolderChanged(inbox);
Mindy Pereirab4a43282012-03-23 16:20:03 -07001337 // Just want to get the inbox, don't care about updates to it
1338 // as this will be tracked by the folder change listener.
1339 mActivity.getLoaderManager().destroyLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereira5ba33802012-03-26 16:30:11 -07001340 } else {
1341 LogUtils.d(LOG_TAG, "Unable to get the account inbox for account %s",
1342 mAccount != null ? mAccount.name : "");
Mindy Pereirab4a43282012-03-23 16:20:03 -07001343 }
Mindy Pereiraab486362012-03-21 18:18:53 -07001344 break;
1345 case LOADER_SEARCH:
1346 data.moveToFirst();
1347 Folder search = new Folder(data);
1348 setFolder(search);
1349 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder,
Mindy Pereirad660d252012-03-26 11:48:43 -07001350 mActivity.getIntent()
1351 .getStringExtra(UIProvider.SearchQueryParameters.QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -07001352 showConversationList(mConvListContext);
1353 mActivity.invalidateOptionsMenu();
Mindy Pereira3b399222012-03-28 15:19:47 -07001354 mActivity.getLoaderManager().destroyLoader(LOADER_SEARCH);
Mindy Pereiraab486362012-03-21 18:18:53 -07001355 break;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001356 }
1357 }
1358
Mindy Pereirafbe40192012-03-20 10:40:45 -07001359 protected abstract class DestructiveActionListener implements ActionCompleteListener {
1360 protected final int mAction;
1361
1362 /**
1363 * Create a listener object. action is one of four constants: R.id.y_button (archive),
1364 * R.id.delete , R.id.mute, and R.id.report_spam.
1365 * @param action
1366 */
1367 public DestructiveActionListener(int action) {
1368 mAction = action;
1369 }
1370
1371 public void performConversationAction(Collection<Conversation> single) {
1372 switch (mAction) {
Mindy Pereira0692baf2012-03-23 17:34:31 -07001373 case R.id.archive:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001374 LogUtils.d(LOG_TAG, "Archiving conversation %s", mCurrentConversation);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001375 Conversation.archive(mContext, single);
1376 break;
1377 case R.id.delete:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001378 LogUtils.d(LOG_TAG, "Deleting conversation %s", mCurrentConversation);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001379 Conversation.delete(mContext, single);
1380 break;
1381 case R.id.mute:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001382 LogUtils.d(LOG_TAG, "Muting conversation %s", mCurrentConversation);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001383 if (mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE))
1384 mCurrentConversation.localDeleteOnUpdate = true;
1385 Conversation.mute(mContext, single);
1386 break;
1387 case R.id.report_spam:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001388 LogUtils.d(LOG_TAG, "reporting spam conversation %s", mCurrentConversation);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001389 Conversation.reportSpam(mContext, single);
1390 break;
1391 }
1392 }
1393
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -07001394 /**
1395 * Get the next conversation according to the AutoAdvance settings and the list of
1396 * conversations available in the folder.
1397 * @return
1398 */
Mindy Pereirafbe40192012-03-20 10:40:45 -07001399 public Conversation getNextConversation() {
Vikram Aggarwalcfa50042012-04-13 13:15:39 -07001400 final int pref = getAutoAdvanceSetting(mCachedSettings);
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -07001401 final boolean getNewer = (pref == AutoAdvance.NEWER && mTracker.hasNewer());
1402 final boolean getOlder = (pref == AutoAdvance.OLDER && mTracker.hasOlder());
1403 final Conversation next = getNewer ? mTracker.getNewer() :
1404 (getOlder ? mTracker.getOlder() : null);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001405 return next;
1406 }
1407
1408 @Override
1409 public abstract void onActionComplete();
1410 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001411
1412 // Called from the FolderSelectionDialog after a user is done changing
1413 // folders.
1414 @Override
Mindy Pereira6778f462012-03-23 18:01:55 -07001415 public void onFolderChangesCommit(ArrayList<Folder> folderChangeList) {
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001416 // Get currently active folder info and compare it to the list
1417 // these conversations have been given; if they no longer contain
1418 // the selected folder, delete them from the list.
1419 HashSet<String> folderUris = new HashSet<String>();
Mindy Pereira6778f462012-03-23 18:01:55 -07001420 if (folderChangeList != null && !folderChangeList.isEmpty()) {
1421 for (Folder f : folderChangeList) {
1422 folderUris.add(f.uri.toString());
1423 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001424 }
Mindy Pereirac9d59182012-03-22 16:06:46 -07001425 final boolean destructiveChange = !folderUris.contains(mFolder.uri.toString());
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001426 DestructiveActionListener listener = getFolderDestructiveActionListener();
Mindy Pereira6778f462012-03-23 18:01:55 -07001427 StringBuilder foldersUrisString = new StringBuilder();
1428 boolean first = true;
1429 for (Folder f : folderChangeList) {
1430 if (first) {
1431 first = false;
1432 } else {
1433 foldersUrisString.append(',');
1434 }
1435 foldersUrisString.append(f.uri.toString());
1436 }
1437 updateCurrentConversation(ConversationColumns.FOLDER_LIST, foldersUrisString.toString());
1438 updateCurrentConversation(ConversationColumns.RAW_FOLDERS,
Mindy Pereira5b84f412012-03-25 12:45:14 -07001439 Folder.getSerializedFolderString(mFolder, folderChangeList));
Mindy Pereira6778f462012-03-23 18:01:55 -07001440 // TODO: (mindyp): set ConversationColumns.RAW_FOLDERS like in
1441 // SelectedConversationsActionMenu
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001442 if (destructiveChange) {
1443 mCurrentConversation.localDeleteOnUpdate = true;
Mindy Pereirac9d59182012-03-22 16:06:46 -07001444 requestDelete(listener);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001445 } else {
1446 final ConversationListFragment convList = getConversationListFragment();
1447 if (convList != null) {
1448 convList.requestListRefresh();
1449 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001450 }
1451 }
1452
1453 protected abstract DestructiveActionListener getFolderDestructiveActionListener();
Mindy Pereira967ede62012-03-22 09:29:09 -07001454
1455 @Override
Marc Blankbf128eb2012-04-18 15:58:45 -07001456 public void onRefreshRequired() {
1457 if (mIsConversationListScrolling) {
1458 LogUtils.d(LOG_TAG, "onRefreshRequired: delay until scrolling done");
1459 return;
1460 }
1461 // Refresh the query in the background
1462 long now = System.currentTimeMillis();
1463 long sinceLastRefresh = now - mConversationListRefreshTime;
1464// if (sinceLastRefresh > CONVERSATION_LIST_THROTTLE_MS) {
1465 if (getConversationListCursor().isRefreshRequired()) {
1466 getConversationListCursor().refresh();
1467 mTracker.updateCursor(mConversationListCursor);
1468 mConversationListRefreshTime = now;
1469 }
1470// } else {
1471// long delay = CONVERSATION_LIST_THROTTLE_MS - sinceLastRefresh;
1472// LogUtils.d(LOG_TAG, "onRefreshRequired: delay for %sms", delay);
1473// mConversationListRefreshTask = new RefreshTimerTask(this, mHandler);
1474// mConversationListTimer.schedule(mConversationListRefreshTask, delay);
1475// }
1476 }
1477
1478 /**
1479 * Called when the {@link ConversationCursor} is changed or has new data in it.
1480 * <p>
1481 * {@inheritDoc}
1482 */
1483 @Override
1484 public void onRefreshReady() {
1485 final ArrayList<Integer> deletedRows = mConversationListCursor.getRefreshDeletions();
1486 // If we have any deletions from the server, and the conversations are in the list view,
1487 // remove them from a selected set, if any
1488 if (!deletedRows.isEmpty() && !mSelectedSet.isEmpty()) {
1489 mSelectedSet.delete(deletedRows);
1490 }
1491 // If we have any deletions from the server, animate them away
1492 final ConversationListFragment convList = getConversationListFragment();
1493 if (!deletedRows.isEmpty() && convList != null) {
1494 final AnimatedAdapter adapter = convList.getAnimatedAdapter();
1495 if (adapter != null) {
1496 adapter.delete(deletedRows, this);
1497 }
1498 } if (!mIsConversationListScrolling) {
1499 // Swap cursors
1500 mConversationListCursor.sync();
1501 refreshAdapter();
1502 }
1503 mTracker.updateCursor(mConversationListCursor);
1504 }
1505
1506 @Override
1507 public void onDataSetChanged() {
1508 refreshAdapter();
1509 }
1510
1511 private void refreshAdapter() {
1512 final ConversationListFragment convList = getConversationListFragment();
1513 if (convList != null) {
1514 final AnimatedAdapter adapter = convList.getAnimatedAdapter();
1515 if (adapter != null) {
1516 adapter.notifyDataSetChanged();
1517 }
1518 }
1519 }
1520
1521 /**
1522 * This class handles throttled refresh of the conversation list
1523 */
1524 static class RefreshTimerTask extends TimerTask {
1525 final Handler mHandler;
1526 final AbstractActivityController mController;
1527
1528 RefreshTimerTask(AbstractActivityController controller, Handler handler) {
1529 mHandler = handler;
1530 mController = controller;
1531 }
1532
1533 @Override
1534 public void run() {
1535 mHandler.post(new Runnable() {
1536 @Override
1537 public void run() {
1538 LogUtils.d(LOG_TAG, "Delay done... calling onRefreshRequired");
1539 mController.onRefreshRequired();
1540 }});
1541 }
1542 }
1543
1544 /**
1545 * Cancel the refresh task, if it's running
1546 */
1547 private void cancelRefreshTask () {
1548 if (mConversationListRefreshTask != null) {
1549 mConversationListRefreshTask.cancel();
1550 mConversationListRefreshTask = null;
1551 }
1552 }
1553
1554 @Override
1555 public void onScrollStateChanged(AbsListView view, int scrollState) {
1556 boolean isScrolling = (scrollState != OnScrollListener.SCROLL_STATE_IDLE);
1557 if (!isScrolling) {
1558 ConversationCursor cc = getConversationListCursor();
1559 if (cc.isRefreshRequired()) {
1560 LogUtils.d(LOG_TAG, "Stop scrolling: refresh");
1561 cc.refresh();
1562 } else if (cc.isRefreshReady()) {
1563 LogUtils.d(LOG_TAG, "Stop scrolling: try sync");
1564 onRefreshReady();
1565 }
1566 }
1567 mIsConversationListScrolling = isScrolling;
1568 }
1569
1570 @Override
1571 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1572 int totalItemCount) {
1573 }
1574
1575 @Override
Mindy Pereira967ede62012-03-22 09:29:09 -07001576 public void onSetEmpty() {
Mindy Pereira967ede62012-03-22 09:29:09 -07001577 }
1578
1579 @Override
1580 public void onSetPopulated(ConversationSelectionSet set) {
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001581 final ConversationListFragment convList = getConversationListFragment();
1582 if (convList == null) {
1583 return;
1584 }
Mindy Pereira07118a02012-04-02 16:35:01 -07001585 mCabActionMenu = new SelectedConversationsActionMenu(mActivity, set,
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001586 convList.getAnimatedAdapter(), this, this,
1587 mAccount, mFolder, (SwipeableListView) convList.getListView());
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001588 enableCabMode();
Mindy Pereira967ede62012-03-22 09:29:09 -07001589 }
1590
Mindy Pereira967ede62012-03-22 09:29:09 -07001591 @Override
1592 public void onSetChanged(ConversationSelectionSet set) {
1593 // Do nothing. We don't care about changes to the set.
1594 }
1595
1596 @Override
1597 public ConversationSelectionSet getSelectedSet() {
1598 return mSelectedSet;
1599 }
1600
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001601 /**
1602 * Disable the Contextual Action Bar (CAB). The selected set is not changed.
1603 */
1604 protected void disableCabMode() {
1605 if (mCabActionMenu != null) {
1606 mCabActionMenu.deactivate();
1607 }
1608 }
1609
1610 /**
1611 * Re-enable the CAB menu if required. The selection set is not changed.
1612 */
1613 protected void enableCabMode() {
1614 if (mCabActionMenu != null) {
1615 mCabActionMenu.activate();
1616 }
1617 }
1618
Mindy Pereira967ede62012-03-22 09:29:09 -07001619 @Override
1620 public void onActionComplete() {
Marc Blankbf128eb2012-04-18 15:58:45 -07001621 if (getConversationListCursor().isRefreshReady()) {
1622 refreshAdapter();
1623 }
Mindy Pereira967ede62012-03-22 09:29:09 -07001624 }
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001625
1626 @Override
1627 public void startSearch() {
1628 if (mAccount.supportsCapability(UIProvider.AccountCapabilities.LOCAL_SEARCH)
1629 | mAccount.supportsCapability(UIProvider.AccountCapabilities.SERVER_SEARCH)) {
Vikram Aggarwal70f130e2012-04-03 12:32:14 -07001630 onSearchRequested(mActionBarView.getQuery());
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001631 } else {
1632 Toast.makeText(mActivity.getActivityContext(), mActivity.getActivityContext()
Mindy Pereiraa46c2992012-03-27 14:12:39 -07001633 .getString(R.string.search_unsupported), Toast.LENGTH_SHORT).show();
Mindy Pereirafd0c2972012-03-27 13:50:39 -07001634 }
1635 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07001636
Vikram Aggarwal0dda5732012-04-06 11:20:16 -07001637 @Override
1638 public void exitSearchMode() {
1639 if (mViewMode.getMode() == ViewMode.SEARCH_RESULTS_LIST) {
1640 mActivity.finish();
1641 }
1642 }
1643
Mindy Pereiraacf60392012-04-06 09:11:00 -07001644 /**
1645 * Supports dragging conversations to a folder.
1646 */
1647 @Override
1648 public boolean supportsDrag(DragEvent event, Folder folder) {
1649 return (folder != null
1650 && event != null
1651 && event.getClipDescription() != null
1652 && folder.supportsCapability
1653 (UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
1654 && folder.supportsCapability
1655 (UIProvider.FolderCapabilities.CAN_HOLD_MAIL)
1656 && !mFolder.uri.equals(folder.uri));
1657 }
1658
1659 /**
1660 * Handles dropping conversations to a label.
1661 */
1662 @Override
1663 public void handleDrop(DragEvent event, final Folder folder) {
1664 /*
1665 * Expect clip data has form: [conversations_uri, conversationId1,
1666 * maxMessageId1, label1, conversationId2, maxMessageId2, label2, ...]
1667 */
1668 if (!supportsDrag(event, folder)) {
1669 return;
1670 }
1671 ClipData data = event.getClipData();
1672 ArrayList<Integer> conversationPositions = Lists.newArrayList();
1673 for (int i = 1; i < data.getItemCount(); i += 3) {
1674 int position = Integer.parseInt(data.getItemAt(i).getText().toString());
1675 conversationPositions.add(position);
1676 }
1677 final Collection<Conversation> conversations = mSelectedSet.values();
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001678 final ConversationListFragment convList = getConversationListFragment();
1679 if (convList == null) {
1680 return;
1681 }
1682 convList.requestDelete(conversations,
Mindy Pereiraacf60392012-04-06 09:11:00 -07001683 new ActionCompleteListener() {
1684 @Override
1685 public void onActionComplete() {
1686 AbstractActivityController.this.onActionComplete();
1687 ArrayList<Folder> changes = new ArrayList<Folder>();
1688 changes.add(folder);
1689 Conversation.updateString(mContext, conversations,
1690 ConversationColumns.FOLDER_LIST, folder.uri.toString());
1691 Conversation.updateString(mContext, conversations,
1692 ConversationColumns.RAW_FOLDERS,
1693 Folder.getSerializedFolderString(mFolder, changes));
Mindy Pereira0963ef82012-04-10 11:43:01 -07001694 onUndoAvailable(new UndoOperation(conversations
Mindy Pereiraacf60392012-04-06 09:11:00 -07001695 .size(), R.id.change_folder));
1696 mSelectedSet.clear();
1697 }
1698 });
1699 }
Mindy Pereira0963ef82012-04-10 11:43:01 -07001700
1701 @Override
1702 public void onUndoCancel() {
1703 mUndoBarView.hide(false);
1704 }
1705
1706 @Override
1707 public void onTouchEvent(MotionEvent event) {
1708 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1709 if (mUndoBarView != null && !mUndoBarView.isEventInUndo(event)) {
1710 mUndoBarView.hide(true);
1711 }
1712 }
1713 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001714
1715 private class ConversationListLoaderCallbacks implements
1716 LoaderManager.LoaderCallbacks<ConversationCursor> {
1717
1718 @Override
1719 public Loader<ConversationCursor> onCreateLoader(int id, Bundle args) {
1720 Loader<ConversationCursor> result = new ConversationCursorLoader((Activity) mActivity,
1721 mAccount, UIProvider.CONVERSATION_PROJECTION, mFolder.conversationListUri);
1722 return result;
1723 }
1724
1725 @Override
1726 public void onLoadFinished(Loader<ConversationCursor> loader, ConversationCursor data) {
1727 mConversationListCursor = data;
1728
1729 // Call the method that updates things when values in the cursor change
Marc Blankbf128eb2012-04-18 15:58:45 -07001730 if (mConversationListCursor.isRefreshReady()) {
1731 onRefreshReady();
1732 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001733
1734 // Register the AbstractActivityController as a listener to changes in
1735 // data in the cursor.
1736 final ConversationListFragment convList = getConversationListFragment();
1737 if (convList != null) {
1738 convList.onCursorUpdated();
Marc Blankbf128eb2012-04-18 15:58:45 -07001739 if (!mConversationListenerAdded) {
1740 // TODO(mindyp): when we move to the cursor loader, we need
1741 // to add/remove the listener when we create/ destroy loaders.
1742 mConversationListCursor
1743 .addListener(AbstractActivityController.this);
1744 convList.getListView().setOnScrollListener(
1745 AbstractActivityController.this);
1746 mConversationListenerAdded = true;
1747 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07001748 }
1749 // Shown for search results in two-pane mode only.
1750 if (shouldShowFirstConversation()) {
1751 if (mConversationListCursor.getCount() > 0) {
1752 mConversationListCursor.moveToPosition(0);
1753 if (convList != null) {
1754 convList.getListView().setItemChecked(0, true);
1755 }
1756 final Conversation conv = new Conversation(mConversationListCursor);
1757 conv.position = 0;
1758 onConversationSelected(conv);
1759 }
1760 }
1761 }
1762
1763 @Override
1764 public void onLoaderReset(Loader<ConversationCursor> loader) {
1765 final ConversationListFragment convList = getConversationListFragment();
1766 if (convList == null) {
1767 return;
1768 }
1769 convList.onCursorUpdated();
1770 }
1771
1772 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08001773}