blob: c6b6154051af8611d69f87b36ef83cd70d04faaa [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;
Andy Huangf9a73482012-03-13 15:54:02 -070025import android.app.LoaderManager;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080026import android.content.ContentResolver;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080027import android.content.Context;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080028import android.content.CursorLoader;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070029import android.content.DialogInterface;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080030import android.content.Intent;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080031import android.content.Loader;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080032import android.database.Cursor;
Paul Westbrook23b74b92012-02-29 11:36:12 -080033import android.net.Uri;
Paul Westbrookdaef1ce2012-02-23 16:42:00 -080034import android.os.AsyncTask;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080035import android.os.Bundle;
Mindy Pereira21ab4902012-03-19 18:48:03 -070036import android.os.Handler;
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -070037import android.text.TextUtils;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080038import android.view.KeyEvent;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080039import android.view.LayoutInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080040import android.view.Menu;
Mindy Pereira28d5f722012-02-15 12:32:40 -080041import android.view.MenuInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080042import android.view.MenuItem;
43import android.view.MotionEvent;
44
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080045import com.android.mail.ConversationListContext;
Andy Huangf9a73482012-03-13 15:54:02 -070046import com.android.mail.R;
Mindy Pereira9b875682012-02-15 18:10:54 -080047import com.android.mail.compose.ComposeActivity;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080048import com.android.mail.providers.Account;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080049import com.android.mail.providers.AccountCacheProvider;
Mindy Pereira9b875682012-02-15 18:10:54 -080050import com.android.mail.providers.Conversation;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080051import com.android.mail.providers.Folder;
Mindy Pereiradac00542012-03-01 10:50:33 -080052import com.android.mail.providers.Settings;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080053import com.android.mail.providers.UIProvider;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070054import com.android.mail.providers.UIProvider.AutoAdvance;
55import com.android.mail.providers.UIProvider.ConversationColumns;
56import com.android.mail.providers.UIProvider.FolderCapabilities;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080057import com.android.mail.utils.LogUtils;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -080058import com.android.mail.utils.Utils;
Marc Blank167faa82012-03-21 13:11:53 -070059import com.google.common.collect.ImmutableList;
60import com.google.common.collect.Sets;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080061
Marc Blank167faa82012-03-21 13:11:53 -070062import java.util.ArrayList;
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -070063import java.util.Arrays;
Mindy Pereirafbe40192012-03-20 10:40:45 -070064import java.util.Collection;
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -070065import java.util.Collections;
66import java.util.HashSet;
Paul Westbrook23b74b92012-02-29 11:36:12 -080067import java.util.Set;
68
69
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080070/**
Mindy Pereira161f50d2012-02-28 15:47:19 -080071 * This is an abstract implementation of the Activity Controller. This class
72 * knows how to respond to menu items, state changes, layout changes, etc. It
73 * weaves together the views and listeners, dispatching actions to the
74 * respective underlying classes.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080075 * <p>
Mindy Pereira161f50d2012-02-28 15:47:19 -080076 * Even though this class is abstract, it should provide default implementations
77 * for most, if not all the methods in the ActivityController interface. This
78 * makes the task of the subclasses easier: OnePaneActivityController and
79 * TwoPaneActivityController can be concise when the common functionality is in
80 * AbstractActivityController.
81 * </p>
82 * <p>
83 * In the Gmail codebase, this was called BaseActivityController
84 * </p>
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080085 */
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080086public abstract class AbstractActivityController implements ActivityController {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080087 // Keys for serialization of various information in Bundles.
88 private static final String SAVED_LIST_CONTEXT = "saved-list-context";
Vikram Aggarwal6c511582012-02-27 10:59:47 -080089 private static final String SAVED_ACCOUNT = "saved-account";
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080090
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -070091 /** Are we on a tablet device or not. */
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -080092 public final boolean IS_TABLET_DEVICE;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080093
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -080094 protected Account mAccount;
Mindy Pereira28e0c342012-02-17 15:05:13 -080095 protected Folder mFolder;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -080096 protected ActionBarView mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080097 protected final RestrictedActivity mActivity;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080098 protected final Context mContext;
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -080099 protected final RecentFolderList mRecentFolderList;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800100 protected ConversationListContext mConvListContext;
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800101 private FetchAccountFolderTask mFetchAccountFolderTask;
Mindy Pereira9b875682012-02-15 18:10:54 -0800102 protected Conversation mCurrentConversation;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800103
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700104 /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
105 private SuppressNotificationReceiver mNewEmailReceiver = null;
106
Mindy Pereirafbe40192012-03-20 10:40:45 -0700107 protected Handler mHandler = new Handler();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800108 protected ConversationListFragment mConversationListFragment;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800109 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800110 * The current mode of the application. All changes in mode are initiated by
111 * the activity controller. View mode changes are propagated to classes that
112 * attach themselves as listeners of view mode changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800113 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800114 protected final ViewMode mViewMode;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800115 protected ContentResolver mResolver;
Mindy Pereira0825efd2012-02-15 17:30:32 -0800116 protected FolderListFragment mFolderListFragment;
Mindy Pereira9b875682012-02-15 18:10:54 -0800117 protected ConversationViewFragment mConversationViewFragment;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800118 protected boolean isLoaderInitialized = false;
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800119 private AsyncRefreshTask mAsyncRefreshTask;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800120
Paul Westbrook23b74b92012-02-29 11:36:12 -0800121 private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
Mindy Pereira559c3b32012-03-01 13:17:47 -0800122 protected Settings mCachedSettings;
Mindy Pereirae0828392012-03-08 10:38:40 -0800123 private FetchSearchFolderTask mFetchSearchFolderTask;
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700124 private FetchInboxTask mFetchInboxTask;
Mindy Pereiradac00542012-03-01 10:50:33 -0800125
Vikram Aggarwal04ff99c2012-02-28 15:29:13 -0800126 protected static final String LOG_TAG = new LogUtils().getLogTag();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800127 /** Constants used to differentiate between the types of loaders. */
128 private static final int LOADER_ACCOUNT_CURSOR = 0;
129 private static final int LOADER_ACCOUNT_SETTINGS = 1;
130 private static final int LOADER_FOLDER_CURSOR = 2;
131 private static final int LOADER_RECENT_FOLDERS = 3;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800132
133 public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
134 mActivity = activity;
135 mViewMode = viewMode;
136 mContext = activity.getApplicationContext();
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800137 IS_TABLET_DEVICE = Utils.useTabletUI(mContext);
Marc Blank167faa82012-03-21 13:11:53 -0700138 mRecentFolderList = new RecentFolderList(mContext, this);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800139 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800140
141 @Override
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800142 public synchronized void attachConversationList(ConversationListFragment fragment) {
143 // If there is an existing fragment, unregister it
144 if (mConversationListFragment != null) {
145 mViewMode.removeListener(mConversationListFragment);
146 }
147 mConversationListFragment = fragment;
148 // If the current fragment is non-null, add it as a listener.
149 if (fragment != null) {
150 mViewMode.addListener(mConversationListFragment);
151 }
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800152 }
153
154 @Override
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800155 public synchronized void attachFolderList(FolderListFragment fragment) {
156 // If there is an existing fragment, unregister it
157 if (mFolderListFragment != null) {
158 mViewMode.removeListener(mFolderListFragment);
159 }
160 mFolderListFragment = fragment;
161 if (fragment != null) {
162 mViewMode.addListener(mFolderListFragment);
163 }
Mindy Pereira555140c2012-02-15 14:55:29 -0800164 }
165
166 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -0800167 public void attachConversationView(ConversationViewFragment conversationViewFragment) {
168 mConversationViewFragment = conversationViewFragment;
169 }
170
171 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800172 public void clearSubject() {
173 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800174 }
175
176 @Override
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800177 public Account getCurrentAccount() {
178 return mAccount;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800179 }
180
181 @Override
182 public ConversationListContext getCurrentListContext() {
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800183 return mConvListContext;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800184 }
185
186 @Override
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800187 public String getHelpContext() {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800188 return "Mail";
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800189 }
190
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800191 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800192 public int getMode() {
193 return mViewMode.getMode();
194 }
195
196 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800197 public String getUnshownSubject(String subject) {
198 // Calculate how much of the subject is shown, and return the remaining.
199 return null;
200 }
201
202 @Override
203 public void handleConversationLoadError() {
204 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800205 }
206
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800207 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800208 * Initialize the action bar. This is not visible to OnePaneController and
209 * TwoPaneController so they cannot override this behavior.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800210 */
211 private void initCustomActionBarView() {
212 ActionBar actionBar = mActivity.getActionBar();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800213 mActionBarView = (ActionBarView) LayoutInflater.from(mContext).inflate(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800214 R.layout.actionbar_view, null);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800215 if (actionBar != null && mActionBarView != null) {
Mindy Pereira161f50d2012-02-28 15:47:19 -0800216 // Why have a different variable for the same thing? We should apply
217 // the same actions
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800218 // on mActionBarView instead.
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800219 mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
220 actionBar.setCustomView(mActionBarView, new ActionBar.LayoutParams(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800221 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
222 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
Vikram Aggarwal2a25d0c2012-02-21 16:43:10 -0800223 ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800224 }
225 }
226
227 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800228 * Returns whether the conversation list fragment is visible or not.
229 * Different layouts will have their own notion on the visibility of
230 * fragments, so this method needs to be overriden.
231 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800232 * @return
233 */
234 protected abstract boolean isConversationListVisible();
235
236 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800237 public void onAccountChanged(Account account) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800238 if (!account.equals(mAccount)) {
239 mAccount = account;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800240 mRecentFolderList.setCurrentAccount(account);
Andy Huangf9a73482012-03-13 15:54:02 -0700241 restartOptionalLoader(LOADER_RECENT_FOLDERS, null /* args */);
242 restartOptionalLoader(LOADER_ACCOUNT_SETTINGS, null /* args */);
Mindy Pereirabc57bf12012-02-29 14:39:09 -0800243 mActionBarView.setAccount(mAccount);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800244 mActivity.invalidateOptionsMenu();
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700245
246 disableNotificationsOnAccountChange(mAccount);
247
Mindy Pereira161f50d2012-02-28 15:47:19 -0800248 // Account changed; existing folder is invalid.
249 mFolder = null;
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800250 fetchAccountFolderInfo();
Mindy Pereira28d5f722012-02-15 12:32:40 -0800251 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800252 }
253
Mindy Pereiradac00542012-03-01 10:50:33 -0800254 public void onSettingsChanged(Settings settings) {
255 mCachedSettings = settings;
Mindy Pereira559c3b32012-03-01 13:17:47 -0800256 resetActionBarIcon();
Mindy Pereiradac00542012-03-01 10:50:33 -0800257 }
258
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800259 @Override
260 public Settings getSettings() {
261 return mCachedSettings;
262 }
263
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800264 private void fetchAccountFolderInfo() {
265 if (mFetchAccountFolderTask != null) {
266 mFetchAccountFolderTask.cancel(true);
267 }
268 mFetchAccountFolderTask = new FetchAccountFolderTask();
269 mFetchAccountFolderTask.execute();
270 }
271
Mindy Pereirae0828392012-03-08 10:38:40 -0800272 private void fetchSearchFolder(Intent intent) {
273 if (mFetchSearchFolderTask != null) {
274 mFetchSearchFolderTask.cancel(true);
275 }
276 mFetchSearchFolderTask = new FetchSearchFolderTask(intent
277 .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
278 mFetchSearchFolderTask.execute();
279 }
280
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800281 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800282 public void onFolderChanged(Folder folder) {
Mindy Pereira7a3471f2012-03-06 12:23:41 -0800283 if (folder != null && !folder.equals(mFolder)) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800284 setFolder(folder);
Mindy Pereira161f50d2012-02-28 15:47:19 -0800285 mConvListContext = ConversationListContext.forFolder(mContext, mAccount, mFolder);
Mindy Pereira28e0c342012-02-17 15:05:13 -0800286 showConversationList(mConvListContext);
Paul Westbrook9024b6d2012-03-19 13:57:55 -0700287
288 // Add the folder that we were viewing to the recent folders list.
289 // TODO: this may need to be fine tuned. If this is the signal that is indicating that
290 // the list is shown to the user, this could fire in one pane if the user goes directly
291 // to a conversation
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700292 updateRecentFolderList();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800293 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800294 }
295
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700296 private void updateRecentFolderList() {
297 mRecentFolderList.setCurrentAccount(mAccount);
298 mRecentFolderList.touchFolder(mFolder);
299 }
300
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700301 // TODO(mindyp): set this up to store a copy of the folder locally
302 // as soon as we realize we haven't gotten the inbox folder yet.
303 public void loadInbox() {
304 if (mFetchInboxTask != null) {
305 mFetchInboxTask.cancel(true);
306 }
307 mFetchInboxTask = new FetchInboxTask();
308 mFetchInboxTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
309 }
310
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800311 /** Set the current folder */
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800312 private void setFolder(Folder folder) {
313 // Start watching folder for sync status.
Mindy Pereirae0458e82012-03-06 11:54:55 -0800314 if (folder != null && !folder.equals(mFolder)) {
Mindy Pereira9b623802012-03-07 17:15:49 -0800315 mActionBarView.setRefreshInProgress(false);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800316 mFolder = folder;
Mindy Pereiraf9323cd2012-02-29 13:47:09 -0800317 mActionBarView.setFolder(mFolder);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800318 mActivity.getLoaderManager().restartLoader(LOADER_FOLDER_CURSOR, null, this);
Mindy Pereirae0458e82012-03-06 11:54:55 -0800319 } else if (folder == null) {
320 LogUtils.wtf(LOG_TAG, "Folder in setFolder is null");
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800321 }
322 }
323
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800324 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800325 public void onActivityResult(int requestCode, int resultCode, Intent data) {
326 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800327 }
328
329 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800330 public void onConversationListVisibilityChanged(boolean visible) {
331 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800332 }
333
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800334 /**
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800335 * By default, doing nothing is right. A two-pane controller will need to
336 * override this.
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800337 */
338 @Override
339 public void onConversationVisibilityChanged(boolean visible) {
340 // Do nothing.
341 return;
342 }
343
344 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800345 public boolean onCreate(Bundle savedState) {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800346 // Initialize the action bar view.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800347 initCustomActionBarView();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800348 // Allow shortcut keys to function for the ActionBar and menus.
349 mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800350 mResolver = mActivity.getContentResolver();
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800351
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700352 mNewEmailReceiver = new SuppressNotificationReceiver();
353
Mindy Pereira161f50d2012-02-28 15:47:19 -0800354 // All the individual UI components listen for ViewMode changes. This
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800355 // simplifies the amount of logic in the AbstractActivityController, but increases the
356 // possibility of timing-related bugs.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800357 mViewMode.addListener(this);
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800358 assert (mActionBarView != null);
359 mViewMode.addListener(mActionBarView);
360
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800361 restoreState(savedState);
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800362 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800363 }
364
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800365 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800366 public Dialog onCreateDialog(int id, Bundle bundle) {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800367 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800368 return null;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800369 }
370
371 @Override
372 public boolean onCreateOptionsMenu(Menu menu) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800373 MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800374 inflater.inflate(mActionBarView.getOptionsMenuId(), menu);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800375 mActionBarView.onCreateOptionsMenu(menu);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800376 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800377 }
378
379 @Override
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800380 public boolean onKeyDown(int keyCode, KeyEvent event) {
381 // TODO(viki): Auto-generated method stub
382 return false;
383 }
384
385 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800386 public boolean onOptionsItemSelected(MenuItem item) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700387 final int id = item.getItemId();
Mindy Pereira9b875682012-02-15 18:10:54 -0800388 boolean handled = true;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800389 switch (id) {
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800390 case android.R.id.home:
391 onUpPressed();
392 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800393 case R.id.compose:
394 ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
395 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800396 case R.id.show_all_folders:
397 showFolderList();
398 break;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800399 case R.id.refresh:
400 requestFolderRefresh();
401 break;
Mindy Pereira1f936682012-03-02 11:30:33 -0800402 case R.id.settings:
403 Utils.showSettings(mActivity.getActivityContext(), mAccount);
Paul Westbrook2861b6a2012-02-15 15:25:34 -0800404 break;
Paul Westbrook94e440d2012-02-24 11:03:47 -0800405 case R.id.help_info_menu_item:
406 // TODO: enable context sensitive help
407 Utils.showHelp(mActivity.getActivityContext(), mAccount.helpIntentUri, null);
408 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700409 case R.id.feedback_menu_item:
Mindy Pereirafbe40192012-03-20 10:40:45 -0700410 Utils.sendFeedback(mActivity.getActivityContext(), mAccount);
411 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800412 default:
413 handled = false;
414 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800415 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800416 return handled;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800417 }
418
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700419 /**
Mindy Pereira21ab4902012-03-19 18:48:03 -0700420 * Return the auto advance setting for the current account.
421 * @param activity
422 * @return the autoadvance setting, a constant from {@link AutoAdvance}
423 */
424 static int getAutoAdvanceSetting(RestrictedActivity activity) {
425 final Settings settings = activity.getSettings();
426 // TODO(mindyp): if this isn't set, then show the dialog telling the user to set it.
427 // Remove defaulting to AutoAdvance.LIST.
428 final int autoAdvance = (settings != null) ?
429 (settings.autoAdvance == AutoAdvance.UNSET ?
430 AutoAdvance.LIST : settings.autoAdvance)
431 : AutoAdvance.LIST;
432 return autoAdvance;
433 }
434
435 /**
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700436 * Implements folder changes. This class is a listener because folder changes need to be
437 * performed <b>after</b> the ConversationListFragment has finished animating away the
438 * removal of the conversation.
439 *
440 */
Mindy Pereirae5f4dc02012-03-21 16:08:53 -0700441 protected abstract class FolderChangeListener implements ActionCompleteListener {
442 protected final String mFolderChangeList;
443 protected final boolean mDestructiveChange;
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700444
445 public FolderChangeListener(String changeList, boolean destructive) {
446 mFolderChangeList = changeList;
447 mDestructiveChange = destructive;
448 }
449
Mindy Pereirae5f4dc02012-03-21 16:08:53 -0700450 public abstract void onActionComplete();
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700451 }
452
453 /**
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700454 * Update the specified column name in conversation for a boolean value.
455 * @param columnName
456 * @param value
457 */
Mindy Pereirafbe40192012-03-20 10:40:45 -0700458 protected void updateCurrentConversation(String columnName, boolean value) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700459 Conversation.updateBoolean(mContext, ImmutableList.of(mCurrentConversation), columnName,
460 value);
461 mConversationListFragment.requestListRefresh();
462 }
463
464 /**
465 * Update the specified column name in conversation for an integer value.
466 * @param columnName
467 * @param value
468 */
Mindy Pereirafbe40192012-03-20 10:40:45 -0700469 protected void updateCurrentConversation(String columnName, int value) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700470 Conversation.updateInt(mContext, ImmutableList.of(mCurrentConversation), columnName, value);
471 mConversationListFragment.requestListRefresh();
472 }
473
Mindy Pereira28e0c342012-02-17 15:05:13 -0800474 private void requestFolderRefresh() {
475 if (mFolder != null) {
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800476 if (mAsyncRefreshTask != null) {
477 mAsyncRefreshTask.cancel(true);
478 }
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800479 mAsyncRefreshTask = new AsyncRefreshTask(mContext, mFolder);
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800480 mAsyncRefreshTask.execute();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800481 }
482 }
483
Mindy Pereirafbe40192012-03-20 10:40:45 -0700484 /**
485 * Confirm (based on user's settings) and delete a conversation from the conversation list and
486 * from the database.
487 * @param showDialog
488 * @param confirmResource
489 * @param listener
490 */
491 protected void confirmAndDelete(boolean showDialog, int confirmResource,
492 final ActionCompleteListener listener) {
493 final ArrayList<Conversation> single = new ArrayList<Conversation>();
494 single.add(mCurrentConversation);
495 if (showDialog) {
496 final AlertDialog.OnClickListener onClick = new AlertDialog.OnClickListener() {
497 @Override
498 public void onClick(DialogInterface dialog, int which) {
499 requestDelete(listener);
500 }
501 };
502 final CharSequence message = Utils.formatPlural(mContext, confirmResource, 1);
503 new AlertDialog.Builder(mActivity.getActivityContext()).setMessage(message)
504 .setPositiveButton(R.string.ok, onClick)
505 .setNegativeButton(R.string.cancel, null)
506 .create().show();
507 } else {
508 requestDelete(listener);
509 }
510 }
511
512
513 protected abstract void requestDelete(ActionCompleteListener listener);
514
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700515
516 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800517 public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
518 // TODO(viki): Auto-generated method stub
519
520 }
521
522 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800523 public boolean onPrepareOptionsMenu(Menu menu) {
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800524 mActionBarView.onPrepareOptionsMenu(menu);
Mindy Pereira363451a2012-02-22 14:14:46 -0800525 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800526 }
527
Mindy Pereira68f2e222012-03-07 10:36:54 -0800528 @Override
529 public void onPause() {
530 isLoaderInitialized = false;
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700531
532 enableNotifications();
Paul Westbrook94e440d2012-02-24 11:03:47 -0800533 }
534
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800535 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800536 public void onResume() {
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700537 // Register the receiver that will prevent the status receiver from
538 // displaying its notification icon as long as we're running.
539 // The SupressNotificationReceiver will block the broadcast if we're looking at the folder
540 // that the notification was received for.
541 disableNotifications();
542
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800543 if (mActionBarView != null) {
544 mActionBarView.onResume();
545 }
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700546
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800547 }
548
549 @Override
550 public void onSaveInstanceState(Bundle outState) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800551 if (mAccount != null) {
552 LogUtils.d(LOG_TAG, "Saving the account now");
553 outState.putParcelable(SAVED_ACCOUNT, mAccount);
554 }
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800555 if (mConvListContext != null) {
556 outState.putBundle(SAVED_LIST_CONTEXT, mConvListContext.toBundle());
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800557 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800558 }
559
560 @Override
Mindy Pereira68f2e222012-03-07 10:36:54 -0800561 public void onSearchRequested(String query) {
562 Intent intent = new Intent();
563 intent.setAction(Intent.ACTION_SEARCH);
564 intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query);
565 intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount);
566 intent.setComponent(mActivity.getComponentName());
567 mActivity.startActivity(intent);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800568 }
569
570 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800571 public void onStartDragMode() {
572 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800573 }
574
575 @Override
576 public void onStop() {
577 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800578 }
579
580 @Override
581 public void onStopDragMode() {
582 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800583 }
584
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800585 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800586 * {@inheritDoc} Subclasses must override this to listen to mode changes
587 * from the ViewMode. Subclasses <b>must</b> call the parent's
588 * onViewModeChanged since the parent will handle common state changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800589 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800590 @Override
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800591 public void onViewModeChanged(int newMode) {
592 // Perform any mode specific work here.
Mindy Pereira161f50d2012-02-28 15:47:19 -0800593 // reset the action bar icon based on the mode. Why don't the individual
594 // controllers do
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800595 // this themselves?
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800596
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800597 // In conversation list mode, clean up the conversation.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800598 if (newMode == ViewMode.CONVERSATION_LIST) {
599 // Clean up the conversation here.
600 }
601
Mindy Pereira161f50d2012-02-28 15:47:19 -0800602 // We don't want to invalidate the options menu when switching to
603 // conversation
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800604 // mode, as it will happen when the conversation finishes loading.
605 if (newMode != ViewMode.CONVERSATION) {
606 mActivity.invalidateOptionsMenu();
607 }
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800608 }
609
610 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800611 public void onWindowFocusChanged(boolean hasFocus) {
612 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800613 }
614
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800615 /**
616 * @param savedState
617 */
618 protected void restoreListContext(Bundle savedState) {
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800619 Bundle listContextBundle = savedState.getBundle(SAVED_LIST_CONTEXT);
620 if (listContextBundle != null) {
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800621 mConvListContext = ConversationListContext.forBundle(listContextBundle);
Mindy Pereiradac00542012-03-01 10:50:33 -0800622 mFolder = mConvListContext.folder;
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800623 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800624 }
625
626 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800627 * Restore the state from the previous bundle. Subclasses should call this
628 * method from the parent class, since it performs important UI
629 * initialization.
630 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800631 * @param savedState
632 */
633 protected void restoreState(Bundle savedState) {
Mindy Pereira68f2e222012-03-07 10:36:54 -0800634 final Intent intent = mActivity.getIntent();
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800635 if (savedState != null) {
636 restoreListContext(savedState);
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800637 mAccount = savedState.getParcelable(SAVED_ACCOUNT);
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800638 mActionBarView.setAccount(mAccount);
Andy Huangf9a73482012-03-13 15:54:02 -0700639 restartOptionalLoader(LOADER_ACCOUNT_SETTINGS, null /* args */);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800640 } else if (intent != null) {
641 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Mindy Pereirad8542ad2012-02-29 13:54:17 -0800642 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
643 mAccount = ((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800644 mActionBarView.setAccount(mAccount);
Andy Huangf9a73482012-03-13 15:54:02 -0700645 restartOptionalLoader(LOADER_ACCOUNT_SETTINGS, null /* args */);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800646 mActivity.invalidateOptionsMenu();
Mindy Pereirad8542ad2012-02-29 13:54:17 -0800647 }
648 if (intent.hasExtra(Utils.EXTRA_FOLDER)) {
649 // Open the folder.
650 LogUtils.d(LOG_TAG, "SHOW THE FOLDER at %s",
651 intent.getParcelableExtra(Utils.EXTRA_FOLDER));
652 onFolderChanged((Folder) intent.getParcelableExtra(Utils.EXTRA_FOLDER));
653 }
Mindy Pereira161f50d2012-02-28 15:47:19 -0800654 if (intent.hasExtra(Utils.EXTRA_CONVERSATION)) {
655 // Open the conversation.
656 LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
657 intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
Mindy Pereira5040f1a2012-03-20 10:14:06 -0700658 setCurrentConversation((Conversation) intent
Mindy Pereira161f50d2012-02-28 15:47:19 -0800659 .getParcelableExtra(Utils.EXTRA_CONVERSATION));
Mindy Pereira5040f1a2012-03-20 10:14:06 -0700660 showConversation(this.mCurrentConversation);
Mindy Pereira161f50d2012-02-28 15:47:19 -0800661 }
Mindy Pereira68f2e222012-03-07 10:36:54 -0800662 } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
663 mViewMode.enterSearchResultsListMode();
664 mAccount = ((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800665 mActionBarView.setAccount(mAccount);
Mindy Pereirae0828392012-03-08 10:38:40 -0800666 fetchSearchFolder(intent);
Mindy Pereira161f50d2012-02-28 15:47:19 -0800667 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800668 }
Mindy Pereira68f2e222012-03-07 10:36:54 -0800669 // Create the accounts loader; this loads the account switch spinner.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800670 mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800671 }
672
673 @Override
674 public void setSubject(String subject) {
675 // Do something useful with the subject. This requires changing the
676 // conversation view's subject text.
677 }
678
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800679 /**
680 * Children can override this method, but they must call super.showConversation().
681 * {@inheritDoc}
682 */
683 @Override
684 public void showConversation(Conversation conversation) {
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800685 }
686
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800687 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -0800688 public void onConversationSelected(Conversation conversation) {
Mindy Pereira5040f1a2012-03-20 10:14:06 -0700689 setCurrentConversation(conversation);
Mindy Pereira9b875682012-02-15 18:10:54 -0800690 showConversation(mCurrentConversation);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800691 if (mConvListContext != null && mConvListContext.isSearchResult()) {
692 mViewMode.enterSearchResultsConversationMode();
693 } else {
694 mViewMode.enterConversationMode();
695 }
Vikram Aggarwal7d602882012-02-07 15:01:20 -0800696 }
Mindy Pereira555140c2012-02-15 14:55:29 -0800697
Mindy Pereira5040f1a2012-03-20 10:14:06 -0700698 public void setCurrentConversation(Conversation conversation) {
699 mCurrentConversation = conversation;
700 }
701
Vikram Aggarwala9b93f32012-02-23 14:51:58 -0800702 /**
703 * {@inheritDoc}
704 */
Mindy Pereira555140c2012-02-15 14:55:29 -0800705 @Override
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800706 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
707 // Create a loader to listen in on account changes.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800708 switch (id) {
709 case LOADER_ACCOUNT_CURSOR:
710 return new CursorLoader(mContext, AccountCacheProvider.getAccountsUri(),
711 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
712 case LOADER_FOLDER_CURSOR:
713 return new CursorLoader(mContext, mFolder.uri,
714 UIProvider.FOLDERS_PROJECTION, null, null, null);
715 case LOADER_ACCOUNT_SETTINGS:
716 if (mAccount.settingsQueryUri != null) {
717 return new CursorLoader(mContext, mAccount.settingsQueryUri,
718 UIProvider.SETTINGS_PROJECTION, null, null, null);
719 }
Paul Westbrookea4ee202012-03-12 14:12:33 -0700720 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800721 case LOADER_RECENT_FOLDERS:
Paul Westbrookea4ee202012-03-12 14:12:33 -0700722 if (mAccount.recentFolderListUri != null) {
723 return new CursorLoader(mContext, mAccount.recentFolderListUri,
724 UIProvider.FOLDERS_PROJECTION, null, null, null);
725 }
726 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800727 default:
728 LogUtils.wtf(LOG_TAG, "Loader returned unexpected id: " + id);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800729 }
730 return null;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800731 }
732
Andy Huangf9a73482012-03-13 15:54:02 -0700733 /**
734 * {@link LoaderManager} currently has a bug in
735 * {@link LoaderManager#restartLoader(int, Bundle, android.app.LoaderManager.LoaderCallbacks)}
736 * where, if a previous onCreateLoader returned a null loader, this method will NPE. Work around
737 * this bug by destroying any loaders that may have been created as null (essentially because
738 * they are optional loads, and may not apply to a particular account).
739 * <p>
740 * A simple null check before restarting a loader will not work, because that would not
741 * give the controller a chance to invalidate UI corresponding the prior loader result.
742 *
743 * @param id loader ID to safely restart
744 * @param args arguments to pass to the restarted loader
745 */
746 private void restartOptionalLoader(int id, Bundle args) {
747 final LoaderManager lm = mActivity.getLoaderManager();
748 lm.destroyLoader(id);
749 lm.restartLoader(id, args, this);
750 }
751
Paul Westbrook23b74b92012-02-29 11:36:12 -0800752 private boolean accountsUpdated(Cursor accountCursor) {
753 // Check to see if the current account hasn't been set, or the account cursor is empty
754 if (mAccount == null || !accountCursor.moveToFirst()) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800755 return true;
Paul Westbrook23b74b92012-02-29 11:36:12 -0800756 }
757
758 // Check to see if the number of accounts are different, from the number we saw on the last
759 // updated
760 if (mCurrentAccountUris.size() != accountCursor.getCount()) {
761 return true;
762 }
763
764 // Check to see if the account list is different or if the current account is not found in
765 // the cursor.
766 boolean foundCurrentAccount = false;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800767 do {
Paul Westbrook23b74b92012-02-29 11:36:12 -0800768 final Uri accountUri =
769 Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
770 if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
771 foundCurrentAccount = true;
772 }
773
774 if (!mCurrentAccountUris.contains(accountUri)) {
775 return true;
776 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800777 } while (accountCursor.moveToNext());
Paul Westbrook23b74b92012-02-29 11:36:12 -0800778
779 // As long as we found the current account, the list hasn't been updated
780 return !foundCurrentAccount;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800781 }
782
783 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800784 * Update the accounts on the device. This currently loads the first account
785 * in the list.
786 *
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800787 * @param loader
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800788 * @param accounts cursor into the AccountCache
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800789 * @return true if the update was successful, false otherwise
790 */
Paul Westbrook23b74b92012-02-29 11:36:12 -0800791 private boolean updateAccounts(Loader<Cursor> loader, Cursor accounts) {
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800792 if (accounts == null || !accounts.moveToFirst()) {
793 return false;
794 }
Paul Westbrook23b74b92012-02-29 11:36:12 -0800795
Vikram Aggarwala9b93f32012-02-23 14:51:58 -0800796 final Account[] allAccounts = Account.getAllAccounts(accounts);
Paul Westbrook23b74b92012-02-29 11:36:12 -0800797
798 // Save the uris for the accounts
799 mCurrentAccountUris.clear();
800 for (Account account : allAccounts) {
801 mCurrentAccountUris.add(account.uri);
802 }
803
804 final Account newAccount;
805 if (mAccount == null || !mCurrentAccountUris.contains(mAccount.uri)) {
806 accounts.moveToFirst();
807 newAccount = new Account(accounts);
808 } else {
809 newAccount = mAccount;
810 }
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700811 // Only bother updating the account/folder if the new account is different than the
Paul Westbrook23b74b92012-02-29 11:36:12 -0800812 // existing one
813 final boolean refetchFolderInfo = !newAccount.equals(mAccount);
814 onAccountChanged(newAccount);
815
816 if(refetchFolderInfo) {
817 fetchAccountFolderInfo();
818 }
819
Vikram Aggarwala9b93f32012-02-23 14:51:58 -0800820 mActionBarView.setAccounts(allAccounts);
821 return (allAccounts.length > 0);
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800822 }
823
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700824 private void disableNotifications() {
825 mNewEmailReceiver.activate(mContext, this);
826 }
827
828 private void enableNotifications() {
829 mNewEmailReceiver.deactivate();
830 }
831
832 private void disableNotificationsOnAccountChange(Account account) {
833 // If the new mail suppression receiver is activated for a different account, we want to
834 // activate it for the new account.
835 if (mNewEmailReceiver.activated() &&
836 !mNewEmailReceiver.notificationsDisabledForAccount(account)) {
837 // Deactivate the current receiver, otherwise multiple receivers may be registered.
838 mNewEmailReceiver.deactivate();
839 mNewEmailReceiver.activate(mContext, this);
840 }
841 }
842
Vikram Aggarwala9b93f32012-02-23 14:51:58 -0800843 /**
844 * {@inheritDoc}
845 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800846 @Override
847 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800848 // We want to reinitialize only if we haven't ever been initialized, or
849 // if the current account has vanished.
Paul Westbrooke3e84292012-03-05 16:19:30 -0800850 if (data == null) {
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800851 LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
Paul Westbrooke3e84292012-03-05 16:19:30 -0800852 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800853 switch (loader.getId()) {
854 case LOADER_ACCOUNT_CURSOR:
855 final boolean accountListUpdated = accountsUpdated(data);
856 if (!isLoaderInitialized || accountListUpdated) {
857 isLoaderInitialized = updateAccounts(loader, data);
858 }
859 break;
860 case LOADER_FOLDER_CURSOR:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800861 // Check status of the cursor.
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800862 data.moveToFirst();
863 Folder folder = new Folder(data);
Mindy Pereira11dd5ef2012-03-10 15:10:18 -0800864 if (folder.isSyncInProgress()) {
865 mActionBarView.onRefreshStarted();
Paul Westbrookc808fac2012-02-22 16:42:18 -0800866 } else {
Mindy Pereira11dd5ef2012-03-10 15:10:18 -0800867 // Stop the spinner here.
868 mActionBarView.onRefreshStopped(folder.lastSyncResult);
869 }
870 if (mConversationListFragment != null) {
871 mConversationListFragment.onFolderUpdated(folder);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800872 }
873 LogUtils.v(LOG_TAG, "FOLDER STATUS = " + folder.syncStatus);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800874 break;
875 case LOADER_ACCOUNT_SETTINGS:
Mindy Pereiradac00542012-03-01 10:50:33 -0800876 data.moveToFirst();
877 onSettingsChanged(new Settings(data));
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800878 break;
879 case LOADER_RECENT_FOLDERS:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800880 mRecentFolderList.loadFromUiProvider(data);
881 break;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800882 }
883 }
884
Vikram Aggarwala9b93f32012-02-23 14:51:58 -0800885 /**
886 * {@inheritDoc}
887 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800888 @Override
889 public void onLoaderReset(Loader<Cursor> loader) {
Andy Huangf9a73482012-03-13 15:54:02 -0700890 switch (loader.getId()) {
891 case LOADER_ACCOUNT_SETTINGS:
892 onSettingsChanged(null);
893 break;
894 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800895 }
Mindy Pereira674afa42012-02-17 14:05:24 -0800896
Mindy Pereira674afa42012-02-17 14:05:24 -0800897 @Override
898 public void onTouchEvent(MotionEvent event) {
899 if (event.getAction() == MotionEvent.ACTION_DOWN) {
900 int mode = mViewMode.getMode();
901 if (mode == ViewMode.CONVERSATION_LIST) {
902 mConversationListFragment.onTouchEvent(event);
903 } else if (mode == ViewMode.CONVERSATION) {
904 mConversationViewFragment.onTouchEvent(event);
905 }
906 }
907 }
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800908
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700909 private class FetchInboxTask extends AsyncTask<Void, Void, ConversationListContext> {
910 @Override
911 public ConversationListContext doInBackground(Void... params) {
912 // Gets the default inbox since there is no context.
913 return ConversationListContext.forFolder(mActivity.getActivityContext(), mAccount,
914 (Folder) null);
915 }
916
917 @Override
918 public void onPostExecute(ConversationListContext result) {
919 mConvListContext = result;
920 setFolder(mConvListContext.folder);
921 if (mFolderListFragment != null) {
922 mFolderListFragment.selectFolder(mConvListContext.folder);
923 }
924 showConversationList(mConvListContext);
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700925
926 // Add the folder that we were viewing to the recent folders list.
927 updateRecentFolderList();
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700928 }
929 }
930
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800931 private class FetchAccountFolderTask extends AsyncTask<Void, Void, ConversationListContext> {
932 @Override
933 public ConversationListContext doInBackground(Void... params) {
Mindy Pereira161f50d2012-02-28 15:47:19 -0800934 return ConversationListContext.forFolder(mContext, mAccount, mFolder);
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800935 }
936
937 @Override
938 public void onPostExecute(ConversationListContext result) {
939 mConvListContext = result;
Mindy Pereira3982e232012-02-29 15:00:34 -0800940 setFolder(mConvListContext.folder);
Mindy Pereira62c5af82012-03-05 10:08:38 -0800941 if (mFolderListFragment != null) {
942 mFolderListFragment.selectFolder(mConvListContext.folder);
943 }
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800944 showConversationList(mConvListContext);
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800945 mFetchAccountFolderTask = null;
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700946
947 // Add the folder that we were viewing to the recent folders list.
948 updateRecentFolderList();
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800949 }
950 }
Mindy Pereirae0828392012-03-08 10:38:40 -0800951
952 private class FetchSearchFolderTask extends AsyncTask<Void, Void, Folder> {
953 String mQuery;
954 public FetchSearchFolderTask(String query) {
955 mQuery = query;
956 }
Vikram Aggarwal276c80b2012-03-09 11:07:07 -0800957
958 @Override
Mindy Pereirae0828392012-03-08 10:38:40 -0800959 public Folder doInBackground(Void... params) {
960 Folder searchFolder = Folder.forSearchResults(mAccount, mQuery,
961 mActivity.getActivityContext());
962 return searchFolder;
963 }
Vikram Aggarwal276c80b2012-03-09 11:07:07 -0800964
965 @Override
Mindy Pereirae0828392012-03-08 10:38:40 -0800966 public void onPostExecute(Folder folder) {
967 setFolder(folder);
968 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder, mQuery);
969 showConversationList(mConvListContext);
970 mActivity.invalidateOptionsMenu();
971 }
972 }
Mindy Pereirafbe40192012-03-20 10:40:45 -0700973
974 protected abstract class DestructiveActionListener implements ActionCompleteListener {
975 protected final int mAction;
976
977 /**
978 * Create a listener object. action is one of four constants: R.id.y_button (archive),
979 * R.id.delete , R.id.mute, and R.id.report_spam.
980 * @param action
981 */
982 public DestructiveActionListener(int action) {
983 mAction = action;
984 }
985
986 public void performConversationAction(Collection<Conversation> single) {
987 switch (mAction) {
988 case R.id.y_button:
989 LogUtils.d(LOG_TAG, "Archiving conversation " + mCurrentConversation);
990 Conversation.archive(mContext, single);
991 break;
992 case R.id.delete:
993 LogUtils.d(LOG_TAG, "Deleting conversation " + mCurrentConversation);
994 Conversation.delete(mContext, single);
995 break;
996 case R.id.mute:
997 LogUtils.d(LOG_TAG, "Muting conversation " + mCurrentConversation);
998 if (mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE))
999 mCurrentConversation.localDeleteOnUpdate = true;
1000 Conversation.mute(mContext, single);
1001 break;
1002 case R.id.report_spam:
1003 LogUtils.d(LOG_TAG, "reporting spam conversation " + mCurrentConversation);
1004 Conversation.reportSpam(mContext, single);
1005 break;
1006 }
1007 }
1008
1009 public Conversation getNextConversation() {
1010 Conversation next = null;
1011 int pref = getAutoAdvanceSetting(mActivity);
1012 Cursor c = mConversationListFragment.getConversationListCursor();
1013 if (c != null) {
1014 c.moveToPosition(mCurrentConversation.position);
1015 }
1016 switch (pref) {
1017 case AutoAdvance.NEWER:
1018 if (c.moveToPrevious()) {
1019 next = new Conversation(c);
1020 }
1021 break;
1022 case AutoAdvance.OLDER:
1023 if (c.moveToNext()) {
1024 next = new Conversation(c);
1025 }
1026 break;
1027 }
1028 return next;
1029 }
1030
1031 @Override
1032 public abstract void onActionComplete();
1033 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001034
1035 // Called from the FolderSelectionDialog after a user is done changing
1036 // folders.
1037 @Override
1038 public void onCommit(String uris) {
1039 // Get currently active folder info and compare it to the list
1040 // these conversations have been given; if they no longer contain
1041 // the selected folder, delete them from the list.
1042 HashSet<String> folderUris = new HashSet<String>();
1043 if (!TextUtils.isEmpty(uris)) {
1044 folderUris.addAll(Arrays.asList(uris.split(",")));
1045 }
1046 final boolean destructiveChange = !folderUris.contains(mFolder.uri);
1047 DestructiveActionListener listener = getFolderDestructiveActionListener();
1048 if (destructiveChange) {
1049 mCurrentConversation.localDeleteOnUpdate = true;
1050 mConversationListFragment.requestDelete(listener);
1051 } else {
1052 final ArrayList<Conversation> single = new ArrayList<Conversation>();
1053 single.add(mCurrentConversation);
1054 listener.performConversationAction(single);
1055 if (mConversationListFragment != null) {
1056 mConversationListFragment.requestListRefresh();
1057 }
1058 }
1059 }
1060
1061 protected abstract DestructiveActionListener getFolderDestructiveActionListener();
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08001062}