blob: c5112ebe85e724f440d43aa1b154a1acd376dfd7 [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 Aggarwal54452ae2012-03-13 15:29:00 -070020import com.google.common.collect.ImmutableList;
Andy Huangf9a73482012-03-13 15:54:02 -070021import com.google.common.collect.Sets;
22
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080023import android.app.ActionBar;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080024import android.app.ActionBar.LayoutParams;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080025import android.app.Activity;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070026import android.app.AlertDialog;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080027import android.app.Dialog;
Andy Huangf9a73482012-03-13 15:54:02 -070028import android.app.LoaderManager;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080029import android.content.ContentResolver;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080030import android.content.Context;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080031import android.content.CursorLoader;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070032import android.content.DialogInterface;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080033import android.content.Intent;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080034import android.content.Loader;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080035import android.database.Cursor;
Paul Westbrook23b74b92012-02-29 11:36:12 -080036import android.net.Uri;
Paul Westbrookdaef1ce2012-02-23 16:42:00 -080037import android.os.AsyncTask;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080038import android.os.Bundle;
Mindy Pereira21ab4902012-03-19 18:48:03 -070039import android.os.Handler;
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -070040import android.text.TextUtils;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080041import android.view.KeyEvent;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080042import android.view.LayoutInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080043import android.view.Menu;
Mindy Pereira28d5f722012-02-15 12:32:40 -080044import android.view.MenuInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080045import android.view.MenuItem;
46import android.view.MotionEvent;
47
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080048import com.android.mail.ConversationListContext;
Andy Huangf9a73482012-03-13 15:54:02 -070049import com.android.mail.R;
Mindy Pereira9b875682012-02-15 18:10:54 -080050import com.android.mail.compose.ComposeActivity;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080051import com.android.mail.providers.Account;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080052import com.android.mail.providers.AccountCacheProvider;
Mindy Pereira9b875682012-02-15 18:10:54 -080053import com.android.mail.providers.Conversation;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080054import com.android.mail.providers.Folder;
Mindy Pereiradac00542012-03-01 10:50:33 -080055import com.android.mail.providers.Settings;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080056import com.android.mail.providers.UIProvider;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070057import com.android.mail.providers.UIProvider.AutoAdvance;
58import com.android.mail.providers.UIProvider.ConversationColumns;
59import com.android.mail.providers.UIProvider.FolderCapabilities;
60import com.android.mail.ui.AsyncRefreshTask;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080061import com.android.mail.utils.LogUtils;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -080062import com.android.mail.utils.Utils;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080063
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -070064import java.util.Arrays;
65import java.util.Collections;
66import java.util.HashSet;
Paul Westbrook23b74b92012-02-29 11:36:12 -080067import java.util.Set;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070068import java.util.ArrayList;
Paul Westbrook23b74b92012-02-29 11:36:12 -080069
70
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080071/**
Mindy Pereira161f50d2012-02-28 15:47:19 -080072 * This is an abstract implementation of the Activity Controller. This class
73 * knows how to respond to menu items, state changes, layout changes, etc. It
74 * weaves together the views and listeners, dispatching actions to the
75 * respective underlying classes.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080076 * <p>
Mindy Pereira161f50d2012-02-28 15:47:19 -080077 * Even though this class is abstract, it should provide default implementations
78 * for most, if not all the methods in the ActivityController interface. This
79 * makes the task of the subclasses easier: OnePaneActivityController and
80 * TwoPaneActivityController can be concise when the common functionality is in
81 * AbstractActivityController.
82 * </p>
83 * <p>
84 * In the Gmail codebase, this was called BaseActivityController
85 * </p>
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080086 */
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080087public abstract class AbstractActivityController implements ActivityController {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080088 // Keys for serialization of various information in Bundles.
89 private static final String SAVED_LIST_CONTEXT = "saved-list-context";
Vikram Aggarwal6c511582012-02-27 10:59:47 -080090 private static final String SAVED_ACCOUNT = "saved-account";
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080091
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -070092 /** Are we on a tablet device or not. */
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -080093 public final boolean IS_TABLET_DEVICE;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080094
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -080095 protected Account mAccount;
Mindy Pereira28e0c342012-02-17 15:05:13 -080096 protected Folder mFolder;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -080097 protected ActionBarView mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080098 protected final RestrictedActivity mActivity;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080099 protected final Context mContext;
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800100 protected final RecentFolderList mRecentFolderList;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800101 protected ConversationListContext mConvListContext;
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800102 private FetchAccountFolderTask mFetchAccountFolderTask;
Mindy Pereira9b875682012-02-15 18:10:54 -0800103 protected Conversation mCurrentConversation;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800104
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700105 /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
106 private SuppressNotificationReceiver mNewEmailReceiver = null;
107
Mindy Pereira21ab4902012-03-19 18:48:03 -0700108 private Handler mHandler = new Handler();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800109 protected ConversationListFragment mConversationListFragment;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800110 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800111 * The current mode of the application. All changes in mode are initiated by
112 * the activity controller. View mode changes are propagated to classes that
113 * attach themselves as listeners of view mode changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800114 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800115 protected final ViewMode mViewMode;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800116 protected ContentResolver mResolver;
Mindy Pereira0825efd2012-02-15 17:30:32 -0800117 protected FolderListFragment mFolderListFragment;
Mindy Pereira9b875682012-02-15 18:10:54 -0800118 protected ConversationViewFragment mConversationViewFragment;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800119 protected boolean isLoaderInitialized = false;
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800120 private AsyncRefreshTask mAsyncRefreshTask;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800121
Paul Westbrook23b74b92012-02-29 11:36:12 -0800122 private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
Mindy Pereira559c3b32012-03-01 13:17:47 -0800123 protected Settings mCachedSettings;
Mindy Pereirae0828392012-03-08 10:38:40 -0800124 private FetchSearchFolderTask mFetchSearchFolderTask;
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700125 private FetchInboxTask mFetchInboxTask;
Mindy Pereiradac00542012-03-01 10:50:33 -0800126
Vikram Aggarwal04ff99c2012-02-28 15:29:13 -0800127 protected static final String LOG_TAG = new LogUtils().getLogTag();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800128 /** Constants used to differentiate between the types of loaders. */
129 private static final int LOADER_ACCOUNT_CURSOR = 0;
130 private static final int LOADER_ACCOUNT_SETTINGS = 1;
131 private static final int LOADER_FOLDER_CURSOR = 2;
132 private static final int LOADER_RECENT_FOLDERS = 3;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800133
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700134 private final ActionCompleteListener mDeleteListener = new DestructiveActionListener(
135 R.id.delete);
136 private final ActionCompleteListener mArchiveListener = new DestructiveActionListener(
137 R.id.archive);
138 private final ActionCompleteListener mMuteListener = new DestructiveActionListener(
139 R.id.mute);
140 private final ActionCompleteListener mSpamListener = new DestructiveActionListener(
141 R.id.report_spam);
142
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800143 public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
144 mActivity = activity;
145 mViewMode = viewMode;
146 mContext = activity.getApplicationContext();
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800147 IS_TABLET_DEVICE = Utils.useTabletUI(mContext);
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800148 mRecentFolderList = new RecentFolderList(mContext);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800149 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800150
151 @Override
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800152 public synchronized void attachConversationList(ConversationListFragment fragment) {
153 // If there is an existing fragment, unregister it
154 if (mConversationListFragment != null) {
155 mViewMode.removeListener(mConversationListFragment);
156 }
157 mConversationListFragment = fragment;
158 // If the current fragment is non-null, add it as a listener.
159 if (fragment != null) {
160 mViewMode.addListener(mConversationListFragment);
161 }
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800162 }
163
164 @Override
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800165 public synchronized void attachFolderList(FolderListFragment fragment) {
166 // If there is an existing fragment, unregister it
167 if (mFolderListFragment != null) {
168 mViewMode.removeListener(mFolderListFragment);
169 }
170 mFolderListFragment = fragment;
171 if (fragment != null) {
172 mViewMode.addListener(mFolderListFragment);
173 }
Mindy Pereira555140c2012-02-15 14:55:29 -0800174 }
175
176 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -0800177 public void attachConversationView(ConversationViewFragment conversationViewFragment) {
178 mConversationViewFragment = conversationViewFragment;
179 }
180
181 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800182 public void clearSubject() {
183 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800184 }
185
186 @Override
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800187 public Account getCurrentAccount() {
188 return mAccount;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800189 }
190
191 @Override
192 public ConversationListContext getCurrentListContext() {
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800193 return mConvListContext;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800194 }
195
196 @Override
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800197 public String getHelpContext() {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800198 return "Mail";
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800199 }
200
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800201 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800202 public int getMode() {
203 return mViewMode.getMode();
204 }
205
206 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800207 public String getUnshownSubject(String subject) {
208 // Calculate how much of the subject is shown, and return the remaining.
209 return null;
210 }
211
212 @Override
213 public void handleConversationLoadError() {
214 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800215 }
216
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800217 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800218 * Initialize the action bar. This is not visible to OnePaneController and
219 * TwoPaneController so they cannot override this behavior.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800220 */
221 private void initCustomActionBarView() {
222 ActionBar actionBar = mActivity.getActionBar();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800223 mActionBarView = (ActionBarView) LayoutInflater.from(mContext).inflate(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800224 R.layout.actionbar_view, null);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800225 if (actionBar != null && mActionBarView != null) {
Mindy Pereira161f50d2012-02-28 15:47:19 -0800226 // Why have a different variable for the same thing? We should apply
227 // the same actions
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800228 // on mActionBarView instead.
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800229 mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
230 actionBar.setCustomView(mActionBarView, new ActionBar.LayoutParams(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800231 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
232 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
Vikram Aggarwal2a25d0c2012-02-21 16:43:10 -0800233 ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800234 }
235 }
236
237 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800238 * Returns whether the conversation list fragment is visible or not.
239 * Different layouts will have their own notion on the visibility of
240 * fragments, so this method needs to be overriden.
241 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800242 * @return
243 */
244 protected abstract boolean isConversationListVisible();
245
246 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800247 public void onAccountChanged(Account account) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800248 if (!account.equals(mAccount)) {
249 mAccount = account;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800250 mRecentFolderList.setCurrentAccount(account);
Andy Huangf9a73482012-03-13 15:54:02 -0700251 restartOptionalLoader(LOADER_RECENT_FOLDERS, null /* args */);
252 restartOptionalLoader(LOADER_ACCOUNT_SETTINGS, null /* args */);
Mindy Pereirabc57bf12012-02-29 14:39:09 -0800253 mActionBarView.setAccount(mAccount);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800254 mActivity.invalidateOptionsMenu();
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700255
256 disableNotificationsOnAccountChange(mAccount);
257
Mindy Pereira161f50d2012-02-28 15:47:19 -0800258 // Account changed; existing folder is invalid.
259 mFolder = null;
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800260 fetchAccountFolderInfo();
Mindy Pereira28d5f722012-02-15 12:32:40 -0800261 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800262 }
263
Mindy Pereiradac00542012-03-01 10:50:33 -0800264 public void onSettingsChanged(Settings settings) {
265 mCachedSettings = settings;
Mindy Pereira559c3b32012-03-01 13:17:47 -0800266 resetActionBarIcon();
Mindy Pereiradac00542012-03-01 10:50:33 -0800267 }
268
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800269 @Override
270 public Settings getSettings() {
271 return mCachedSettings;
272 }
273
Paul Westbrookdaef1ce2012-02-23 16:42:00 -0800274 private void fetchAccountFolderInfo() {
275 if (mFetchAccountFolderTask != null) {
276 mFetchAccountFolderTask.cancel(true);
277 }
278 mFetchAccountFolderTask = new FetchAccountFolderTask();
279 mFetchAccountFolderTask.execute();
280 }
281
Mindy Pereirae0828392012-03-08 10:38:40 -0800282 private void fetchSearchFolder(Intent intent) {
283 if (mFetchSearchFolderTask != null) {
284 mFetchSearchFolderTask.cancel(true);
285 }
286 mFetchSearchFolderTask = new FetchSearchFolderTask(intent
287 .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
288 mFetchSearchFolderTask.execute();
289 }
290
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800291 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800292 public void onFolderChanged(Folder folder) {
Mindy Pereira7a3471f2012-03-06 12:23:41 -0800293 if (folder != null && !folder.equals(mFolder)) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800294 setFolder(folder);
Mindy Pereira161f50d2012-02-28 15:47:19 -0800295 mConvListContext = ConversationListContext.forFolder(mContext, mAccount, mFolder);
Mindy Pereira28e0c342012-02-17 15:05:13 -0800296 showConversationList(mConvListContext);
Paul Westbrook9024b6d2012-03-19 13:57:55 -0700297
298 // Add the folder that we were viewing to the recent folders list.
299 // TODO: this may need to be fine tuned. If this is the signal that is indicating that
300 // the list is shown to the user, this could fire in one pane if the user goes directly
301 // to a conversation
302 mRecentFolderList.touchFolder(mFolder);
Mindy Pereira28e0c342012-02-17 15:05:13 -0800303 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800304 }
305
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700306 // TODO(mindyp): set this up to store a copy of the folder locally
307 // as soon as we realize we haven't gotten the inbox folder yet.
308 public void loadInbox() {
309 if (mFetchInboxTask != null) {
310 mFetchInboxTask.cancel(true);
311 }
312 mFetchInboxTask = new FetchInboxTask();
313 mFetchInboxTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
314 }
315
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800316 /** Set the current folder */
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800317 private void setFolder(Folder folder) {
318 // Start watching folder for sync status.
Mindy Pereirae0458e82012-03-06 11:54:55 -0800319 if (folder != null && !folder.equals(mFolder)) {
Mindy Pereira9b623802012-03-07 17:15:49 -0800320 mActionBarView.setRefreshInProgress(false);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800321 mFolder = folder;
Mindy Pereiraf9323cd2012-02-29 13:47:09 -0800322 mActionBarView.setFolder(mFolder);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800323 mActivity.getLoaderManager().restartLoader(LOADER_FOLDER_CURSOR, null, this);
Mindy Pereirae0458e82012-03-06 11:54:55 -0800324 } else if (folder == null) {
325 LogUtils.wtf(LOG_TAG, "Folder in setFolder is null");
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800326 }
327 }
328
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800329 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800330 public void onActivityResult(int requestCode, int resultCode, Intent data) {
331 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800332 }
333
334 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800335 public void onConversationListVisibilityChanged(boolean visible) {
336 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800337 }
338
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800339 /**
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800340 * By default, doing nothing is right. A two-pane controller will need to
341 * override this.
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800342 */
343 @Override
344 public void onConversationVisibilityChanged(boolean visible) {
345 // Do nothing.
346 return;
347 }
348
349 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800350 public boolean onCreate(Bundle savedState) {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800351 // Initialize the action bar view.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800352 initCustomActionBarView();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800353 // Allow shortcut keys to function for the ActionBar and menus.
354 mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800355 mResolver = mActivity.getContentResolver();
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800356
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700357 mNewEmailReceiver = new SuppressNotificationReceiver();
358
Mindy Pereira161f50d2012-02-28 15:47:19 -0800359 // All the individual UI components listen for ViewMode changes. This
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800360 // simplifies the amount of logic in the AbstractActivityController, but increases the
361 // possibility of timing-related bugs.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800362 mViewMode.addListener(this);
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -0800363 assert (mActionBarView != null);
364 mViewMode.addListener(mActionBarView);
365
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800366 restoreState(savedState);
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800367 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800368 }
369
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800370 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800371 public Dialog onCreateDialog(int id, Bundle bundle) {
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800372 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800373 return null;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800374 }
375
376 @Override
377 public boolean onCreateOptionsMenu(Menu menu) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800378 MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800379 inflater.inflate(mActionBarView.getOptionsMenuId(), menu);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800380 mActionBarView.onCreateOptionsMenu(menu);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800381 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800382 }
383
384 @Override
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800385 public boolean onKeyDown(int keyCode, KeyEvent event) {
386 // TODO(viki): Auto-generated method stub
387 return false;
388 }
389
390 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800391 public boolean onOptionsItemSelected(MenuItem item) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700392 final int id = item.getItemId();
393 LogUtils.d(LOG_TAG, "onOptionsItemSelected with id: " + id + ", And as hex " +
394 Integer.toHexString(id));
Mindy Pereira9b875682012-02-15 18:10:54 -0800395 boolean handled = true;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800396 switch (id) {
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800397 case android.R.id.home:
398 onUpPressed();
399 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800400 case R.id.compose:
401 ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
402 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800403 case R.id.show_all_folders:
404 showFolderList();
405 break;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800406 case R.id.refresh:
407 requestFolderRefresh();
408 break;
Mindy Pereira1f936682012-03-02 11:30:33 -0800409 case R.id.settings:
410 Utils.showSettings(mActivity.getActivityContext(), mAccount);
Paul Westbrook2861b6a2012-02-15 15:25:34 -0800411 break;
Paul Westbrook94e440d2012-02-24 11:03:47 -0800412 case R.id.help_info_menu_item:
413 // TODO: enable context sensitive help
414 Utils.showHelp(mActivity.getActivityContext(), mAccount.helpIntentUri, null);
415 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700416 case R.id.y_button: {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700417 final Settings settings = mActivity.getSettings();
418 final boolean showDialog = (settings != null && settings.confirmArchive);
419 final int autoAdvance = settings != null
420 ? settings.autoAdvance : AutoAdvance.LIST;
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700421 confirmAndDelete(showDialog, R.plurals.confirm_archive_conversation,
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700422 mArchiveListener);
423 break;
424 }
425 case R.id.delete: {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700426 final Settings settings = mActivity.getSettings();
427 final boolean showDialog = (settings != null && settings.confirmDelete);
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700428 confirmAndDelete(showDialog, R.plurals.confirm_delete_conversation,
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700429 mDeleteListener);
430 break;
431 }
432 case R.id.change_folders:
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700433 new FoldersSelectionDialog(mActivity.getActivityContext(), mAccount, this,
434 Collections.singletonList(mCurrentConversation)).show();
435 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700436 case R.id.inside_conversation_unread:
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700437 updateCurrentConversation(ConversationColumns.READ, false);
438 break;
439 case R.id.mark_important:
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700440 updateCurrentConversation(ConversationColumns.PRIORITY,
441 UIProvider.ConversationPriority.HIGH);
442 break;
443 case R.id.mark_not_important:
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700444 updateCurrentConversation(ConversationColumns.PRIORITY,
445 UIProvider.ConversationPriority.LOW);
446 break;
447 case R.id.mute:
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700448 mConversationListFragment.requestDelete(mMuteListener);
449 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700450 case R.id.report_spam:
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700451 mConversationListFragment.requestDelete(mSpamListener);
452 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700453 case R.id.feedback_menu_item:
Mindy Pereira9b875682012-02-15 18:10:54 -0800454 default:
455 handled = false;
456 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800457 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800458 return handled;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800459 }
460
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700461 /**
462 * Confirm (based on user's settings) and delete a conversation from the conversation list and
463 * from the database.
464 * @param showDialog
465 * @param confirmResource
466 * @param listener
467 */
468 private void confirmAndDelete(boolean showDialog, int confirmResource,
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700469 final ActionCompleteListener listener) {
470 final ArrayList<Conversation> single = new ArrayList<Conversation>();
471 final Settings settings = mActivity.getSettings();
472 final int autoAdvance = settings != null ? settings.autoAdvance : AutoAdvance.LIST;
473 single.add(mCurrentConversation);
474 if (showDialog) {
475 final AlertDialog.OnClickListener onClick = new AlertDialog.OnClickListener() {
476 @Override
477 public void onClick(DialogInterface dialog, int which) {
Mindy Pereira21ab4902012-03-19 18:48:03 -0700478 requestDelete(listener);
479 // mConversationListFragment.requestDelete(listener);
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700480 }
481 };
482 final CharSequence message = Utils.formatPlural(mContext, confirmResource, 1);
483 new AlertDialog.Builder(mActivity.getActivityContext()).setMessage(message)
484 .setPositiveButton(R.string.ok, onClick)
485 .setNegativeButton(R.string.cancel, null)
486 .create().show();
487 } else {
Mindy Pereira21ab4902012-03-19 18:48:03 -0700488 requestDelete(listener);
489 }
490 }
491
492 private void requestDelete(final ActionCompleteListener listener) {
493 // TODO: don't do this in tablet.
494 int pref = getAutoAdvanceSetting(mActivity);
495 boolean canMove = false;
496 int position = mCurrentConversation.position;
497 switch (pref) {
498 case AutoAdvance.NEWER:
499 canMove = position - 1 >= 0;
500 break;
501 case AutoAdvance.OLDER:
502 Cursor c = mConversationListFragment.getConversationListCursor();
503 if (c != null) {
504 canMove = position + 1 < c.getCount();
505 }
506 break;
507 }
508 if (pref == AutoAdvance.LIST || !canMove) {
509 onBackPressed();
510 mHandler.post(new Runnable() {
511
512 @Override
513 public void run() {
514 mConversationListFragment.requestDelete(listener);
515 }
516
517 });
518 } else {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700519 mConversationListFragment.requestDelete(listener);
520 }
521 }
522
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700523 /**
Mindy Pereira21ab4902012-03-19 18:48:03 -0700524 * Return the auto advance setting for the current account.
525 * @param activity
526 * @return the autoadvance setting, a constant from {@link AutoAdvance}
527 */
528 static int getAutoAdvanceSetting(RestrictedActivity activity) {
529 final Settings settings = activity.getSettings();
530 // TODO(mindyp): if this isn't set, then show the dialog telling the user to set it.
531 // Remove defaulting to AutoAdvance.LIST.
532 final int autoAdvance = (settings != null) ?
533 (settings.autoAdvance == AutoAdvance.UNSET ?
534 AutoAdvance.LIST : settings.autoAdvance)
535 : AutoAdvance.LIST;
536 return autoAdvance;
537 }
538
539 /**
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700540 * An object that performs an action on the conversation database. This is an
541 * ActionCompleteListener since this is called <b>after</a> the conversation list has animated
542 * the conversation away. Once the animation is completed, the {@link #onActionComplete()}
543 * method is called which performs the correct data operation.
544 */
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700545 private class DestructiveActionListener implements ActionCompleteListener {
546 private final int mAction;
547
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700548 /**
549 * Create a listener object. action is one of four constants: R.id.y_button (archive),
550 * R.id.delete , R.id.mute, and R.id.report_spam.
551 * @param action
552 */
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700553 public DestructiveActionListener(int action) {
554 mAction = action;
555 }
556
557 @Override
558 public void onActionComplete() {
559 LogUtils.d(LOG_TAG, "in onActionComplete with conversation " + mCurrentConversation);
Mindy Pereira21ab4902012-03-19 18:48:03 -0700560 Conversation next = null;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700561 final ArrayList<Conversation> single = new ArrayList<Conversation>();
562 single.add(mCurrentConversation);
Mindy Pereira21ab4902012-03-19 18:48:03 -0700563 if (!mConversationListFragment.isVisible()) {
564 int pref = getAutoAdvanceSetting(mActivity);
565 Cursor c = mConversationListFragment.getConversationListCursor();
566 if (c != null) {
567 c.moveToPosition(mCurrentConversation.position);
568 }
569 switch (pref) {
570 case AutoAdvance.NEWER:
571 if (c.moveToPrevious()) {
572 next = new Conversation(c);
573 }
574 break;
575 case AutoAdvance.OLDER:
576 if (c.moveToNext()) {
577 next = new Conversation(c);
578 }
579 break;
580 }
581 } else {
582 mConversationListFragment.onActionComplete();
583 mConversationListFragment.onUndoAvailable(new UndoOperation(1, mAction));
584 }
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700585 switch (mAction) {
586 case R.id.y_button:
587 LogUtils.d(LOG_TAG, "Archiving conversation " + mCurrentConversation);
588 Conversation.archive(mContext, single);
589 break;
590 case R.id.delete:
591 LogUtils.d(LOG_TAG, "Deleting conversation " + mCurrentConversation);
592 Conversation.delete(mContext, single);
593 break;
594 case R.id.mute:
595 LogUtils.d(LOG_TAG, "Muting conversation " + mCurrentConversation);
596 if (mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE))
597 mCurrentConversation.localDeleteOnUpdate = true;
598 Conversation.mute(mContext, single);
599 break;
600 case R.id.report_spam:
601 LogUtils.d(LOG_TAG, "reporting spam conversation " + mCurrentConversation);
602 Conversation.reportSpam(mContext, single);
603 break;
604 }
605 mConversationListFragment.requestListRefresh();
Mindy Pereira21ab4902012-03-19 18:48:03 -0700606 if (next != null) {
607 showConversation(next);
608 } else {
609 onBackPressed();
610 }
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700611 }
612 }
613
614 /**
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700615 * Implements folder changes. This class is a listener because folder changes need to be
616 * performed <b>after</b> the ConversationListFragment has finished animating away the
617 * removal of the conversation.
618 *
619 */
620 private class FolderChangeListener implements ActionCompleteListener {
621 private final String mFolderChangeList;
622 private final boolean mDestructiveChange;
623
624 public FolderChangeListener(String changeList, boolean destructive) {
625 mFolderChangeList = changeList;
626 mDestructiveChange = destructive;
627 }
628
629 @Override
630 public void onActionComplete() {
631 // Only show undo if this was a destructive folder change.
632 if (mDestructiveChange) {
633 mConversationListFragment.onUndoAvailable(new UndoOperation(1, R.id.change_folder));
634 }
635 // Update the folders for this conversation
636 Conversation.updateString(mContext, Collections.singletonList(mCurrentConversation),
637 ConversationColumns.FOLDER_LIST, mFolderChangeList);
638 mConversationListFragment.requestListRefresh();
639 }
640 }
641
642 /**
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700643 * Update the specified column name in conversation for a boolean value.
644 * @param columnName
645 * @param value
646 */
647 private void updateCurrentConversation(String columnName, boolean value) {
648 Conversation.updateBoolean(mContext, ImmutableList.of(mCurrentConversation), columnName,
649 value);
650 mConversationListFragment.requestListRefresh();
651 }
652
653 /**
654 * Update the specified column name in conversation for an integer value.
655 * @param columnName
656 * @param value
657 */
658 private void updateCurrentConversation(String columnName, int value) {
659 Conversation.updateInt(mContext, ImmutableList.of(mCurrentConversation), columnName, value);
660 mConversationListFragment.requestListRefresh();
661 }
662
Mindy Pereira28e0c342012-02-17 15:05:13 -0800663 private void requestFolderRefresh() {
664 if (mFolder != null) {
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800665 if (mAsyncRefreshTask != null) {
666 mAsyncRefreshTask.cancel(true);
667 }
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800668 mAsyncRefreshTask = new AsyncRefreshTask(mContext, mFolder);
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800669 mAsyncRefreshTask.execute();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800670 }
671 }
672
Mindy Pereira363451a2012-02-22 14:14:46 -0800673 @Override
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -0700674 public void onCommit(String uris) {
675 // Get currently active folder info and compare it to the list
676 // these conversations have been given; if they no longer contain
677 // the selected folder, delete them from the list.
678 HashSet<String> folderUris = new HashSet<String>();
679 if (!TextUtils.isEmpty(uris)) {
680 folderUris.addAll(Arrays.asList(uris.split(",")));
681 }
682 final boolean destructiveChange = !folderUris.contains(mFolder.uri);
683 FolderChangeListener listener = new FolderChangeListener(uris, destructiveChange);
684 if (destructiveChange) {
685 mCurrentConversation.localDeleteOnUpdate = true;
686 mConversationListFragment.requestDelete(listener);
687 } else {
688 listener.onActionComplete();
689 }
690 }
691
692 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800693 public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
694 // TODO(viki): Auto-generated method stub
695
696 }
697
698 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800699 public boolean onPrepareOptionsMenu(Menu menu) {
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800700 mActionBarView.onPrepareOptionsMenu(menu);
Mindy Pereira363451a2012-02-22 14:14:46 -0800701 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800702 }
703
Mindy Pereira68f2e222012-03-07 10:36:54 -0800704 @Override
705 public void onPause() {
706 isLoaderInitialized = false;
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700707
708 enableNotifications();
Paul Westbrook94e440d2012-02-24 11:03:47 -0800709 }
710
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800711 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800712 public void onResume() {
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700713 // Register the receiver that will prevent the status receiver from
714 // displaying its notification icon as long as we're running.
715 // The SupressNotificationReceiver will block the broadcast if we're looking at the folder
716 // that the notification was received for.
717 disableNotifications();
718
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800719 if (mActionBarView != null) {
720 mActionBarView.onResume();
721 }
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700722
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800723 }
724
725 @Override
726 public void onSaveInstanceState(Bundle outState) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800727 if (mAccount != null) {
728 LogUtils.d(LOG_TAG, "Saving the account now");
729 outState.putParcelable(SAVED_ACCOUNT, mAccount);
730 }
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800731 if (mConvListContext != null) {
732 outState.putBundle(SAVED_LIST_CONTEXT, mConvListContext.toBundle());
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800733 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800734 }
735
736 @Override
Mindy Pereira68f2e222012-03-07 10:36:54 -0800737 public void onSearchRequested(String query) {
738 Intent intent = new Intent();
739 intent.setAction(Intent.ACTION_SEARCH);
740 intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query);
741 intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount);
742 intent.setComponent(mActivity.getComponentName());
743 mActivity.startActivity(intent);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800744 }
745
746 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800747 public void onStartDragMode() {
748 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800749 }
750
751 @Override
752 public void onStop() {
753 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800754 }
755
756 @Override
757 public void onStopDragMode() {
758 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800759 }
760
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800761 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800762 * {@inheritDoc} Subclasses must override this to listen to mode changes
763 * from the ViewMode. Subclasses <b>must</b> call the parent's
764 * onViewModeChanged since the parent will handle common state changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800765 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800766 @Override
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800767 public void onViewModeChanged(int newMode) {
768 // Perform any mode specific work here.
Mindy Pereira161f50d2012-02-28 15:47:19 -0800769 // reset the action bar icon based on the mode. Why don't the individual
770 // controllers do
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800771 // this themselves?
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800772
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800773 // In conversation list mode, clean up the conversation.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800774 if (newMode == ViewMode.CONVERSATION_LIST) {
775 // Clean up the conversation here.
776 }
777
Mindy Pereira161f50d2012-02-28 15:47:19 -0800778 // We don't want to invalidate the options menu when switching to
779 // conversation
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800780 // mode, as it will happen when the conversation finishes loading.
781 if (newMode != ViewMode.CONVERSATION) {
782 mActivity.invalidateOptionsMenu();
783 }
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800784 }
785
786 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800787 public void onWindowFocusChanged(boolean hasFocus) {
788 // TODO(viki): Auto-generated method stub
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800789 }
790
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800791 /**
792 * @param savedState
793 */
794 protected void restoreListContext(Bundle savedState) {
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800795 Bundle listContextBundle = savedState.getBundle(SAVED_LIST_CONTEXT);
796 if (listContextBundle != null) {
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800797 mConvListContext = ConversationListContext.forBundle(listContextBundle);
Mindy Pereiradac00542012-03-01 10:50:33 -0800798 mFolder = mConvListContext.folder;
Vikram Aggarwal8b152632012-02-03 14:58:45 -0800799 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800800 }
801
802 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800803 * Restore the state from the previous bundle. Subclasses should call this
804 * method from the parent class, since it performs important UI
805 * initialization.
806 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800807 * @param savedState
808 */
809 protected void restoreState(Bundle savedState) {
Mindy Pereira68f2e222012-03-07 10:36:54 -0800810 final Intent intent = mActivity.getIntent();
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800811 if (savedState != null) {
812 restoreListContext(savedState);
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800813 mAccount = savedState.getParcelable(SAVED_ACCOUNT);
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800814 mActionBarView.setAccount(mAccount);
Andy Huangf9a73482012-03-13 15:54:02 -0700815 restartOptionalLoader(LOADER_ACCOUNT_SETTINGS, null /* args */);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800816 } else if (intent != null) {
817 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Mindy Pereirad8542ad2012-02-29 13:54:17 -0800818 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
819 mAccount = ((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800820 mActionBarView.setAccount(mAccount);
Andy Huangf9a73482012-03-13 15:54:02 -0700821 restartOptionalLoader(LOADER_ACCOUNT_SETTINGS, null /* args */);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800822 mActivity.invalidateOptionsMenu();
Mindy Pereirad8542ad2012-02-29 13:54:17 -0800823 }
824 if (intent.hasExtra(Utils.EXTRA_FOLDER)) {
825 // Open the folder.
826 LogUtils.d(LOG_TAG, "SHOW THE FOLDER at %s",
827 intent.getParcelableExtra(Utils.EXTRA_FOLDER));
828 onFolderChanged((Folder) intent.getParcelableExtra(Utils.EXTRA_FOLDER));
829 }
Mindy Pereira161f50d2012-02-28 15:47:19 -0800830 if (intent.hasExtra(Utils.EXTRA_CONVERSATION)) {
831 // Open the conversation.
832 LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
833 intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
Mindy Pereira161f50d2012-02-28 15:47:19 -0800834 showConversation((Conversation) intent
835 .getParcelableExtra(Utils.EXTRA_CONVERSATION));
Mindy Pereira161f50d2012-02-28 15:47:19 -0800836 }
Mindy Pereira68f2e222012-03-07 10:36:54 -0800837 } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
838 mViewMode.enterSearchResultsListMode();
839 mAccount = ((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800840 mActionBarView.setAccount(mAccount);
Mindy Pereirae0828392012-03-08 10:38:40 -0800841 fetchSearchFolder(intent);
Mindy Pereira161f50d2012-02-28 15:47:19 -0800842 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800843 }
Mindy Pereira68f2e222012-03-07 10:36:54 -0800844 // Create the accounts loader; this loads the account switch spinner.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800845 mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800846 }
847
848 @Override
849 public void setSubject(String subject) {
850 // Do something useful with the subject. This requires changing the
851 // conversation view's subject text.
852 }
853
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800854 /**
855 * Children can override this method, but they must call super.showConversation().
856 * {@inheritDoc}
857 */
858 @Override
859 public void showConversation(Conversation conversation) {
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800860 }
861
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800862 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -0800863 public void onConversationSelected(Conversation conversation) {
864 mCurrentConversation = conversation;
865 showConversation(mCurrentConversation);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800866 if (mConvListContext != null && mConvListContext.isSearchResult()) {
867 mViewMode.enterSearchResultsConversationMode();
868 } else {
869 mViewMode.enterConversationMode();
870 }
Vikram Aggarwal7d602882012-02-07 15:01:20 -0800871 }
Mindy Pereira555140c2012-02-15 14:55:29 -0800872
Vikram Aggarwala9b93f32012-02-23 14:51:58 -0800873 /**
874 * {@inheritDoc}
875 */
Mindy Pereira555140c2012-02-15 14:55:29 -0800876 @Override
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800877 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
878 // Create a loader to listen in on account changes.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800879 switch (id) {
880 case LOADER_ACCOUNT_CURSOR:
881 return new CursorLoader(mContext, AccountCacheProvider.getAccountsUri(),
882 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
883 case LOADER_FOLDER_CURSOR:
884 return new CursorLoader(mContext, mFolder.uri,
885 UIProvider.FOLDERS_PROJECTION, null, null, null);
886 case LOADER_ACCOUNT_SETTINGS:
887 if (mAccount.settingsQueryUri != null) {
888 return new CursorLoader(mContext, mAccount.settingsQueryUri,
889 UIProvider.SETTINGS_PROJECTION, null, null, null);
890 }
Paul Westbrookea4ee202012-03-12 14:12:33 -0700891 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800892 case LOADER_RECENT_FOLDERS:
Paul Westbrookea4ee202012-03-12 14:12:33 -0700893 if (mAccount.recentFolderListUri != null) {
894 return new CursorLoader(mContext, mAccount.recentFolderListUri,
895 UIProvider.FOLDERS_PROJECTION, null, null, null);
896 }
897 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800898 default:
899 LogUtils.wtf(LOG_TAG, "Loader returned unexpected id: " + id);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800900 }
901 return null;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800902 }
903
Andy Huangf9a73482012-03-13 15:54:02 -0700904 /**
905 * {@link LoaderManager} currently has a bug in
906 * {@link LoaderManager#restartLoader(int, Bundle, android.app.LoaderManager.LoaderCallbacks)}
907 * where, if a previous onCreateLoader returned a null loader, this method will NPE. Work around
908 * this bug by destroying any loaders that may have been created as null (essentially because
909 * they are optional loads, and may not apply to a particular account).
910 * <p>
911 * A simple null check before restarting a loader will not work, because that would not
912 * give the controller a chance to invalidate UI corresponding the prior loader result.
913 *
914 * @param id loader ID to safely restart
915 * @param args arguments to pass to the restarted loader
916 */
917 private void restartOptionalLoader(int id, Bundle args) {
918 final LoaderManager lm = mActivity.getLoaderManager();
919 lm.destroyLoader(id);
920 lm.restartLoader(id, args, this);
921 }
922
Paul Westbrook23b74b92012-02-29 11:36:12 -0800923 private boolean accountsUpdated(Cursor accountCursor) {
924 // Check to see if the current account hasn't been set, or the account cursor is empty
925 if (mAccount == null || !accountCursor.moveToFirst()) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800926 return true;
Paul Westbrook23b74b92012-02-29 11:36:12 -0800927 }
928
929 // Check to see if the number of accounts are different, from the number we saw on the last
930 // updated
931 if (mCurrentAccountUris.size() != accountCursor.getCount()) {
932 return true;
933 }
934
935 // Check to see if the account list is different or if the current account is not found in
936 // the cursor.
937 boolean foundCurrentAccount = false;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800938 do {
Paul Westbrook23b74b92012-02-29 11:36:12 -0800939 final Uri accountUri =
940 Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
941 if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
942 foundCurrentAccount = true;
943 }
944
945 if (!mCurrentAccountUris.contains(accountUri)) {
946 return true;
947 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800948 } while (accountCursor.moveToNext());
Paul Westbrook23b74b92012-02-29 11:36:12 -0800949
950 // As long as we found the current account, the list hasn't been updated
951 return !foundCurrentAccount;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800952 }
953
954 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800955 * Update the accounts on the device. This currently loads the first account
956 * in the list.
957 *
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800958 * @param loader
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800959 * @param accounts cursor into the AccountCache
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800960 * @return true if the update was successful, false otherwise
961 */
Paul Westbrook23b74b92012-02-29 11:36:12 -0800962 private boolean updateAccounts(Loader<Cursor> loader, Cursor accounts) {
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800963 if (accounts == null || !accounts.moveToFirst()) {
964 return false;
965 }
Paul Westbrook23b74b92012-02-29 11:36:12 -0800966
Vikram Aggarwala9b93f32012-02-23 14:51:58 -0800967 final Account[] allAccounts = Account.getAllAccounts(accounts);
Paul Westbrook23b74b92012-02-29 11:36:12 -0800968
969 // Save the uris for the accounts
970 mCurrentAccountUris.clear();
971 for (Account account : allAccounts) {
972 mCurrentAccountUris.add(account.uri);
973 }
974
975 final Account newAccount;
976 if (mAccount == null || !mCurrentAccountUris.contains(mAccount.uri)) {
977 accounts.moveToFirst();
978 newAccount = new Account(accounts);
979 } else {
980 newAccount = mAccount;
981 }
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700982 // Only bother updating the account/folder if the new account is different than the
Paul Westbrook23b74b92012-02-29 11:36:12 -0800983 // existing one
984 final boolean refetchFolderInfo = !newAccount.equals(mAccount);
985 onAccountChanged(newAccount);
986
987 if(refetchFolderInfo) {
988 fetchAccountFolderInfo();
989 }
990
Vikram Aggarwala9b93f32012-02-23 14:51:58 -0800991 mActionBarView.setAccounts(allAccounts);
992 return (allAccounts.length > 0);
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800993 }
994
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700995 private void disableNotifications() {
996 mNewEmailReceiver.activate(mContext, this);
997 }
998
999 private void enableNotifications() {
1000 mNewEmailReceiver.deactivate();
1001 }
1002
1003 private void disableNotificationsOnAccountChange(Account account) {
1004 // If the new mail suppression receiver is activated for a different account, we want to
1005 // activate it for the new account.
1006 if (mNewEmailReceiver.activated() &&
1007 !mNewEmailReceiver.notificationsDisabledForAccount(account)) {
1008 // Deactivate the current receiver, otherwise multiple receivers may be registered.
1009 mNewEmailReceiver.deactivate();
1010 mNewEmailReceiver.activate(mContext, this);
1011 }
1012 }
1013
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001014 /**
1015 * {@inheritDoc}
1016 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001017 @Override
1018 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001019 // We want to reinitialize only if we haven't ever been initialized, or
1020 // if the current account has vanished.
Paul Westbrooke3e84292012-03-05 16:19:30 -08001021 if (data == null) {
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001022 LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
Paul Westbrooke3e84292012-03-05 16:19:30 -08001023 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001024 switch (loader.getId()) {
1025 case LOADER_ACCOUNT_CURSOR:
1026 final boolean accountListUpdated = accountsUpdated(data);
1027 if (!isLoaderInitialized || accountListUpdated) {
1028 isLoaderInitialized = updateAccounts(loader, data);
1029 }
1030 break;
1031 case LOADER_FOLDER_CURSOR:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001032 // Check status of the cursor.
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001033 data.moveToFirst();
1034 Folder folder = new Folder(data);
Mindy Pereira11dd5ef2012-03-10 15:10:18 -08001035 if (folder.isSyncInProgress()) {
1036 mActionBarView.onRefreshStarted();
Paul Westbrookc808fac2012-02-22 16:42:18 -08001037 } else {
Mindy Pereira11dd5ef2012-03-10 15:10:18 -08001038 // Stop the spinner here.
1039 mActionBarView.onRefreshStopped(folder.lastSyncResult);
1040 }
1041 if (mConversationListFragment != null) {
1042 mConversationListFragment.onFolderUpdated(folder);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001043 }
1044 LogUtils.v(LOG_TAG, "FOLDER STATUS = " + folder.syncStatus);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001045 break;
1046 case LOADER_ACCOUNT_SETTINGS:
Mindy Pereiradac00542012-03-01 10:50:33 -08001047 data.moveToFirst();
1048 onSettingsChanged(new Settings(data));
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001049 break;
1050 case LOADER_RECENT_FOLDERS:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001051 mRecentFolderList.loadFromUiProvider(data);
1052 break;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001053 }
1054 }
1055
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001056 /**
1057 * {@inheritDoc}
1058 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001059 @Override
1060 public void onLoaderReset(Loader<Cursor> loader) {
Andy Huangf9a73482012-03-13 15:54:02 -07001061 switch (loader.getId()) {
1062 case LOADER_ACCOUNT_SETTINGS:
1063 onSettingsChanged(null);
1064 break;
1065 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001066 }
Mindy Pereira674afa42012-02-17 14:05:24 -08001067
Mindy Pereira674afa42012-02-17 14:05:24 -08001068 @Override
1069 public void onTouchEvent(MotionEvent event) {
1070 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1071 int mode = mViewMode.getMode();
1072 if (mode == ViewMode.CONVERSATION_LIST) {
1073 mConversationListFragment.onTouchEvent(event);
1074 } else if (mode == ViewMode.CONVERSATION) {
1075 mConversationViewFragment.onTouchEvent(event);
1076 }
1077 }
1078 }
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001079
Mindy Pereiraf6acdad2012-03-15 11:21:13 -07001080 private class FetchInboxTask extends AsyncTask<Void, Void, ConversationListContext> {
1081 @Override
1082 public ConversationListContext doInBackground(Void... params) {
1083 // Gets the default inbox since there is no context.
1084 return ConversationListContext.forFolder(mActivity.getActivityContext(), mAccount,
1085 (Folder) null);
1086 }
1087
1088 @Override
1089 public void onPostExecute(ConversationListContext result) {
1090 mConvListContext = result;
1091 setFolder(mConvListContext.folder);
1092 if (mFolderListFragment != null) {
1093 mFolderListFragment.selectFolder(mConvListContext.folder);
1094 }
1095 showConversationList(mConvListContext);
1096 }
1097 }
1098
Paul Westbrookdaef1ce2012-02-23 16:42:00 -08001099 private class FetchAccountFolderTask extends AsyncTask<Void, Void, ConversationListContext> {
1100 @Override
1101 public ConversationListContext doInBackground(Void... params) {
Mindy Pereira161f50d2012-02-28 15:47:19 -08001102 return ConversationListContext.forFolder(mContext, mAccount, mFolder);
Paul Westbrookdaef1ce2012-02-23 16:42:00 -08001103 }
1104
1105 @Override
1106 public void onPostExecute(ConversationListContext result) {
1107 mConvListContext = result;
Mindy Pereira3982e232012-02-29 15:00:34 -08001108 setFolder(mConvListContext.folder);
Mindy Pereira62c5af82012-03-05 10:08:38 -08001109 if (mFolderListFragment != null) {
1110 mFolderListFragment.selectFolder(mConvListContext.folder);
1111 }
Paul Westbrookdaef1ce2012-02-23 16:42:00 -08001112 showConversationList(mConvListContext);
Paul Westbrookdaef1ce2012-02-23 16:42:00 -08001113 mFetchAccountFolderTask = null;
1114 }
1115 }
Mindy Pereirae0828392012-03-08 10:38:40 -08001116
1117 private class FetchSearchFolderTask extends AsyncTask<Void, Void, Folder> {
1118 String mQuery;
1119 public FetchSearchFolderTask(String query) {
1120 mQuery = query;
1121 }
Vikram Aggarwal276c80b2012-03-09 11:07:07 -08001122
1123 @Override
Mindy Pereirae0828392012-03-08 10:38:40 -08001124 public Folder doInBackground(Void... params) {
1125 Folder searchFolder = Folder.forSearchResults(mAccount, mQuery,
1126 mActivity.getActivityContext());
1127 return searchFolder;
1128 }
Vikram Aggarwal276c80b2012-03-09 11:07:07 -08001129
1130 @Override
Mindy Pereirae0828392012-03-08 10:38:40 -08001131 public void onPostExecute(Folder folder) {
1132 setFolder(folder);
1133 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder, mQuery);
1134 showConversationList(mConvListContext);
1135 mActivity.invalidateOptionsMenu();
1136 }
1137 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08001138}