blob: 59e7f262f5c4b8ada697ddc57ac50a91d5bc207b [file] [log] [blame]
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08001/*******************************************************************************
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *******************************************************************************/
17
18package com.android.mail.ui;
19
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080020import android.app.ActionBar;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080021import android.app.ActionBar.LayoutParams;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080022import android.app.Activity;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070023import android.app.AlertDialog;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080024import android.app.Dialog;
Andrew Sapperstein00179f12012-08-09 15:15:40 -070025import android.app.DialogFragment;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -070026import android.app.Fragment;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -070027import android.app.FragmentManager;
Andy Huangf9a73482012-03-13 15:54:02 -070028import android.app.LoaderManager;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070029import android.app.SearchManager;
Andy Huang839ada22012-07-20 15:48:40 -070030import android.content.ContentProviderOperation;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080031import android.content.ContentResolver;
Mindy Pereira6c2663d2012-07-20 15:37:29 -070032import android.content.ContentValues;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080033import android.content.Context;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080034import android.content.CursorLoader;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070035import android.content.DialogInterface;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080036import android.content.Intent;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -080037import android.content.Loader;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080038import android.database.Cursor;
Andy Huang632721e2012-04-11 16:57:26 -070039import android.database.DataSetObservable;
40import android.database.DataSetObserver;
Paul Westbrook23b74b92012-02-29 11:36:12 -080041import android.net.Uri;
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -070042import android.os.AsyncTask;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080043import android.os.Bundle;
Mindy Pereira21ab4902012-03-19 18:48:03 -070044import android.os.Handler;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070045import android.provider.SearchRecentSuggestions;
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -070046import android.text.TextUtils;
Mindy Pereiraacf60392012-04-06 09:11:00 -070047import android.view.DragEvent;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080048import android.view.KeyEvent;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080049import android.view.LayoutInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080050import android.view.Menu;
Mindy Pereira28d5f722012-02-15 12:32:40 -080051import android.view.MenuInflater;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080052import android.view.MenuItem;
53import android.view.MotionEvent;
Mindy Pereirad33674992012-06-25 16:26:30 -070054import android.view.View;
Mindy Pereirafd0c2972012-03-27 13:50:39 -070055import android.widget.Toast;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -080056
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080057import com.android.mail.ConversationListContext;
Andy Huangf9a73482012-03-13 15:54:02 -070058import com.android.mail.R;
Mindy Pereira967ede62012-03-22 09:29:09 -070059import com.android.mail.browse.ConversationCursor;
Paul Westbrookbf232c32012-04-18 03:17:41 -070060import com.android.mail.browse.ConversationPagerController;
Andy Huang839ada22012-07-20 15:48:40 -070061import com.android.mail.browse.MessageCursor.ConversationMessage;
Marc Blank7c9f6ac2012-04-02 13:27:19 -070062import com.android.mail.browse.SelectedConversationsActionMenu;
Andy Huang991f4532012-08-14 13:32:55 -070063import com.android.mail.browse.SyncErrorDialogFragment;
Mindy Pereira9b875682012-02-15 18:10:54 -080064import com.android.mail.compose.ComposeActivity;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080065import com.android.mail.providers.Account;
Mindy Pereira9b875682012-02-15 18:10:54 -080066import com.android.mail.providers.Conversation;
Andy Huang839ada22012-07-20 15:48:40 -070067import com.android.mail.providers.ConversationInfo;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080068import com.android.mail.providers.Folder;
Vikram Aggarwal7a5d95a2012-07-27 16:24:54 -070069import com.android.mail.providers.FolderWatcher;
Paul Westbrookc2074c42012-03-22 15:26:58 -070070import com.android.mail.providers.MailAppProvider;
Mindy Pereiradac00542012-03-01 10:50:33 -080071import com.android.mail.providers.Settings;
Vikram Aggarwale620a7a2012-03-28 13:16:14 -070072import com.android.mail.providers.SuggestionsProvider;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080073import com.android.mail.providers.UIProvider;
Mindy Pereira3cb28b52012-05-24 15:26:39 -070074import com.android.mail.providers.UIProvider.AccountCapabilities;
Paul Westbrook2388c5d2012-03-25 12:29:11 -070075import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
Mindy Pereirac9d59182012-03-22 16:06:46 -070076import com.android.mail.providers.UIProvider.ConversationColumns;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -070077import com.android.mail.providers.UIProvider.FolderCapabilities;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -070078import com.android.mail.ui.ActionableToastBar.ActionClickedListener;
Andy Huang839ada22012-07-20 15:48:40 -070079import com.android.mail.utils.ContentProviderTask;
Paul Westbrookb334c902012-06-25 11:42:46 -070080import com.android.mail.utils.LogTag;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -080081import com.android.mail.utils.LogUtils;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -080082import com.android.mail.utils.Utils;
Paul Westbrookca08fc12012-07-31 12:01:15 -070083import com.google.common.base.Objects;
Paul Westbrook77eee622012-07-10 13:41:57 -070084import com.google.common.collect.ImmutableList;
Mindy Pereiraacf60392012-04-06 09:11:00 -070085import com.google.common.collect.Lists;
Marc Blank167faa82012-03-21 13:11:53 -070086import com.google.common.collect.Sets;
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080087
Marc Blank167faa82012-03-21 13:11:53 -070088import java.util.ArrayList;
Mindy Pereirafbe40192012-03-20 10:40:45 -070089import java.util.Collection;
Andy Huang839ada22012-07-20 15:48:40 -070090import java.util.Collections;
Mindy Pereira8db7e402012-07-13 10:32:47 -070091import java.util.HashMap;
Paul Westbrook23b74b92012-02-29 11:36:12 -080092import java.util.Set;
Marc Blankbf128eb2012-04-18 15:58:45 -070093import java.util.TimerTask;
Paul Westbrook23b74b92012-02-29 11:36:12 -080094
95
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -080096/**
Mindy Pereira161f50d2012-02-28 15:47:19 -080097 * This is an abstract implementation of the Activity Controller. This class
98 * knows how to respond to menu items, state changes, layout changes, etc. It
99 * weaves together the views and listeners, dispatching actions to the
100 * respective underlying classes.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800101 * <p>
Mindy Pereira161f50d2012-02-28 15:47:19 -0800102 * Even though this class is abstract, it should provide default implementations
103 * for most, if not all the methods in the ActivityController interface. This
104 * makes the task of the subclasses easier: OnePaneActivityController and
105 * TwoPaneActivityController can be concise when the common functionality is in
106 * AbstractActivityController.
107 * </p>
108 * <p>
109 * In the Gmail codebase, this was called BaseActivityController
110 * </p>
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800111 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700112public abstract class AbstractActivityController implements ActivityController {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800113 // Keys for serialization of various information in Bundles.
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700114 /** Tag for {@link #mAccount} */
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800115 private static final String SAVED_ACCOUNT = "saved-account";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700116 /** Tag for {@link #mFolder} */
Mindy Pereira5e478d22012-03-26 18:04:58 -0700117 private static final String SAVED_FOLDER = "saved-folder";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700118 /** Tag for {@link #mCurrentConversation} */
Mindy Pereira26f23fc2012-03-27 10:26:04 -0700119 private static final String SAVED_CONVERSATION = "saved-conversation";
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -0700120 /** Tag for {@link #mSelectedSet} */
121 private static final String SAVED_SELECTED_SET = "saved-selected-set";
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -0700122 /** Tag for {@link ActionableToastBar#getOperation()} */
Mindy Pereirad33674992012-06-25 16:26:30 -0700123 private static final String SAVED_TOAST_BAR_OP = "saved-toast-bar-op";
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -0700124 /** Tag for {@link #mFolderListFolder} */
125 private static final String SAVED_HIERARCHICAL_FOLDER = "saved-hierarchical-folder";
Vikram Aggarwalf3341402012-08-07 10:09:38 -0700126 /** Tag for {@link ConversationListContext#searchQuery} */
127 private static final String SAVED_QUERY = "saved-query";
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800128
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700129 /** Tag used when loading a wait fragment */
130 protected static final String TAG_WAIT = "wait-fragment";
131 /** Tag used when loading a conversation list fragment. */
Paul Westbrookbf232c32012-04-18 03:17:41 -0700132 public static final String TAG_CONVERSATION_LIST = "tag-conversation-list";
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700133 /** Tag used when loading a folder list fragment. */
134 protected static final String TAG_FOLDER_LIST = "tag-folder-list";
135
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800136 protected Account mAccount;
Mindy Pereira12a4d802012-06-22 09:24:43 -0700137 protected Folder mFolder;
Andy Huang6681e542012-06-14 14:36:45 -0700138 protected MailActionBarView mActionBarView;
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700139 protected final ControllableActivity mActivity;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800140 protected final Context mContext;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700141 private final FragmentManager mFragmentManager;
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800142 protected final RecentFolderList mRecentFolderList;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800143 protected ConversationListContext mConvListContext;
Mindy Pereira9b875682012-02-15 18:10:54 -0800144 protected Conversation mCurrentConversation;
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800145
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700146 /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
147 private SuppressNotificationReceiver mNewEmailReceiver = null;
148
Mindy Pereirafbe40192012-03-20 10:40:45 -0700149 protected Handler mHandler = new Handler();
Mindy Pereirafa995b42012-07-25 12:06:13 -0700150
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800151 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800152 * The current mode of the application. All changes in mode are initiated by
153 * the activity controller. View mode changes are propagated to classes that
154 * attach themselves as listeners of view mode changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800155 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800156 protected final ViewMode mViewMode;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800157 protected ContentResolver mResolver;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -0800158 protected boolean isLoaderInitialized = false;
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800159 private AsyncRefreshTask mAsyncRefreshTask;
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800160
Andy Huang4e0158f2012-08-07 21:06:01 -0700161 private boolean mDestroyed;
162
Andy Huang1ee96b22012-08-24 20:19:53 -0700163 /**
164 * Are we in a point in the Activity/Fragment lifecycle where it's safe to execute fragment
165 * transactions? (including back stack manipulation)
166 * <p>
167 * Per docs in {@link FragmentManager#beginTransaction()}, this flag starts out true, switches
168 * to false after {@link Activity#onSaveInstanceState}, and becomes true again in both onStart
169 * and onResume.
170 */
171 private boolean mSafeToModifyFragments = true;
172
Paul Westbrook23b74b92012-02-29 11:36:12 -0800173 private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
Mindy Pereira967ede62012-03-22 09:29:09 -0700174 protected ConversationCursor mConversationListCursor;
Andy Huang632721e2012-04-11 16:57:26 -0700175 private final DataSetObservable mConversationListObservable = new DataSetObservable() {
176 @Override
177 public void registerObserver(DataSetObserver observer) {
178 final int count = mObservers.size();
179 super.registerObserver(observer);
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700180 LogUtils.d(LOG_TAG, "IN AAC.register(List)Observer: %s before=%d after=%d", observer,
Andy Huang632721e2012-04-11 16:57:26 -0700181 count, mObservers.size());
182 }
183 @Override
184 public void unregisterObserver(DataSetObserver observer) {
185 final int count = mObservers.size();
186 super.unregisterObserver(observer);
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700187 LogUtils.d(LOG_TAG, "IN AAC.unregister(List)Observer: %s before=%d after=%d", observer,
Andy Huang632721e2012-04-11 16:57:26 -0700188 count, mObservers.size());
189 }
190 };
Marc Blankbf128eb2012-04-18 15:58:45 -0700191
Marc Blankbf128eb2012-04-18 15:58:45 -0700192 private RefreshTimerTask mConversationListRefreshTask;
Marc Blanke1d1b072012-04-13 17:29:16 -0700193
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700194 /** Listeners that are interested in changes to the current account. */
195 private final DataSetObservable mAccountObservers = new DataSetObservable() {
196 @Override
197 public void registerObserver(DataSetObserver observer) {
198 final int count = mObservers.size();
199 super.registerObserver(observer);
200 LogUtils.d(LOG_TAG, "IN AAC.register(Account)Observer: %s before=%d after=%d",
201 observer, count, mObservers.size());
202 }
203 @Override
204 public void unregisterObserver(DataSetObserver observer) {
205 final int count = mObservers.size();
206 super.unregisterObserver(observer);
207 LogUtils.d(LOG_TAG, "IN AAC.unregister(Account)Observer: %s before=%d after=%d",
208 observer, count, mObservers.size());
209 }
210 };
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700211
Mindy Pereira967ede62012-03-22 09:29:09 -0700212 /**
213 * Selected conversations, if any.
214 */
Andy Huang4556a442012-03-30 16:42:05 -0700215 private final ConversationSelectionSet mSelectedSet = new ConversationSelectionSet();
Mindy Pereiradac00542012-03-01 10:50:33 -0800216
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700217 private final int mFolderItemUpdateDelayMs;
218
Vikram Aggarwal135fd022012-04-11 14:44:15 -0700219 /** Keeps track of selected and unselected conversations */
Paul Westbrook937c94f2012-08-16 13:01:18 -0700220 final protected ConversationPositionTracker mTracker;
Vikram Aggarwalaf1ee0c2012-04-12 17:13:13 -0700221
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700222 /**
223 * Action menu associated with the selected set.
224 */
225 SelectedConversationsActionMenu mCabActionMenu;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700226 protected ActionableToastBar mToastBar;
Andy Huang632721e2012-04-11 16:57:26 -0700227 protected ConversationPagerController mPagerController;
Mindy Pereiraab486362012-03-21 18:18:53 -0700228
Andy Huangb1c34dc2012-04-17 16:36:19 -0700229 // this is split out from the general loader dispatcher because its loader doesn't return a
230 // basic Cursor
231 private final ConversationListLoaderCallbacks mListCursorCallbacks =
232 new ConversationListLoaderCallbacks();
233
Andy Huang090db1e2012-07-25 13:25:28 -0700234 private final DataSetObservable mFolderObservable = new DataSetObservable();
235
Paul Westbrookb334c902012-06-25 11:42:46 -0700236 protected static final String LOG_TAG = LogTag.getLogTag();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800237 /** Constants used to differentiate between the types of loaders. */
238 private static final int LOADER_ACCOUNT_CURSOR = 0;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800239 private static final int LOADER_FOLDER_CURSOR = 2;
240 private static final int LOADER_RECENT_FOLDERS = 3;
Mindy Pereira967ede62012-03-22 09:29:09 -0700241 private static final int LOADER_CONVERSATION_LIST = 4;
Mindy Pereiraab486362012-03-21 18:18:53 -0700242 private static final int LOADER_ACCOUNT_INBOX = 5;
243 private static final int LOADER_SEARCH = 6;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -0700244 private static final int LOADER_ACCOUNT_UPDATE_CURSOR = 7;
Vikram Aggarwal7a5d95a2012-07-27 16:24:54 -0700245 /**
246 * Guaranteed to be the last loader ID used by the activity. Loaders are owned by Activity or
247 * fragments, and within an activity, loader IDs need to be unique. A hack to ensure that the
248 * {@link FolderWatcher} can create its folder loaders without clashing with the IDs of those
249 * of the {@link AbstractActivityController}. Currently, the {@link FolderWatcher} is the only
250 * other class that uses this activity's LoaderManager. If another class needs activity-level
251 * loaders, consider consolidating the loaders in a central location: a UI-less fragment
252 * perhaps.
253 */
254 public static final int LAST_LOADER_ID = 100;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800255
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700256 private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
Paul Westbrook122f7c22012-08-20 17:50:31 -0700257 private static final int REAUTHENTICATE_REQUEST_CODE = 2;
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700258
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700259 /** The pending destructive action to be carried out before swapping the conversation cursor.*/
260 private DestructiveAction mPendingDestruction;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700261 protected AsyncRefreshTask mFolderSyncTask;
Mindy Pereira49e5dbe2012-07-12 11:47:54 -0700262 // Task for setting any share intents for the account to enabled.
263 // This gets cancelled if the user kills the app before it finishes, and
264 // will just run the next time the user opens the app.
265 private AsyncTask<String, Void, Void> mEnableShareIntents;
Mindy Pereirac975e842012-07-16 09:15:00 -0700266 private Folder mFolderListFolder;
mindyp5390fca2012-08-22 12:12:25 -0700267 private boolean mIsDragHappening;
mindypead50392012-08-23 11:03:53 -0700268 private int mShowUndoBarDelay;
Andrew Sapperstein00179f12012-08-09 15:15:40 -0700269 public static final String SYNC_ERROR_DIALOG_FRAGMENT_TAG = "SyncErrorDialogFragment";
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700270
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800271 public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
272 mActivity = activity;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700273 mFragmentManager = mActivity.getFragmentManager();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800274 mViewMode = viewMode;
275 mContext = activity.getApplicationContext();
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700276 mRecentFolderList = new RecentFolderList(mContext);
Paul Westbrook937c94f2012-08-16 13:01:18 -0700277 mTracker = new ConversationPositionTracker(this);
Mindy Pereira967ede62012-03-22 09:29:09 -0700278 // Allow the fragment to observe changes to its own selection set. No other object is
279 // aware of the selected set.
280 mSelectedSet.addObserver(this);
Paul Westbrookc7a070f2012-04-12 01:46:41 -0700281
282 mFolderItemUpdateDelayMs =
283 mContext.getResources().getInteger(R.integer.folder_item_refresh_delay_ms);
mindypead50392012-08-23 11:03:53 -0700284 mShowUndoBarDelay =
285 mContext.getResources().getInteger(R.integer.show_undo_bar_delay_ms);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800286 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800287
288 @Override
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800289 public Account getCurrentAccount() {
290 return mAccount;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800291 }
292
293 @Override
294 public ConversationListContext getCurrentListContext() {
Vikram Aggarwale9a81032012-02-22 13:15:35 -0800295 return mConvListContext;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800296 }
297
298 @Override
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800299 public String getHelpContext() {
Paul Westbrook30745b62012-08-19 14:10:32 -0700300 final int mode = mViewMode.getMode();
301 final int helpContextResId;
302 switch (mode) {
303 case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
304 helpContextResId = R.string.wait_help_context;
305 break;
306 default:
307 helpContextResId = R.string.main_help_context;
308 }
309 return mContext.getString(helpContextResId);
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800310 }
311
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800312 @Override
Vikram Aggarwalef7c9922012-04-23 12:35:04 -0700313 public final ConversationCursor getConversationListCursor() {
Mindy Pereira967ede62012-03-22 09:29:09 -0700314 return mConversationListCursor;
315 }
316
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700317 /**
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700318 * Check if the fragment is attached to an activity and has a root view.
319 * @param in
320 * @return true if the fragment is valid, false otherwise
321 */
322 private static final boolean isValidFragment(Fragment in) {
323 if (in == null || in.getActivity() == null || in.getView() == null) {
324 return false;
325 }
326 return true;
327 }
328
329 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700330 * Get the conversation list fragment for this activity. If the conversation list fragment
331 * is not attached, this method returns null
Andy Huang839ada22012-07-20 15:48:40 -0700332 *
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700333 */
334 protected ConversationListFragment getConversationListFragment() {
335 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_CONVERSATION_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700336 if (isValidFragment(fragment)) {
337 return (ConversationListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700338 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700339 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700340 }
341
342 /**
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700343 * Returns the folder list fragment attached with this activity. If no such fragment is attached
344 * this method returns null.
Andy Huang839ada22012-07-20 15:48:40 -0700345 *
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700346 */
347 protected FolderListFragment getFolderListFragment() {
348 final Fragment fragment = mFragmentManager.findFragmentByTag(TAG_FOLDER_LIST);
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700349 if (isValidFragment(fragment)) {
350 return (FolderListFragment) fragment;
Andy Huang9585d782012-04-16 19:45:04 -0700351 }
Vikram Aggarwal34a65012012-04-17 12:39:06 -0700352 return null;
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -0700353 }
354
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800355 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800356 * Initialize the action bar. This is not visible to OnePaneController and
357 * TwoPaneController so they cannot override this behavior.
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800358 */
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700359 private void initializeActionBar() {
360 final ActionBar actionBar = mActivity.getActionBar();
Andy Huang5895f7b2012-06-01 17:07:20 -0700361 if (actionBar == null) {
362 return;
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700363 }
Andy Huang5895f7b2012-06-01 17:07:20 -0700364
365 // be sure to inherit from the ActionBar theme when inflating
366 final LayoutInflater inflater = LayoutInflater.from(actionBar.getThemedContext());
Mindy Pereira82faec72012-06-14 17:21:50 -0700367 final boolean isSearch = mActivity.getIntent() != null
368 && Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction());
369 mActionBarView = (MailActionBarView) inflater.inflate(
370 isSearch ? R.layout.search_actionbar_view : R.layout.actionbar_view, null);
Andy Huang5895f7b2012-06-01 17:07:20 -0700371 mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700372 }
373
374 /**
375 * Attach the action bar to the activity.
376 */
377 private void attachActionBar() {
378 final ActionBar actionBar = mActivity.getActionBar();
379 if (actionBar != null && mActionBarView != null) {
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800380 actionBar.setCustomView(mActionBarView, new ActionBar.LayoutParams(
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800381 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -0700382 // Show a custom view and home icon, but remove the title
383 final int mask = ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE
384 | ActionBar.DISPLAY_SHOW_HOME;
385 final int enabled = ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME;
386 actionBar.setDisplayOptions(enabled, mask);
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700387 mActionBarView.attach();
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800388 }
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700389 mViewMode.addListener(mActionBarView);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800390 }
391
392 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -0800393 * Returns whether the conversation list fragment is visible or not.
394 * Different layouts will have their own notion on the visibility of
395 * fragments, so this method needs to be overriden.
396 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800397 */
398 protected abstract boolean isConversationListVisible();
399
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700400 /**
401 * Switch the current account to the one provided as an argument to the method.
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700402 * @param account new account
403 * @param shouldReloadInbox whether the default inbox should be reloaded.
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700404 */
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700405 private void switchAccount(Account account, boolean shouldReloadInbox){
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700406 // Current account is different from the new account, restart loaders and show
407 // the account Inbox.
408 mAccount = account;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -0700409 LogUtils.d(LOG_TAG, "AbstractActivityController.switchAccount(): mAccount = %s",
410 mAccount.uri);
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700411 cancelRefreshTask();
Vikram Aggarwal60069912012-07-24 14:26:09 -0700412 updateSettings();
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700413 if (shouldReloadInbox) {
414 loadAccountInbox();
415 }
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700416 restartOptionalLoader(LOADER_RECENT_FOLDERS);
417 mActivity.invalidateOptionsMenu();
418 disableNotificationsOnAccountChange(mAccount);
419 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
420 MailAppProvider.getInstance().setLastViewedAccount(mAccount.uri.toString());
421 }
422
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800423 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800424 public void onAccountChanged(Account account) {
Vikram Aggarwal60069912012-07-24 14:26:09 -0700425 // Is the account or account settings different from the existing account?
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700426 final boolean firstLoad = mAccount == null;
Andy Huang3825f3d2012-08-29 16:44:12 -0700427 LogUtils.d(LOG_TAG, "onAccountChanged (%s) called. firstLoad=%s", account, firstLoad);
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700428 final boolean accountChanged = firstLoad || !account.uri.equals(mAccount.uri);
429 final boolean settingsChanged = firstLoad || !account.settings.equals(mAccount.settings);
430 if (accountChanged || settingsChanged) {
Mindy Pereirafa20c1a2012-07-23 13:00:02 -0700431 if (account != null) {
Mindy Pereirafa995b42012-07-25 12:06:13 -0700432 final String accountName = account.name;
433 mHandler.post(new Runnable() {
434 @Override
435 public void run() {
436 MailActivity.setForegroundNdef(MailActivity.getMailtoNdef(accountName));
437 }
438 });
Mindy Pereirafa20c1a2012-07-23 13:00:02 -0700439 }
mindypc6adce32012-08-22 18:46:42 -0700440 if (accountChanged) {
441 commitDestructiveActions(false);
442 }
Vikram Aggarwal66bc6ed2012-07-31 09:59:55 -0700443 switchAccount(account, accountChanged);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800444 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800445 }
446
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700447 /**
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700448 * Changes the settings for the current account. The new settings are provided as a parameter.
449 * @param settings
450 */
Vikram Aggarwal60069912012-07-24 14:26:09 -0700451 public void updateSettings() {
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700452 mAccountObservers.notifyChanged();
Mindy Pereira12a676a2012-03-23 13:00:22 -0700453 resetActionBarIcon();
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700454 mActivity.invalidateOptionsMenu();
455 // If the user was viewing the default Inbox here, and the new setting contains a different
Vikram Aggarwal60069912012-07-24 14:26:09 -0700456 // default Inbox, we don't want to load a different folder here. So do not change the
457 // current folder.
Mindy Pereiradac00542012-03-01 10:50:33 -0800458 }
459
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700460 /**
461 * Adds a listener interested in change in the current account. If a class is storing a
462 * reference to the current account, it should listen on changes, so it can receive updates to
463 * settings. Must happen in the UI thread.
464 */
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800465 @Override
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700466 public void registerAccountObserver(DataSetObserver obs) {
467 mAccountObservers.registerObserver(obs);
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800468 }
469
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700470 /**
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700471 * Removes a listener from receiving current account changes.
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700472 * Must happen in the UI thread.
473 */
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700474 @Override
475 public void unregisterAccountObserver(DataSetObserver obs) {
476 mAccountObservers.unregisterObserver(obs);
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700477 }
478
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700479 @Override
480 public Account getAccount() {
481 return mAccount;
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700482 }
483
Mindy Pereirae0828392012-03-08 10:38:40 -0800484 private void fetchSearchFolder(Intent intent) {
Mindy Pereiraab486362012-03-21 18:18:53 -0700485 Bundle args = new Bundle();
486 args.putString(ConversationListContext.EXTRA_SEARCH_QUERY, intent
Mindy Pereirae0828392012-03-08 10:38:40 -0800487 .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
Mindy Pereiraab486362012-03-21 18:18:53 -0700488 mActivity.getLoaderManager().restartLoader(LOADER_SEARCH, args, this);
Mindy Pereirae0828392012-03-08 10:38:40 -0800489 }
490
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800491 @Override
Mindy Pereira28e0c342012-02-17 15:05:13 -0800492 public void onFolderChanged(Folder folder) {
Vikram Aggarwalf3341402012-08-07 10:09:38 -0700493 changeFolder(folder, null);
494 }
495
496 /**
Vikram Aggarwal203dc002012-08-23 13:59:04 -0700497 * Sets the folder state without changing view mode and without creating a list fragment, if
498 * possible.
499 * @param folder
500 */
501 private void setListContext(Folder folder, String query) {
502 updateFolder(folder);
503 if (query != null) {
504 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder, query);
505 } else {
506 mConvListContext = ConversationListContext.forFolder(mAccount, mFolder);
507 }
Vikram Aggarwal203dc002012-08-23 13:59:04 -0700508 cancelRefreshTask();
509 }
510
511 /**
Vikram Aggarwalf3341402012-08-07 10:09:38 -0700512 * Changes the folder to the value provided here. This causes the view mode to change.
513 * @param folder the folder to change to
514 * @param query if non-null, this represents the search string that the folder represents.
515 */
516 private void changeFolder(Folder folder, String query) {
Vikram Aggarwal203dc002012-08-23 13:59:04 -0700517 if (!Objects.equal(mFolder, folder)) {
518 commitDestructiveActions(false);
519 }
Mindy Pereiraa1f99982012-07-16 16:32:15 -0700520 if (folder != null && !folder.equals(mFolder)
521 || (mViewMode.getMode() != ViewMode.CONVERSATION_LIST)) {
Vikram Aggarwal203dc002012-08-23 13:59:04 -0700522 setListContext(folder, query);
Mindy Pereira28e0c342012-02-17 15:05:13 -0800523 showConversationList(mConvListContext);
Mindy Pereira28e0c342012-02-17 15:05:13 -0800524 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800525 }
526
Mindy Pereira13c12a62012-05-31 15:41:08 -0700527 @Override
Mindy Pereira505df5f2012-06-19 17:57:17 -0700528 public void onFolderSelected(Folder folder) {
Mindy Pereira13c12a62012-05-31 15:41:08 -0700529 onFolderChanged(folder);
530 }
531
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700532 /**
533 * Update the recent folders. This only needs to be done once when accessing a new folder.
534 */
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700535 private void updateRecentFolderList() {
Mindy Pereiraab486362012-03-21 18:18:53 -0700536 if (mFolder != null) {
Marc Blank2675dbc2012-04-03 10:17:13 -0700537 mRecentFolderList.touchFolder(mFolder, mAccount);
Mindy Pereiraab486362012-03-21 18:18:53 -0700538 }
Paul Westbrook7ebdfd02012-03-21 15:55:30 -0700539 }
540
Mindy Pereiraab486362012-03-21 18:18:53 -0700541 // TODO(mindyp): set this up to store a copy of the folder as a transient
542 // field in the account.
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700543 @Override
544 public void loadAccountInbox() {
Vikram Aggarwal94c94de2012-04-04 15:38:28 -0700545 restartOptionalLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereiraf6acdad2012-03-15 11:21:13 -0700546 }
547
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800548 /** Set the current folder */
Mindy Pereira11e35962012-06-01 14:49:46 -0700549 private void updateFolder(Folder folder) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800550 // Start watching folder for sync status.
Mindy Pereirac975e842012-07-16 09:15:00 -0700551 boolean wasNull = mFolder == null;
Paul Westbrook4bfce9a2012-08-07 22:54:42 -0700552 if (folder != null && !folder.equals(mFolder) && folder.isInitialized()) {
Vikram Aggarwaleb7d3292012-04-20 17:07:20 -0700553 LogUtils.d(LOG_TAG, "AbstractActivityController.setFolder(%s)", folder.name);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700554 final LoaderManager lm = mActivity.getLoaderManager();
Vikram Aggarwal7a5d95a2012-07-27 16:24:54 -0700555 mFolder = folder;
Mindy Pereiraf9323cd2012-02-29 13:47:09 -0800556 mActionBarView.setFolder(mFolder);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700557
558 // Only when we switch from one folder to another do we want to restart the
559 // folder and conversation list loaders (to trigger onCreateLoader).
560 // The first time this runs when the activity is [re-]initialized, we want to re-use the
561 // previous loader's instance and data upon configuration change (e.g. rotation).
Mindy Pereira11e35962012-06-01 14:49:46 -0700562 // If there was not already an instance of the loader, init it.
563 if (lm.getLoader(LOADER_FOLDER_CURSOR) == null) {
Andy Huangb1c34dc2012-04-17 16:36:19 -0700564 lm.initLoader(LOADER_FOLDER_CURSOR, null, this);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700565 } else {
566 lm.restartLoader(LOADER_FOLDER_CURSOR, null, this);
Mindy Pereirac975e842012-07-16 09:15:00 -0700567 }
568 // In this case, we are starting from no folder, which would occur
569 // the first time the app was launched or on orientation changes.
570 // We want to attach to an existing loader, if available.
571 if (wasNull || lm.getLoader(LOADER_CONVERSATION_LIST) == null) {
572 lm.initLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
573 } else {
574 // However, if there was an existing folder AND we have changed
575 // folders, we want to restart the loader to get the information
576 // for the newly selected folder
mindypfaa87d52012-08-20 09:35:03 -0700577 lm.destroyLoader(LOADER_CONVERSATION_LIST);
578 lm.initLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
Andy Huangb1c34dc2012-04-17 16:36:19 -0700579 }
Paul Westbrook4bfce9a2012-08-07 22:54:42 -0700580 } else if (!folder.isInitialized()) {
Paul Westbrook3c0f4192012-08-09 12:15:42 -0700581 LogUtils.e(LOG_TAG, new Error(), "Uninitialized Folder %s in setFolder.", folder);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -0800582 }
583 }
584
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800585 @Override
Andy Huang090db1e2012-07-25 13:25:28 -0700586 public Folder getFolder() {
587 return mFolder;
588 }
589
590 @Override
Mindy Pereirac975e842012-07-16 09:15:00 -0700591 public Folder getHierarchyFolder() {
592 return mFolderListFolder;
593 }
594
595 @Override
596 public void setHierarchyFolder(Folder folder) {
597 mFolderListFolder = folder;
Mindy Pereira23aadfd2012-05-25 11:24:33 -0700598 }
599
600 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800601 public void onActivityResult(int requestCode, int resultCode, Intent data) {
Paul Westbrook122f7c22012-08-20 17:50:31 -0700602 switch (requestCode) {
603 case ADD_ACCOUNT_REQUEST_CODE:
604 // We were waiting for the user to create an account
605 if (resultCode == Activity.RESULT_OK) {
606 // restart the loader to get the updated list of accounts
607 mActivity.getLoaderManager().initLoader(
608 LOADER_ACCOUNT_CURSOR, null, this);
609 } else {
610 // The user failed to create an account, just exit the app
611 mActivity.finish();
612 }
613 break;
614 case REAUTHENTICATE_REQUEST_CODE:
615 if (resultCode == Activity.RESULT_OK) {
616 // The user successfully authenticated, attempt to refresh the list
617 final Uri refreshUri = mFolder != null ? mFolder.refreshUri : null;
618 if (refreshUri != null) {
619 startAsyncRefreshTask(refreshUri);
620 }
621 }
622 break;
Paul Westbrook2388c5d2012-03-25 12:29:11 -0700623 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800624 }
625
626 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800627 public void onConversationListVisibilityChanged(boolean visible) {
Paul Westbrook9f119c72012-04-24 16:10:59 -0700628 if (mConversationListCursor != null) {
629 // The conversation list is visible.
630 Utils.setConversationCursorVisibility(mConversationListCursor, visible);
631 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800632 }
633
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800634 /**
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700635 * Called when a conversation is visible. Child classes must call the super class implementation
636 * before performing local computation.
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800637 */
638 @Override
639 public void onConversationVisibilityChanged(boolean visible) {
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -0800640 return;
641 }
642
643 @Override
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800644 public boolean onCreate(Bundle savedState) {
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700645 initializeActionBar();
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800646 // Allow shortcut keys to function for the ActionBar and menus.
647 mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800648 mResolver = mActivity.getContentResolver();
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700649 mNewEmailReceiver = new SuppressNotificationReceiver();
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700650 mRecentFolderList.initialize(mActivity);
Paul Westbrook6ead20d2012-03-19 14:48:14 -0700651
Mindy Pereira161f50d2012-02-28 15:47:19 -0800652 // All the individual UI components listen for ViewMode changes. This
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800653 // simplifies the amount of logic in the AbstractActivityController, but increases the
654 // possibility of timing-related bugs.
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800655 mViewMode.addListener(this);
Andy Huang632721e2012-04-11 16:57:26 -0700656 mPagerController = new ConversationPagerController(mActivity, this);
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700657 mToastBar = (ActionableToastBar) mActivity.findViewById(R.id.toast_bar);
Vikram Aggarwalada51782012-04-26 14:18:31 -0700658 attachActionBar();
Andy Huang632721e2012-04-11 16:57:26 -0700659
660 final Intent intent = mActivity.getIntent();
Vikram Aggarwalabd24d82012-04-26 13:23:14 -0700661 // Immediately handle a clean launch with intent, and any state restoration
Andy Huang632721e2012-04-11 16:57:26 -0700662 // that does not rely on restored fragments or loader data
663 // any state restoration that relies on those can be done later in
664 // onRestoreInstanceState, once fragments are up and loader data is re-delivered
665 if (savedState != null) {
666 if (savedState.containsKey(SAVED_ACCOUNT)) {
667 setAccount((Account) savedState.getParcelable(SAVED_ACCOUNT));
668 mActivity.invalidateOptionsMenu();
669 }
670 if (savedState.containsKey(SAVED_FOLDER)) {
Vikram Aggarwalf3341402012-08-07 10:09:38 -0700671 final Folder folder = (Folder) savedState.getParcelable(SAVED_FOLDER);
Vikram Aggarwal203dc002012-08-23 13:59:04 -0700672 final String query = savedState.getString(SAVED_QUERY, null);
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -0700673 setListContext(folder, query);
Andy Huang632721e2012-04-11 16:57:26 -0700674 }
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -0700675 mViewMode.handleRestore(savedState);
Andy Huang632721e2012-04-11 16:57:26 -0700676 } else if (intent != null) {
677 handleIntent(intent);
678 }
Andy Huang632721e2012-04-11 16:57:26 -0700679 // Create the accounts loader; this loads the account switch spinner.
680 mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
Andy Huang632721e2012-04-11 16:57:26 -0700681 return true;
Andy Huangb1c34dc2012-04-17 16:36:19 -0700682 }
683
684 @Override
Andy Huang1ee96b22012-08-24 20:19:53 -0700685 public void onStart() {
686 mSafeToModifyFragments = true;
687 }
688
689 @Override
Andrew Sapperstein00179f12012-08-09 15:15:40 -0700690 public void onRestart() {
691 DialogFragment fragment = (DialogFragment)
692 mFragmentManager.findFragmentByTag(SYNC_ERROR_DIALOG_FRAGMENT_TAG);
693 if (fragment != null) {
694 fragment.dismiss();
695 }
mindypea04f932012-08-27 14:17:59 -0700696 // When the user places the app in the background by pressing "home",
697 // dismiss the toast bar. However, since there is no way to determine if
698 // home was pressed, just dismiss any existing toast bar when restarting
699 // the app.
700 if (mToastBar != null) {
701 mToastBar.hide(false);
702 }
Andrew Sapperstein00179f12012-08-09 15:15:40 -0700703 }
704
705 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800706 public Dialog onCreateDialog(int id, Bundle bundle) {
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800707 return null;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800708 }
709
710 @Override
Vikram Aggarwal4eb52712012-06-19 16:24:50 -0700711 public final boolean onCreateOptionsMenu(Menu menu) {
Mindy Pereira28d5f722012-02-15 12:32:40 -0800712 MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800713 inflater.inflate(mActionBarView.getOptionsMenuId(), menu);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800714 mActionBarView.onCreateOptionsMenu(menu);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800715 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800716 }
717
718 @Override
Vikram Aggarwal4eb52712012-06-19 16:24:50 -0700719 public final boolean onKeyDown(int keyCode, KeyEvent event) {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800720 // TODO(viki): Auto-generated method stub
721 return false;
722 }
723
724 @Override
Vikram Aggarwal4eb52712012-06-19 16:24:50 -0700725 public final boolean onOptionsItemSelected(MenuItem item) {
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700726 final int id = item.getItemId();
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -0700727 LogUtils.d(LOG_TAG, "AbstractController.onOptionsItemSelected(%d) called.", id);
Mindy Pereira9b875682012-02-15 18:10:54 -0800728 boolean handled = true;
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700729 final Collection<Conversation> target = Conversation.listOf(mCurrentConversation);
Vikram Aggarwal112cd162012-06-18 10:51:13 -0700730 final Settings settings = (mAccount == null) ? null : mAccount.settings;
Mindy Pereira8937bf12012-07-23 14:05:02 -0700731 // The user is choosing a new action; commit whatever they had been doing before.
mindypc6adce32012-08-22 18:46:42 -0700732 commitDestructiveActions(true);
Mindy Pereira28d5f722012-02-15 12:32:40 -0800733 switch (id) {
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700734 case R.id.archive: {
735 final boolean showDialog = (settings != null && settings.confirmArchive);
736 confirmAndDelete(target, showDialog, R.plurals.confirm_archive_conversation,
737 getAction(R.id.archive, target));
738 break;
739 }
Mindy Pereira01f30502012-08-14 10:30:51 -0700740 case R.id.remove_folder:
741 delete(target, getRemoveFolder(target, mFolder, true, false, true));
742 break;
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700743 case R.id.delete: {
744 final boolean showDialog = (settings != null && settings.confirmDelete);
745 confirmAndDelete(target, showDialog, R.plurals.confirm_delete_conversation,
746 getAction(R.id.delete, target));
747 break;
748 }
Paul Westbrookef362542012-08-27 14:53:32 -0700749 case R.id.discard_drafts:
750 final boolean showDialog = (settings != null && settings.confirmDelete);
751 confirmAndDelete(target, showDialog, R.plurals.confirm_discard_drafts_conversation,
752 getAction(R.id.discard_drafts, target));
753 break;
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700754 case R.id.mark_important:
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700755 updateConversation(Conversation.listOf(mCurrentConversation),
756 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.HIGH);
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700757 break;
758 case R.id.mark_not_important:
Mindy Pereira0d03ef82012-08-15 09:05:48 -0700759 if (mFolder != null && mFolder.isImportantOnly()) {
760 delete(target, getAction(R.id.mark_not_important, target));
761 } else {
762 updateConversation(Conversation.listOf(mCurrentConversation),
763 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
764 }
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700765 break;
766 case R.id.mute:
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700767 delete(target, getAction(R.id.mute, target));
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700768 break;
769 case R.id.report_spam:
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700770 delete(target, getAction(R.id.report_spam, target));
Mindy Pereiraba68fda2012-05-24 15:53:06 -0700771 break;
Paul Westbrook77eee622012-07-10 13:41:57 -0700772 case R.id.mark_not_spam:
773 // Currently, since spam messages are only shown in list with other spam messages,
774 // marking a message not as spam is a destructive action
775 delete(target, getAction(R.id.mark_not_spam, target));
776 break;
Paul Westbrook76b20622012-07-12 11:45:43 -0700777 case R.id.report_phishing:
778 delete(target, getAction(R.id.report_phishing, target));
779 break;
Mindy Pereiraf5acda42012-02-15 20:13:59 -0800780 case android.R.id.home:
781 onUpPressed();
782 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800783 case R.id.compose:
784 ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
785 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800786 case R.id.show_all_folders:
787 showFolderList();
788 break;
Mindy Pereira28e0c342012-02-17 15:05:13 -0800789 case R.id.refresh:
790 requestFolderRefresh();
791 break;
Mindy Pereira1f936682012-03-02 11:30:33 -0800792 case R.id.settings:
793 Utils.showSettings(mActivity.getActivityContext(), mAccount);
Paul Westbrook2861b6a2012-02-15 15:25:34 -0800794 break;
Paul Westbrooke5503552012-03-28 00:35:57 -0700795 case R.id.folder_options:
796 Utils.showFolderSettings(mActivity.getActivityContext(), mAccount, mFolder);
797 break;
Paul Westbrook94e440d2012-02-24 11:03:47 -0800798 case R.id.help_info_menu_item:
Paul Westbrook30745b62012-08-19 14:10:32 -0700799 Utils.showHelp(mActivity.getActivityContext(), mAccount, getHelpContext());
Paul Westbrook94e440d2012-02-24 11:03:47 -0800800 break;
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700801 case R.id.feedback_menu_item:
Paul Westbrook17beb0b2012-08-20 13:34:37 -0700802 Utils.sendFeedback(mActivity.getActivityContext(), mAccount, false);
Mindy Pereirafbe40192012-03-20 10:40:45 -0700803 break;
Paul Westbrook18babd22012-04-09 22:17:08 -0700804 case R.id.manage_folders_item:
805 Utils.showManageFolder(mActivity.getActivityContext(), mAccount);
806 break;
Vikram Aggarwald503df42012-05-11 10:13:35 -0700807 case R.id.change_folder:
808 new FoldersSelectionDialog(mActivity.getActivityContext(), mAccount, this,
Mindy Pereiraa832d3d2012-07-27 11:19:50 -0700809 Conversation.listOf(mCurrentConversation), false, mFolder).show();
Vikram Aggarwald503df42012-05-11 10:13:35 -0700810 break;
Mindy Pereira9b875682012-02-15 18:10:54 -0800811 default:
812 handled = false;
813 break;
Mindy Pereira28d5f722012-02-15 12:32:40 -0800814 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800815 return handled;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -0800816 }
817
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700818 @Override
Mindy Pereira6c2663d2012-07-20 15:37:29 -0700819 public void updateConversation(Collection<Conversation> target, ContentValues values) {
820 mConversationListCursor.updateValues(mContext, target, values);
821 refreshConversationList();
822 }
823
824 @Override
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700825 public void updateConversation(Collection <Conversation> target, String columnName,
826 boolean value) {
827 mConversationListCursor.updateBoolean(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700828 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700829 }
830
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700831 @Override
Mindy Pereira6c2663d2012-07-20 15:37:29 -0700832 public void updateConversation(Collection <Conversation> target, String columnName,
833 int value) {
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700834 mConversationListCursor.updateInt(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700835 refreshConversationList();
Mindy Pereirac9d59182012-03-22 16:06:46 -0700836 }
837
Vikram Aggarwal531488e2012-05-29 16:36:52 -0700838 @Override
839 public void updateConversation(Collection <Conversation> target, String columnName,
840 String value) {
841 mConversationListCursor.updateString(mContext, target, columnName, value);
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700842 refreshConversationList();
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700843 }
844
Andy Huang839ada22012-07-20 15:48:40 -0700845 @Override
846 public void markConversationMessagesUnread(Conversation conv, Set<Uri> unreadMessageUris,
Vikram Aggarwal4a878b62012-07-31 15:09:25 -0700847 String originalConversationInfo) {
Andy Huang8f6b0062012-07-31 15:36:31 -0700848 // The only caller of this method is the conversation view, from where marking unread should
849 // *always* take you back to list mode.
850 showConversation(null);
851
Andy Huang839ada22012-07-20 15:48:40 -0700852 // locally mark conversation unread (the provider is supposed to propagate message unread
853 // to conversation unread)
854 conv.read = false;
855
Andy Huang28e31e22012-07-26 16:33:15 -0700856 // only do a granular 'mark unread' if a subset of messages are unread
857 final int unreadCount = (unreadMessageUris == null) ? 0 : unreadMessageUris.size();
Mindy Pereira0972e072012-08-01 17:43:06 -0700858 final int numMessages = conv.getNumMessages();
859 final boolean subsetIsUnread = (numMessages > 1 && unreadCount > 0
860 && unreadCount < numMessages);
Andy Huang28e31e22012-07-26 16:33:15 -0700861
862 if (!subsetIsUnread) {
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700863 // Conversations are neither marked read, nor viewed, and we don't want to show
864 // the next conversation.
865 markConversationsRead(Collections.singletonList(conv), false, false, false);
Andy Huang839ada22012-07-20 15:48:40 -0700866 } else {
Andy Huangdaa06ab2012-07-24 10:46:44 -0700867 mConversationListCursor.setConversationColumn(conv.uri, ConversationColumns.READ, 0);
Andy Huang839ada22012-07-20 15:48:40 -0700868
Mindy Pereira7b6d03d2012-07-30 13:03:41 -0700869 // locally update conversation's conversationInfo to revert to original version
Andy Huang28e31e22012-07-26 16:33:15 -0700870 if (originalConversationInfo != null) {
871 mConversationListCursor.setConversationColumn(conv.uri,
872 ConversationColumns.CONVERSATION_INFO, originalConversationInfo);
873 }
Andy Huang839ada22012-07-20 15:48:40 -0700874
875 // applyBatch with each CPO as an UPDATE op on each affected message uri
876 final ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
877 String authority = null;
878 for (Uri messageUri : unreadMessageUris) {
879 if (authority == null) {
880 authority = messageUri.getAuthority();
881 }
882 ops.add(ContentProviderOperation.newUpdate(messageUri)
883 .withValue(UIProvider.MessageColumns.READ, 0)
884 .build());
885 }
886
887 new ContentProviderTask() {
888 @Override
889 protected void onPostExecute(Result result) {
890 // TODO: handle errors?
891 }
892 }.run(mResolver, authority, ops);
Andy Huang839ada22012-07-20 15:48:40 -0700893 }
Andy Huang839ada22012-07-20 15:48:40 -0700894 }
895
896 @Override
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700897 public void markConversationsRead(Collection<Conversation> targets, boolean read,
898 boolean viewed) {
899 // We want to show the next conversation if we are marking unread.
900 markConversationsRead(targets, read, viewed, true);
Andy Huang8f6b0062012-07-31 15:36:31 -0700901 }
902
903 private void markConversationsRead(Collection<Conversation> targets, boolean read,
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700904 boolean markViewed, boolean showNext) {
Andy Huang8f6b0062012-07-31 15:36:31 -0700905 // auto-advance if requested and the current conversation is being marked unread
906 if (showNext && !read) {
907 showNextConversation(targets);
908 }
909
Andy Huang839ada22012-07-20 15:48:40 -0700910 for (Conversation target : targets) {
Vikram Aggarwal192fac12012-07-25 16:44:55 -0700911 final ContentValues values = new ContentValues();
Andy Huang839ada22012-07-20 15:48:40 -0700912 values.put(ConversationColumns.READ, read);
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700913 if (markViewed) {
914 values.put(ConversationColumns.VIEWED, true);
915 }
Vikram Aggarwal192fac12012-07-25 16:44:55 -0700916 final ConversationInfo info = target.conversationInfo;
Andy Huang839ada22012-07-20 15:48:40 -0700917 if (info != null) {
Mindy Pereira5f424372012-07-30 11:49:55 -0700918 info.markRead(read);
919 values.put(ConversationColumns.CONVERSATION_INFO, ConversationInfo.toString(info));
Andy Huang839ada22012-07-20 15:48:40 -0700920 }
921 updateConversation(Conversation.listOf(target), values);
922 }
923 // Update the conversations in the selection too.
924 for (final Conversation c : targets) {
925 c.read = read;
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700926 if (markViewed) {
927 c.markViewed();
928 }
Andy Huang839ada22012-07-20 15:48:40 -0700929 }
930 }
931
Andy Huang8f6b0062012-07-31 15:36:31 -0700932 /**
933 * Auto-advance to a different conversation if the currently visible conversation in
934 * conversation mode is affected (deleted, marked unread, etc.).
935 *
936 * <p>Does nothing if outside of conversation mode.
937 *
938 * @param target the set of conversations being deleted/marked unread
939 */
940 private void showNextConversation(Collection<Conversation> target) {
941 final boolean currentConversationInView = (mViewMode.getMode() == ViewMode.CONVERSATION)
942 && Conversation.contains(target, mCurrentConversation);
943 if (currentConversationInView) {
944 final Conversation next = mTracker.getNextConversation(
945 Settings.getAutoAdvanceSetting(mAccount.settings), target);
946 LogUtils.d(LOG_TAG, "showNextConversation: showing %s next.", next);
947 showConversation(next);
948 }
949 }
950
Andy Huang839ada22012-07-20 15:48:40 -0700951 @Override
952 public void starMessage(ConversationMessage msg, boolean starred) {
953 if (msg.starred == starred) {
954 return;
955 }
956
957 msg.starred = starred;
958
959 // locally propagate the change to the owning conversation
960 // (figure the provider will properly propagate the change when it commits it)
961 //
962 // when unstarring, only propagate the change if this was the only message starred
963 final boolean conversationStarred = starred || msg.isConversationStarred();
964 if (conversationStarred != msg.conversation.starred) {
965 msg.conversation.starred = conversationStarred;
Andy Huangdaa06ab2012-07-24 10:46:44 -0700966 mConversationListCursor.setConversationColumn(msg.conversation.uri,
Andy Huang839ada22012-07-20 15:48:40 -0700967 ConversationColumns.STARRED, conversationStarred);
968 }
969
970 final ContentValues values = new ContentValues(1);
971 values.put(UIProvider.MessageColumns.STARRED, starred ? 1 : 0);
972
973 new ContentProviderTask.UpdateTask() {
974 @Override
975 protected void onPostExecute(Result result) {
976 // TODO: handle errors?
977 }
978 }.run(mResolver, msg.uri, values, null /* selection*/, null /* selectionArgs */);
979 }
980
Mindy Pereira28e0c342012-02-17 15:05:13 -0800981 private void requestFolderRefresh() {
982 if (mFolder != null) {
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800983 if (mAsyncRefreshTask != null) {
984 mAsyncRefreshTask.cancel(true);
985 }
Paul Westbrook7e2a2a12012-06-27 13:52:40 -0700986 mAsyncRefreshTask = new AsyncRefreshTask(mContext, mFolder.refreshUri);
Mindy Pereirab7b33e02012-02-21 15:32:19 -0800987 mAsyncRefreshTask.execute();
Mindy Pereira28e0c342012-02-17 15:05:13 -0800988 }
989 }
990
Mindy Pereirafbe40192012-03-20 10:40:45 -0700991 /**
992 * Confirm (based on user's settings) and delete a conversation from the conversation list and
993 * from the database.
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700994 * @param target the conversations to act upon
995 * @param showDialog true if a confirmation dialog is to be shown, false otherwise.
996 * @param confirmResource the resource ID of the string that is shown in the confirmation dialog
997 * @param action the action to perform after animating the deletion of the conversations.
Mindy Pereirafbe40192012-03-20 10:40:45 -0700998 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700999 protected void confirmAndDelete(final Collection<Conversation> target, boolean showDialog,
1000 int confirmResource, final DestructiveAction action) {
Mindy Pereirafbe40192012-03-20 10:40:45 -07001001 if (showDialog) {
1002 final AlertDialog.OnClickListener onClick = new AlertDialog.OnClickListener() {
1003 @Override
1004 public void onClick(DialogInterface dialog, int which) {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001005 delete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001006 }
1007 };
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001008 final CharSequence message = Utils.formatPlural(mContext, confirmResource,
1009 target.size());
Mindy Pereirafbe40192012-03-20 10:40:45 -07001010 new AlertDialog.Builder(mActivity.getActivityContext()).setMessage(message)
1011 .setPositiveButton(R.string.ok, onClick)
1012 .setNegativeButton(R.string.cancel, null)
1013 .create().show();
1014 } else {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001015 delete(target, action);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001016 }
1017 }
1018
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001019 @Override
1020 public void delete(final Collection<Conversation> target, final DestructiveAction action) {
Vikram Aggarwal192fac12012-07-25 16:44:55 -07001021 // Order of events is critical! The Conversation View Fragment must be notified
1022 // of the next conversation with showConversation(next) *before* the conversation list
1023 // fragment has a chance to delete the conversation, animating it away.
1024
Vikram Aggarwald503df42012-05-11 10:13:35 -07001025 // Update the conversation fragment if the current conversation is deleted.
Andy Huang8f6b0062012-07-31 15:36:31 -07001026 showNextConversation(target);
Vikram Aggarwal192fac12012-07-25 16:44:55 -07001027 // The conversation list deletes and performs the action if it exists.
1028 final ConversationListFragment convListFragment = getConversationListFragment();
1029 if (convListFragment != null) {
1030 LogUtils.d(LOG_TAG, "AAC.requestDelete: ListFragment is handling delete.");
1031 convListFragment.requestDelete(target, action);
1032 return;
1033 }
Vikram Aggarwald503df42012-05-11 10:13:35 -07001034 // No visible UI element handled it on our behalf. Perform the action ourself.
1035 action.performAction();
1036 }
1037
1038 /**
1039 * Requests that the action be performed and the UI state is updated to reflect the new change.
1040 * @param target
1041 * @param action
1042 */
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07001043 private void requestUpdate(final Collection<Conversation> target,
Vikram Aggarwald503df42012-05-11 10:13:35 -07001044 final DestructiveAction action) {
1045 action.performAction();
1046 refreshConversationList();
Vikram Aggarwal75daee52012-04-30 11:13:09 -07001047 }
Vikram Aggarwal6fbc87a2012-03-15 15:24:00 -07001048
1049 @Override
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001050 public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
1051 // TODO(viki): Auto-generated method stub
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001052 }
1053
1054 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001055 public boolean onPrepareOptionsMenu(Menu menu) {
Mindy Pereirab849dfb2012-03-07 18:13:15 -08001056 mActionBarView.onPrepareOptionsMenu(menu);
Mindy Pereira363451a2012-02-22 14:14:46 -08001057 return true;
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001058 }
1059
Mindy Pereira68f2e222012-03-07 10:36:54 -08001060 @Override
1061 public void onPause() {
1062 isLoaderInitialized = false;
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001063 enableNotifications();
Paul Westbrook94e440d2012-02-24 11:03:47 -08001064 }
1065
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001066 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001067 public void onResume() {
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001068 // Register the receiver that will prevent the status receiver from
1069 // displaying its notification icon as long as we're running.
1070 // The SupressNotificationReceiver will block the broadcast if we're looking at the folder
1071 // that the notification was received for.
1072 disableNotifications();
Andy Huang1ee96b22012-08-24 20:19:53 -07001073
1074 mSafeToModifyFragments = true;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001075 }
1076
1077 @Override
1078 public void onSaveInstanceState(Bundle outState) {
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001079 mViewMode.handleSaveInstanceState(outState);
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001080 if (mAccount != null) {
1081 LogUtils.d(LOG_TAG, "Saving the account now");
1082 outState.putParcelable(SAVED_ACCOUNT, mAccount);
1083 }
Mindy Pereira5e478d22012-03-26 18:04:58 -07001084 if (mFolder != null) {
1085 outState.putParcelable(SAVED_FOLDER, mFolder);
Vikram Aggarwal8b152632012-02-03 14:58:45 -08001086 }
Vikram Aggarwalf3341402012-08-07 10:09:38 -07001087 // If this is a search activity, let's store the search query term as well.
1088 if (ConversationListContext.isSearchResult(mConvListContext)) {
1089 outState.putString(SAVED_QUERY, mConvListContext.searchQuery);
1090 }
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001091 final int mode = mViewMode.getMode();
Mindy Pereira9fa43ca2012-05-17 14:18:01 -07001092 if (mCurrentConversation != null
Mindy Pereira0f7ae7a2012-07-17 13:39:56 -07001093 && (mode == ViewMode.CONVERSATION ||
Mindy Pereira9fa43ca2012-05-17 14:18:01 -07001094 mViewMode.getMode() == ViewMode.SEARCH_RESULTS_CONVERSATION)) {
Mindy Pereira26f23fc2012-03-27 10:26:04 -07001095 outState.putParcelable(SAVED_CONVERSATION, mCurrentConversation);
1096 }
Andy Huang4556a442012-03-30 16:42:05 -07001097 if (!mSelectedSet.isEmpty()) {
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001098 outState.putParcelable(SAVED_SELECTED_SET, mSelectedSet);
Andy Huang4556a442012-03-30 16:42:05 -07001099 }
Mindy Pereirad33674992012-06-25 16:26:30 -07001100 if (mToastBar.getVisibility() == View.VISIBLE) {
1101 outState.putParcelable(SAVED_TOAST_BAR_OP, mToastBar.getOperation());
1102 }
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001103 final ConversationListFragment convListFragment = getConversationListFragment();
Mindy Pereirad33674992012-06-25 16:26:30 -07001104 if (convListFragment != null) {
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001105 convListFragment.getAnimatedAdapter().onSaveInstanceState(outState);
Mindy Pereirad33674992012-06-25 16:26:30 -07001106 }
Andy Huang1ee96b22012-08-24 20:19:53 -07001107 mSafeToModifyFragments = false;
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001108 outState.putString(SAVED_HIERARCHICAL_FOLDER,
1109 (mFolderListFolder != null) ? Folder.toString(mFolderListFolder) : null);
Andy Huang1ee96b22012-08-24 20:19:53 -07001110 }
1111
1112 /**
1113 * @see #mSafeToModifyFragments
1114 */
1115 protected boolean safeToModifyFragments() {
1116 return mSafeToModifyFragments;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001117 }
1118
1119 @Override
Mindy Pereira68f2e222012-03-07 10:36:54 -08001120 public void onSearchRequested(String query) {
1121 Intent intent = new Intent();
1122 intent.setAction(Intent.ACTION_SEARCH);
1123 intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query);
1124 intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount);
1125 intent.setComponent(mActivity.getComponentName());
Vikram Aggarwalb17cbc02012-04-06 15:41:46 -07001126 mActionBarView.collapseSearch();
Mindy Pereira68f2e222012-03-07 10:36:54 -08001127 mActivity.startActivity(intent);
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001128 }
1129
1130 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001131 public void onStop() {
Mindy Pereira49e5dbe2012-07-12 11:47:54 -07001132 if (mEnableShareIntents != null) {
1133 mEnableShareIntents.cancel(true);
1134 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001135 }
1136
Andy Huang632721e2012-04-11 16:57:26 -07001137 @Override
1138 public void onDestroy() {
1139 // unregister the ViewPager's observer on the conversation cursor
1140 mPagerController.onDestroy();
Mindy Pereira641de652012-08-02 15:21:50 -07001141 mActionBarView.onDestroy();
Vikram Aggarwal7c401b72012-08-13 16:43:47 -07001142 mRecentFolderList.destroy();
Andy Huang4e0158f2012-08-07 21:06:01 -07001143 mDestroyed = true;
Andy Huang632721e2012-04-11 16:57:26 -07001144 }
1145
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001146 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -08001147 * {@inheritDoc} Subclasses must override this to listen to mode changes
1148 * from the ViewMode. Subclasses <b>must</b> call the parent's
1149 * onViewModeChanged since the parent will handle common state changes.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001150 */
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001151 @Override
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001152 public void onViewModeChanged(int newMode) {
1153 // Perform any mode specific work here.
Mindy Pereira161f50d2012-02-28 15:47:19 -08001154 // reset the action bar icon based on the mode. Why don't the individual
1155 // controllers do
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001156 // this themselves?
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001157
Mindy Pereira8937bf12012-07-23 14:05:02 -07001158 // Commit any destructive undoable actions the user may have performed.
mindypc6adce32012-08-22 18:46:42 -07001159 commitDestructiveActions(true);
Mindy Pereira8937bf12012-07-23 14:05:02 -07001160
Mindy Pereira161f50d2012-02-28 15:47:19 -08001161 // We don't want to invalidate the options menu when switching to
1162 // conversation
Vikram Aggarwalfa131a22012-02-02 13:56:22 -08001163 // mode, as it will happen when the conversation finishes loading.
1164 if (newMode != ViewMode.CONVERSATION) {
1165 mActivity.invalidateOptionsMenu();
1166 }
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001167 }
1168
Andy Huang3825f3d2012-08-29 16:44:12 -07001169 public void disablePagerUpdates() {
1170 mPagerController.stopListening();
1171 }
1172
Andy Huang4e0158f2012-08-07 21:06:01 -07001173 public boolean isDestroyed() {
1174 return mDestroyed;
1175 }
1176
mindyp54f120f2012-08-28 13:10:33 -07001177 @Override
1178 public void commitDestructiveActions(boolean animate) {
mindypc6adce32012-08-22 18:46:42 -07001179 ConversationListFragment fragment = getConversationListFragment();
Mindy Pereira1e2573b2012-04-17 14:34:36 -07001180 if (fragment != null) {
mindypc6adce32012-08-22 18:46:42 -07001181 fragment.commitDestructiveActions(animate);
Mindy Pereira1e2573b2012-04-17 14:34:36 -07001182 }
1183 }
1184
Vikram Aggarwala55b36c2012-01-13 11:45:02 -08001185 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001186 public void onWindowFocusChanged(boolean hasFocus) {
Paul Westbrook9f119c72012-04-24 16:10:59 -07001187 ConversationListFragment convList = getConversationListFragment();
1188 if (hasFocus && convList != null && convList.isVisible()) {
1189 // The conversation list is visible.
1190 Utils.setConversationCursorVisibility(mConversationListCursor, true);
1191 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001192 }
1193
Vikram Aggarwalca716f12012-08-20 11:11:48 -07001194 /**
1195 * Set the account, and carry out all the account-related changes that rely on this.
1196 * @param account
1197 */
1198 // TODO(viki): Two different methods do the same thing. Resolve
1199 // {@link #setAccount(Account)} and {@link #switchAccount(Account, boolean)}
Mindy Pereira75181e82012-04-18 08:17:13 -07001200 private void setAccount(Account account) {
Andy Huangb9ca9792012-05-18 15:31:49 -07001201 if (account == null) {
1202 LogUtils.w(LOG_TAG, new Error(),
1203 "AAC ignoring null (presumably invalid) account restoration");
1204 return;
1205 }
Andy Huangb1148412012-05-19 00:16:30 -07001206 LogUtils.d(LOG_TAG, "AbstractActivityController.setAccount(): account = %s", account.uri);
Vikram Aggarwal91e87372012-05-18 15:36:04 -07001207 mAccount = account;
Vikram Aggarwalca716f12012-08-20 11:11:48 -07001208 // Only change AAC state here. Do *not* modify any other object's state. The object
1209 // should listen on account changes.
1210 restartOptionalLoader(LOADER_RECENT_FOLDERS);
1211 mActivity.invalidateOptionsMenu();
1212 disableNotificationsOnAccountChange(mAccount);
1213 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1214 MailAppProvider.getInstance().setLastViewedAccount(mAccount.uri.toString());
1215
Vikram Aggarwal91e87372012-05-18 15:36:04 -07001216 if (account.settings == null) {
1217 LogUtils.w(LOG_TAG, new Error(), "AAC ignoring account with null settings.");
1218 return;
1219 }
Vikram Aggarwal7c401b72012-08-13 16:43:47 -07001220 mAccountObservers.notifyChanged();
Mindy Pereira75181e82012-04-18 08:17:13 -07001221 }
1222
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001223 /**
Mindy Pereira161f50d2012-02-28 15:47:19 -08001224 * Restore the state from the previous bundle. Subclasses should call this
1225 * method from the parent class, since it performs important UI
1226 * initialization.
1227 *
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001228 * @param savedState
1229 */
Andy Huang632721e2012-04-11 16:57:26 -07001230 @Override
1231 public void onRestoreInstanceState(Bundle savedState) {
1232 LogUtils.d(LOG_TAG, "IN AAC.onRestoreInstanceState");
1233 if (savedState.containsKey(SAVED_CONVERSATION)) {
1234 // Open the conversation.
Paul Westbrook534e4a22012-04-25 03:46:29 -07001235 final Conversation conversation =
1236 (Conversation)savedState.getParcelable(SAVED_CONVERSATION);
1237 if (conversation != null && conversation.position < 0) {
1238 // Set the position to 0 on this conversation, as we don't know where it is
1239 // in the list
1240 conversation.position = 0;
1241 }
Andy Huanged4fdf02012-07-26 17:12:50 -07001242 showConversation(conversation);
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001243 }
Mindy Pereira967ede62012-03-22 09:29:09 -07001244
Mindy Pereirad33674992012-06-25 16:26:30 -07001245 if (savedState.containsKey(SAVED_TOAST_BAR_OP)) {
1246 ToastBarOperation op = ((ToastBarOperation) savedState
1247 .getParcelable(SAVED_TOAST_BAR_OP));
1248 if (op != null) {
1249 if (op.getType() == ToastBarOperation.UNDO) {
1250 onUndoAvailable(op);
Andrew Sapperstein9d7519d2012-07-16 14:03:53 -07001251 } else if (op.getType() == ToastBarOperation.ERROR) {
1252 onError(mFolder, true);
Mindy Pereirad33674992012-06-25 16:26:30 -07001253 }
1254 }
1255 }
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001256 final String folderString = savedState.getString(SAVED_HIERARCHICAL_FOLDER, null);
1257 if (!TextUtils.isEmpty(folderString)) {
1258 mFolderListFolder = Folder.fromString(folderString);
1259 }
1260 final ConversationListFragment convListFragment = getConversationListFragment();
Mindy Pereirad33674992012-06-25 16:26:30 -07001261 if (convListFragment != null) {
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001262 convListFragment.getAnimatedAdapter().onRestoreInstanceState(savedState);
Mindy Pereirad33674992012-06-25 16:26:30 -07001263 }
Mindy Pereira967ede62012-03-22 09:29:09 -07001264 /**
1265 * Restore the state of selected conversations. This needs to be done after the correct mode
1266 * is set and the action bar is fully initialized. If not, several key pieces of state
1267 * information will be missing, and the split views may not be initialized correctly.
1268 * @param savedState
1269 */
Andy Huang4556a442012-03-30 16:42:05 -07001270 restoreSelectedConversations(savedState);
Andy Huang632721e2012-04-11 16:57:26 -07001271 }
1272
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001273 /**
1274 * Handle an intent to open the app. This method is called only when there is no saved state,
1275 * so we need to set state that wasn't set before. It is correct to change the viewmode here
1276 * since it has not been previously set.
1277 * @param intent
1278 */
Andy Huang632721e2012-04-11 16:57:26 -07001279 private void handleIntent(Intent intent) {
1280 boolean handled = false;
1281 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
1282 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001283 setAccount(Account.newinstance(intent.getStringExtra(Utils.EXTRA_ACCOUNT)));
Andy Huang632721e2012-04-11 16:57:26 -07001284 }
Andy Huangb9ca9792012-05-18 15:31:49 -07001285 if (mAccount == null) {
1286 return;
Andy Huang632721e2012-04-11 16:57:26 -07001287 }
Andy Huangb9ca9792012-05-18 15:31:49 -07001288 mActivity.invalidateOptionsMenu();
Vikram Aggarwal1a249e02012-08-03 16:19:33 -07001289 final boolean isConversationMode = intent.hasExtra(Utils.EXTRA_CONVERSATION);
Vikram Aggarwal649b9ea2012-08-27 12:15:20 -07001290 if (isConversationMode && mViewMode.getMode() == ViewMode.UNKNOWN) {
Vikram Aggarwal1a249e02012-08-03 16:19:33 -07001291 mViewMode.enterConversationMode();
1292 } else {
1293 mViewMode.enterConversationListMode();
1294 }
Andy Huang632721e2012-04-11 16:57:26 -07001295
1296 Folder folder = null;
1297 if (intent.hasExtra(Utils.EXTRA_FOLDER)) {
1298 // Open the folder.
Mindy Pereira7b6d03d2012-07-30 13:03:41 -07001299 folder = Folder.fromString(intent.getStringExtra(Utils.EXTRA_FOLDER));
Andy Huang632721e2012-04-11 16:57:26 -07001300 }
1301 if (folder != null) {
1302 onFolderChanged(folder);
1303 handled = true;
1304 }
1305
Vikram Aggarwal1a249e02012-08-03 16:19:33 -07001306 if (isConversationMode) {
Andy Huang632721e2012-04-11 16:57:26 -07001307 // Open the conversation.
1308 LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
1309 intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
Paul Westbrooka9161912012-04-24 10:10:04 -07001310 final Conversation conversation =
1311 (Conversation)intent.getParcelableExtra(Utils.EXTRA_CONVERSATION);
1312 if (conversation != null && conversation.position < 0) {
1313 // Set the position to 0 on this conversation, as we don't know where it is
1314 // in the list
1315 conversation.position = 0;
1316 }
Andy Huang980aaea2012-07-26 17:22:19 -07001317 showConversation(conversation);
Andy Huang632721e2012-04-11 16:57:26 -07001318 handled = true;
1319 }
1320
1321 if (!handled) {
1322 // Nothing was saved; just load the account inbox.
1323 loadAccountInbox();
1324 }
1325 } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
1326 if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
1327 // Save this search query for future suggestions.
1328 final String query = intent.getStringExtra(SearchManager.QUERY);
1329 final String authority = mContext.getString(R.string.suggestions_authority);
1330 SearchRecentSuggestions suggestions = new SearchRecentSuggestions(
1331 mContext, authority, SuggestionsProvider.MODE);
1332 suggestions.saveRecentQuery(query, null);
Mindy Pereiraac254822012-06-18 10:46:43 -07001333 if (Utils.showTwoPaneSearchResults(mActivity.getActivityContext())) {
1334 mViewMode.enterSearchResultsConversationMode();
1335 } else {
1336 mViewMode.enterSearchResultsListMode();
1337 }
Andy Huang632721e2012-04-11 16:57:26 -07001338 setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
1339 mActivity.invalidateOptionsMenu();
1340 restartOptionalLoader(LOADER_RECENT_FOLDERS);
Andy Huang632721e2012-04-11 16:57:26 -07001341 fetchSearchFolder(intent);
1342 } else {
1343 LogUtils.e(LOG_TAG, "Missing account extra from search intent. Finishing");
1344 mActivity.finish();
1345 }
1346 }
1347 if (mAccount != null) {
1348 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1349 }
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001350 }
1351
Andy Huang4556a442012-03-30 16:42:05 -07001352 /**
1353 * Copy any selected conversations stored in the saved bundle into our selection set,
1354 * triggering {@link ConversationSetObserver} callbacks as our selection set changes.
1355 *
1356 */
Vikram Aggarwal4eb52712012-06-19 16:24:50 -07001357 private final void restoreSelectedConversations(Bundle savedState) {
Mindy Pereira967ede62012-03-22 09:29:09 -07001358 if (savedState == null) {
Andy Huang4556a442012-03-30 16:42:05 -07001359 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001360 return;
1361 }
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001362 final ConversationSelectionSet selectedSet = savedState.getParcelable(SAVED_SELECTED_SET);
Andy Huang4556a442012-03-30 16:42:05 -07001363 if (selectedSet == null || selectedSet.isEmpty()) {
1364 mSelectedSet.clear();
Mindy Pereira967ede62012-03-22 09:29:09 -07001365 return;
1366 }
Andy Huang632721e2012-04-11 16:57:26 -07001367
1368 // putAll will take care of calling our registered onSetPopulated method
Vikram Aggarwalcabd3f22012-04-19 10:14:41 -07001369 mSelectedSet.putAll(selectedSet);
Mindy Pereira967ede62012-03-22 09:29:09 -07001370 }
1371
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001372 @Override
Andy Huang5895f7b2012-06-01 17:07:20 -07001373 public SubjectDisplayChanger getSubjectDisplayChanger() {
1374 return mActionBarView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001375 }
1376
Andy Huang1ee96b22012-08-24 20:19:53 -07001377 private void showConversation(Conversation conversation) {
1378 showConversation(conversation, false /* inLoaderCallbacks */);
1379 }
1380
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001381 /**
1382 * Children can override this method, but they must call super.showConversation().
Andy Huang1ee96b22012-08-24 20:19:53 -07001383 *
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001384 */
Andy Huang1ee96b22012-08-24 20:19:53 -07001385 protected void showConversation(Conversation conversation, boolean inLoaderCallbacks) {
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001386 // Set the current conversation just in case it wasn't already set.
1387 setCurrentConversation(conversation);
Vikram Aggarwal0f142732012-08-24 09:39:34 -07001388 // Add the folder that we were viewing to the recent folders list.
1389 // TODO: this may need to be fine tuned. If this is the signal that is indicating that
1390 // the list is shown to the user, this could fire in one pane if the user goes directly
1391 // to a conversation
1392 updateRecentFolderList();
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -08001393 }
1394
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001395 /**
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001396 * Children can override this method, but they must call super.showWaitForInitialization().
1397 * {@inheritDoc}
1398 */
1399 @Override
1400 public void showWaitForInitialization() {
1401 mViewMode.enterWaitingForInitializationMode();
1402 }
1403
1404 @Override
1405 public void hideWaitForInitialization() {
1406 }
1407
1408 @Override
1409 public void updateWaitMode() {
1410 final FragmentManager manager = mActivity.getFragmentManager();
1411 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001412 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001413 if (waitFragment != null) {
1414 waitFragment.updateAccount(mAccount);
1415 }
1416 }
1417
Vikram Aggarwal48b2a6c2012-05-29 14:09:27 -07001418 /**
1419 * Returns true if we are waiting for the account to sync, and cannot show any folders or
1420 * conversation for the current account yet.
Andy Huang839ada22012-07-20 15:48:40 -07001421 *
Vikram Aggarwal48b2a6c2012-05-29 14:09:27 -07001422 */
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001423 public boolean inWaitMode() {
1424 final FragmentManager manager = mActivity.getFragmentManager();
1425 final WaitFragment waitFragment =
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07001426 (WaitFragment)manager.findFragmentByTag(TAG_WAIT);
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001427 if (waitFragment != null) {
1428 final Account fragmentAccount = waitFragment.getAccount();
1429 return fragmentAccount.uri.equals(mAccount.uri) &&
1430 mViewMode.getMode() == ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION;
1431 }
1432 return false;
1433 }
1434
1435 /**
Vikram Aggarwale128fc22012-04-04 12:33:34 -07001436 * Children can override this method, but they must call super.showConversationList().
1437 * {@inheritDoc}
1438 */
1439 @Override
1440 public void showConversationList(ConversationListContext listContext) {
1441 }
1442
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -08001443 @Override
Andy Huang1ee96b22012-08-24 20:19:53 -07001444 public void onConversationSelected(Conversation conversation, boolean inLoaderCallbacks) {
mindypaa55bc92012-08-24 09:49:56 -07001445 // Only animate destructive actions if we are going to be showing the
1446 // conversation list when we show the next conversation.
1447 commitDestructiveActions(Utils.useTabletUI(mContext));
Andy Huang1ee96b22012-08-24 20:19:53 -07001448 showConversation(conversation, inLoaderCallbacks);
1449 }
1450
1451 @Override
1452 public Conversation getCurrentConversation() {
1453 return mCurrentConversation;
Vikram Aggarwal7d602882012-02-07 15:01:20 -08001454 }
Mindy Pereira555140c2012-02-15 14:55:29 -08001455
Vikram Aggarwalc67182d2012-04-03 14:35:06 -07001456 /**
1457 * Set the current conversation. This is the conversation on which all actions are performed.
1458 * Do not modify mCurrentConversation except through this method, which makes it easy to
1459 * perform common actions associated with changing the current conversation.
1460 * @param conversation
1461 */
Andy Huang632721e2012-04-11 16:57:26 -07001462 @Override
1463 public void setCurrentConversation(Conversation conversation) {
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001464 mCurrentConversation = conversation;
Vikram Aggarwal135fd022012-04-11 14:44:15 -07001465 mTracker.initialize(mCurrentConversation);
Mindy Pereira5040f1a2012-03-20 10:14:06 -07001466 }
1467
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001468 /**
1469 * {@inheritDoc}
1470 */
Mindy Pereira555140c2012-02-15 14:55:29 -08001471 @Override
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001472 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
1473 // Create a loader to listen in on account changes.
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001474 switch (id) {
1475 case LOADER_ACCOUNT_CURSOR:
Paul Westbrookc2074c42012-03-22 15:26:58 -07001476 return new CursorLoader(mContext, MailAppProvider.getAccountsUri(),
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001477 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
1478 case LOADER_FOLDER_CURSOR:
Paul Westbrookc7a070f2012-04-12 01:46:41 -07001479 final CursorLoader loader = new CursorLoader(mContext, mFolder.uri,
1480 UIProvider.FOLDERS_PROJECTION, null, null, null);
1481 loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
1482 return loader;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001483 case LOADER_RECENT_FOLDERS:
Paul Westbrook91d10502012-04-13 12:01:39 -07001484 if (mAccount != null && mAccount.recentFolderListUri != null) {
Paul Westbrookea4ee202012-03-12 14:12:33 -07001485 return new CursorLoader(mContext, mAccount.recentFolderListUri,
1486 UIProvider.FOLDERS_PROJECTION, null, null, null);
1487 }
1488 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001489 case LOADER_ACCOUNT_INBOX:
Vikram Aggarwal025eba82012-05-08 10:45:30 -07001490 final Uri defaultInbox = Settings.getDefaultInboxUri(mAccount.settings);
Vikram Aggarwal1e57e672012-05-07 14:48:24 -07001491 final Uri inboxUri = defaultInbox.equals(Uri.EMPTY) ?
1492 mAccount.folderListUri : defaultInbox;
Paul Westbrook7496e822012-04-24 09:50:54 -07001493 LogUtils.d(LOG_TAG, "Loading the default inbox: %s", inboxUri);
Paul Westbrook1220b6d2012-04-10 00:48:00 -07001494 if (inboxUri != null) {
1495 return new CursorLoader(mContext, inboxUri, UIProvider.FOLDERS_PROJECTION, null,
1496 null, null);
1497 }
1498 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001499 case LOADER_SEARCH:
1500 return Folder.forSearchResults(mAccount,
1501 args.getString(ConversationListContext.EXTRA_SEARCH_QUERY),
1502 mActivity.getActivityContext());
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001503 case LOADER_ACCOUNT_UPDATE_CURSOR:
1504 return new CursorLoader(mContext, mAccount.uri, UIProvider.ACCOUNTS_PROJECTION,
1505 null, null, null);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001506 default:
Paul Westbrookad6a2752012-04-04 16:58:13 -07001507 LogUtils.wtf(LOG_TAG, "Loader returned unexpected id: %d", id);
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001508 }
1509 return null;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001510 }
1511
Paul Westbrookb1f573c2012-04-06 11:38:28 -07001512 @Override
1513 public void onLoaderReset(Loader<Cursor> loader) {
1514
1515 }
1516
Andy Huangf9a73482012-03-13 15:54:02 -07001517 /**
1518 * {@link LoaderManager} currently has a bug in
1519 * {@link LoaderManager#restartLoader(int, Bundle, android.app.LoaderManager.LoaderCallbacks)}
1520 * where, if a previous onCreateLoader returned a null loader, this method will NPE. Work around
1521 * this bug by destroying any loaders that may have been created as null (essentially because
1522 * they are optional loads, and may not apply to a particular account).
1523 * <p>
1524 * A simple null check before restarting a loader will not work, because that would not
1525 * give the controller a chance to invalidate UI corresponding the prior loader result.
1526 *
1527 * @param id loader ID to safely restart
Andy Huangf9a73482012-03-13 15:54:02 -07001528 */
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001529 private void restartOptionalLoader(int id) {
Andy Huangf9a73482012-03-13 15:54:02 -07001530 final LoaderManager lm = mActivity.getLoaderManager();
1531 lm.destroyLoader(id);
Vikram Aggarwal94c94de2012-04-04 15:38:28 -07001532 lm.restartLoader(id, Bundle.EMPTY, this);
1533 }
1534
Andy Huang632721e2012-04-11 16:57:26 -07001535 @Override
1536 public void registerConversationListObserver(DataSetObserver observer) {
1537 mConversationListObservable.registerObserver(observer);
1538 }
1539
1540 @Override
1541 public void unregisterConversationListObserver(DataSetObserver observer) {
1542 mConversationListObservable.unregisterObserver(observer);
1543 }
1544
Andy Huang090db1e2012-07-25 13:25:28 -07001545 @Override
1546 public void registerFolderObserver(DataSetObserver observer) {
1547 mFolderObservable.registerObserver(observer);
1548 }
1549
1550 @Override
1551 public void unregisterFolderObserver(DataSetObserver observer) {
1552 mFolderObservable.unregisterObserver(observer);
1553 }
1554
Vikram Aggarwal60069912012-07-24 14:26:09 -07001555 /**
1556 * Returns true if the number of accounts is different, or if the current account has been
1557 * removed from the device
1558 * @param accountCursor
1559 * @return
1560 */
Paul Westbrook23b74b92012-02-29 11:36:12 -08001561 private boolean accountsUpdated(Cursor accountCursor) {
1562 // Check to see if the current account hasn't been set, or the account cursor is empty
1563 if (mAccount == null || !accountCursor.moveToFirst()) {
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001564 return true;
Paul Westbrook23b74b92012-02-29 11:36:12 -08001565 }
1566
1567 // Check to see if the number of accounts are different, from the number we saw on the last
1568 // updated
1569 if (mCurrentAccountUris.size() != accountCursor.getCount()) {
1570 return true;
1571 }
1572
1573 // Check to see if the account list is different or if the current account is not found in
1574 // the cursor.
1575 boolean foundCurrentAccount = false;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001576 do {
Paul Westbrook23b74b92012-02-29 11:36:12 -08001577 final Uri accountUri =
1578 Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
1579 if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
1580 foundCurrentAccount = true;
1581 }
Vikram Aggarwal60069912012-07-24 14:26:09 -07001582 // Is there a new account that we do not know about?
Paul Westbrook23b74b92012-02-29 11:36:12 -08001583 if (!mCurrentAccountUris.contains(accountUri)) {
1584 return true;
1585 }
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001586 } while (accountCursor.moveToNext());
Paul Westbrook23b74b92012-02-29 11:36:12 -08001587
1588 // As long as we found the current account, the list hasn't been updated
1589 return !foundCurrentAccount;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001590 }
1591
1592 /**
Vikram Aggarwal60069912012-07-24 14:26:09 -07001593 * Updates accounts for the app. If the current account is missing, the first
1594 * account in the list is set to the current account (we <em>have</em> to choose something).
Mindy Pereira161f50d2012-02-28 15:47:19 -08001595 *
Vikram Aggarwal6c511582012-02-27 10:59:47 -08001596 * @param accounts cursor into the AccountCache
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001597 * @return true if the update was successful, false otherwise
1598 */
Vikram Aggarwal60069912012-07-24 14:26:09 -07001599 private boolean updateAccounts(Cursor accounts) {
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001600 if (accounts == null || !accounts.moveToFirst()) {
1601 return false;
1602 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001603
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001604 final Account[] allAccounts = Account.getAllAccounts(accounts);
Vikram Aggarwal60069912012-07-24 14:26:09 -07001605 // A match for the current account's URI in the list of accounts.
1606 Account currentFromList = null;
Paul Westbrook23b74b92012-02-29 11:36:12 -08001607
1608 // Save the uris for the accounts
1609 mCurrentAccountUris.clear();
1610 for (Account account : allAccounts) {
Vikram Aggarwal60069912012-07-24 14:26:09 -07001611 LogUtils.d(LOG_TAG, "updateAccounts(%s)", account);
Paul Westbrook23b74b92012-02-29 11:36:12 -08001612 mCurrentAccountUris.add(account.uri);
Vikram Aggarwal60069912012-07-24 14:26:09 -07001613 if (mAccount != null && account.uri.equals(mAccount.uri)) {
1614 currentFromList = account;
1615 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001616 }
1617
Vikram Aggarwal60069912012-07-24 14:26:09 -07001618 // 1. current account is already set and is in allAccounts:
1619 // 1a. It has changed -> load the updated account.
1620 // 2b. It is unchanged -> no-op
Andy Huang0d647352012-03-21 21:48:16 -07001621 // 2. current account is set and is not in allAccounts -> pick first (acct was deleted?)
Vikram Aggarwal60069912012-07-24 14:26:09 -07001622 // 3. saved preference has an account -> pick that one
Andy Huang0d647352012-03-21 21:48:16 -07001623 // 4. otherwise just pick first
1624
Vikram Aggarwal60069912012-07-24 14:26:09 -07001625 boolean accountChanged = false;
1626 /// Assume case 4, initialize to first account, and see if we can find anything better.
1627 Account newAccount = allAccounts[0];
1628 if (currentFromList != null) {
1629 // Case 1: Current account exists but has changed
1630 if (!currentFromList.equals(mAccount)) {
1631 newAccount = currentFromList;
1632 accountChanged = true;
Andy Huang0d647352012-03-21 21:48:16 -07001633 }
Vikram Aggarwal60069912012-07-24 14:26:09 -07001634 // Case 1b: else, current account is unchanged: nothing to do.
Paul Westbrook23b74b92012-02-29 11:36:12 -08001635 } else {
Vikram Aggarwal60069912012-07-24 14:26:09 -07001636 // Case 2: Current account is not in allAccounts, the account needs to change.
1637 accountChanged = true;
1638 if (mAccount == null) {
1639 // Case 3: Check for last viewed account, and check if it exists in the list.
1640 final String lastAccountUri = MailAppProvider.getInstance().getLastViewedAccount();
1641 if (lastAccountUri != null) {
1642 for (final Account account : allAccounts) {
1643 if (lastAccountUri.equals(account.uri.toString())) {
1644 newAccount = account;
1645 break;
1646 }
Andy Huang0d647352012-03-21 21:48:16 -07001647 }
1648 }
1649 }
Paul Westbrook23b74b92012-02-29 11:36:12 -08001650 }
Vikram Aggarwal60069912012-07-24 14:26:09 -07001651 if (accountChanged) {
1652 onAccountChanged(newAccount);
1653 }
1654 // Whether we have updated the current account or not, we need to update the list of
1655 // accounts in the ActionBar.
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001656 mActionBarView.setAccounts(allAccounts);
1657 return (allAccounts.length > 0);
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001658 }
1659
Paul Westbrook6ead20d2012-03-19 14:48:14 -07001660 private void disableNotifications() {
1661 mNewEmailReceiver.activate(mContext, this);
1662 }
1663
1664 private void enableNotifications() {
1665 mNewEmailReceiver.deactivate();
1666 }
1667
1668 private void disableNotificationsOnAccountChange(Account account) {
1669 // If the new mail suppression receiver is activated for a different account, we want to
1670 // activate it for the new account.
1671 if (mNewEmailReceiver.activated() &&
1672 !mNewEmailReceiver.notificationsDisabledForAccount(account)) {
1673 // Deactivate the current receiver, otherwise multiple receivers may be registered.
1674 mNewEmailReceiver.deactivate();
1675 mNewEmailReceiver.activate(mContext, this);
1676 }
1677 }
1678
Vikram Aggarwala9b93f32012-02-23 14:51:58 -08001679 /**
1680 * {@inheritDoc}
1681 */
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001682 @Override
1683 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereira5cb0c3e2012-02-22 15:22:47 -08001684 // We want to reinitialize only if we haven't ever been initialized, or
1685 // if the current account has vanished.
Paul Westbrooke3e84292012-03-05 16:19:30 -08001686 if (data == null) {
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001687 LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
Paul Westbrooke3e84292012-03-05 16:19:30 -08001688 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001689 switch (loader.getId()) {
1690 case LOADER_ACCOUNT_CURSOR:
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -07001691 // If the account list is not null, and the account list cursor is empty,
Paul Westbrook2388c5d2012-03-25 12:29:11 -07001692 // we need to start the specified activity.
1693 if (data != null && data.getCount() == 0) {
1694 // If an empty cursor is returned, the MailAppProvider is indicating that
1695 // no accounts have been specified. We want to navigate to the "add account"
1696 // activity that will handle the intent returned by the MailAppProvider
1697
1698 // If the MailAppProvider believes that all accounts have been loaded, and the
1699 // account list is still empty, we want to prompt the user to add an account
1700 final Bundle extras = data.getExtras();
1701 final boolean accountsLoaded =
1702 extras.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
1703
1704 if (accountsLoaded) {
1705 final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(mContext);
1706 if (noAccountIntent != null) {
1707 mActivity.startActivityForResult(noAccountIntent,
1708 ADD_ACCOUNT_REQUEST_CODE);
1709 }
1710 }
1711 } else {
1712 final boolean accountListUpdated = accountsUpdated(data);
1713 if (!isLoaderInitialized || accountListUpdated) {
Vikram Aggarwal60069912012-07-24 14:26:09 -07001714 isLoaderInitialized = updateAccounts(data);
Paul Westbrook2388c5d2012-03-25 12:29:11 -07001715 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001716 }
1717 break;
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001718 case LOADER_ACCOUNT_UPDATE_CURSOR:
1719 // We have gotten an update for current account.
1720
Vikram Aggarwal60069912012-07-24 14:26:09 -07001721 // Make sure that this is an update for the current account
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001722 if (data != null && data.moveToFirst()) {
1723 final Account updatedAccount = new Account(data);
1724
1725 if (updatedAccount.uri.equals(mAccount.uri)) {
Paul Westbrookca08fc12012-07-31 12:01:15 -07001726 // Keep a reference to the previous settings object
1727 final Settings previousSettings = mAccount.settings;
1728
Vikram Aggarwal7d816002012-04-17 17:06:41 -07001729 // Update the controller's reference to the current account
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001730 mAccount = updatedAccount;
Vikram Aggarwaledc137c2012-04-24 13:40:58 -07001731 LogUtils.d(LOG_TAG, "AbstractActivityController.onLoadFinished(): "
1732 + "mAccount = %s", mAccount.uri);
Paul Westbrookca08fc12012-07-31 12:01:15 -07001733
1734 // Only notify about a settings change if something differs
1735 if (!Objects.equal(mAccount.settings, previousSettings)) {
Vikram Aggarwal7c401b72012-08-13 16:43:47 -07001736 mAccountObservers.notifyChanged();
Paul Westbrookca08fc12012-07-31 12:01:15 -07001737 }
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001738
1739 // Got an update for the current account
1740 final boolean inWaitingMode = inWaitMode();
1741 if (!updatedAccount.isAccountIntialized() && !inWaitingMode) {
1742 // Transition to waiting mode
1743 showWaitForInitialization();
Mindy Pereira830bdaf2012-07-11 12:59:55 -07001744 } else if (updatedAccount.isAccountIntialized()) {
1745 if (inWaitingMode) {
1746 // Dismiss waiting mode
1747 hideWaitForInitialization();
1748 }
Paul Westbrook2d50bcd2012-04-10 11:53:47 -07001749 } else if (!updatedAccount.isAccountIntialized() && inWaitingMode) {
1750 // Update the WaitFragment's account object
1751 updateWaitMode();
1752 }
1753 } else {
1754 LogUtils.e(LOG_TAG, "Got update for account: %s with current account: %s",
1755 updatedAccount.uri, mAccount.uri);
1756 // We need to restart the loader, so the correct account information will
1757 // be returned
1758 restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
1759 }
1760 }
1761 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001762 case LOADER_FOLDER_CURSOR:
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001763 // Check status of the cursor.
Marc Blankfd9d0b82012-04-23 16:01:51 -07001764 if (data != null && data.moveToFirst()) {
Andy Huang090db1e2012-07-25 13:25:28 -07001765 final Folder folder = new Folder(data);
Marc Blankfd9d0b82012-04-23 16:01:51 -07001766 LogUtils.d(LOG_TAG, "FOLDER STATUS = %d", folder.syncStatus);
Andy Huang090db1e2012-07-25 13:25:28 -07001767
1768 mFolder = folder;
1769 mFolderObservable.notifyChanged();
1770
Paul Westbrookc808fac2012-02-22 16:42:18 -08001771 } else {
Marc Blankfd9d0b82012-04-23 16:01:51 -07001772 LogUtils.d(LOG_TAG, "Unable to get the folder %s",
1773 mFolder != null ? mAccount.name : "");
Mindy Pereira11dd5ef2012-03-10 15:10:18 -08001774 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001775 break;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001776 case LOADER_RECENT_FOLDERS:
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -07001777 // No recent folders and we are running on a phone? Populate the default recents.
1778 if (data != null && data.getCount() == 0 && !Utils.useTabletUI(mContext)) {
1779 final class PopulateDefault extends AsyncTask<Uri, Void, Void> {
1780 @Override
1781 protected Void doInBackground(Uri... uri) {
1782 // Asking for an update on the URI and ignore the result.
1783 final ContentResolver resolver = mContext.getContentResolver();
1784 resolver.update(uri[0], null, null, null);
1785 return null;
1786 }
1787 }
1788 final Uri uri = mAccount.defaultRecentFolderListUri;
1789 LogUtils.v(LOG_TAG, "Default recents at %s", uri);
1790 new PopulateDefault().execute(uri);
1791 break;
1792 }
1793 LogUtils.v(LOG_TAG, "Reading recent folders from the cursor.");
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001794 mRecentFolderList.loadFromUiProvider(data);
Vikram Aggarwal37263972012-04-17 15:51:14 -07001795 mActionBarView.requestRecentFoldersAndRedraw();
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -08001796 break;
Mindy Pereiraab486362012-03-21 18:18:53 -07001797 case LOADER_ACCOUNT_INBOX:
Marc Blankfd9d0b82012-04-23 16:01:51 -07001798 if (data != null && !data.isClosed() && data.moveToFirst()) {
Mindy Pereira0e88e9f2012-03-25 13:47:41 -07001799 Folder inbox = new Folder(data);
1800 onFolderChanged(inbox);
Mindy Pereirab4a43282012-03-23 16:20:03 -07001801 // Just want to get the inbox, don't care about updates to it
1802 // as this will be tracked by the folder change listener.
1803 mActivity.getLoaderManager().destroyLoader(LOADER_ACCOUNT_INBOX);
Mindy Pereira5ba33802012-03-26 16:30:11 -07001804 } else {
1805 LogUtils.d(LOG_TAG, "Unable to get the account inbox for account %s",
1806 mAccount != null ? mAccount.name : "");
Mindy Pereirab4a43282012-03-23 16:20:03 -07001807 }
Mindy Pereiraab486362012-03-21 18:18:53 -07001808 break;
1809 case LOADER_SEARCH:
Paul Westbrookc4845c52012-08-29 21:48:43 -07001810 if (data != null && data.getCount() > 0) {
1811 data.moveToFirst();
1812 Folder search = new Folder(data);
1813 updateFolder(search);
1814 mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder,
1815 mActivity.getIntent()
1816 .getStringExtra(UIProvider.SearchQueryParameters.QUERY));
1817 showConversationList(mConvListContext);
1818 mActivity.invalidateOptionsMenu();
1819 mActivity.getLoaderManager().destroyLoader(LOADER_SEARCH);
1820 } else {
1821 LogUtils.e(LOG_TAG, "Null or empty cursor returned by LOADER_SEARCH loader");
1822 }
Mindy Pereiraab486362012-03-21 18:18:53 -07001823 break;
Vikram Aggarwal7dedb952012-02-16 16:10:23 -08001824 }
1825 }
1826
Vikram Aggarwalc7694222012-04-23 13:37:01 -07001827 /**
1828 * Destructive actions on Conversations. This class should only be created by controllers, and
1829 * clients should only require {@link DestructiveAction}s, not specific implementations of the.
1830 * Only the controllers should know what kind of destructive actions are being created.
1831 */
Mindy Pereirade3e74a2012-07-24 09:43:10 -07001832 public class ConversationAction implements DestructiveAction {
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001833 /**
1834 * The action to be performed. This is specified as the resource ID of the menu item
1835 * corresponding to this action: R.id.delete, R.id.report_spam, etc.
1836 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001837 private final int mAction;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001838 /** The action will act upon these conversations */
Paul Westbrook77eee622012-07-10 13:41:57 -07001839 private final Collection<Conversation> mTarget;
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001840 /** Whether this destructive action has already been performed */
1841 private boolean mCompleted;
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001842 /** Whether this is an action on the currently selected set. */
1843 private final boolean mIsSelectedSet;
Vikram Aggarwale8a85322012-04-24 09:01:38 -07001844
Mindy Pereirafbe40192012-03-20 10:40:45 -07001845 /**
1846 * Create a listener object. action is one of four constants: R.id.y_button (archive),
1847 * R.id.delete , R.id.mute, and R.id.report_spam.
1848 * @param action
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001849 * @param target Conversation that we want to apply the action to.
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001850 * @param isBatch whether the conversations are in the currently selected batch set.
1851 */
1852 public ConversationAction(int action, Collection<Conversation> target, boolean isBatch) {
Mindy Pereirafbe40192012-03-20 10:40:45 -07001853 mAction = action;
Paul Westbrook77eee622012-07-10 13:41:57 -07001854 mTarget = ImmutableList.copyOf(target);
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001855 mIsSelectedSet = isBatch;
Mindy Pereirafbe40192012-03-20 10:40:45 -07001856 }
1857
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001858 /**
1859 * The action common to child classes. This performs the action specified in the constructor
1860 * on the conversations given here.
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07001861 */
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001862 @Override
1863 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001864 if (isPerformed()) {
1865 return;
1866 }
Mindy Pereira3cb28b52012-05-24 15:26:39 -07001867 boolean undoEnabled = mAccount.supportsCapability(AccountCapabilities.UNDO);
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001868
1869 // Are we destroying the currently shown conversation? Show the next one.
1870 if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)){
Vikram Aggarwal8742a612012-08-13 10:22:50 -07001871 LogUtils.d(LOG_TAG, "ConversationAction.performAction():"
1872 + "\nmTarget=%s\nCurrent=%s",
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001873 Conversation.toString(mTarget), mCurrentConversation);
1874 }
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001875
Paul Westbrooke1221d22012-08-19 11:09:07 -07001876 if (mConversationListCursor == null) {
1877 LogUtils.e(LOG_TAG, "null ConversationCursor in ConversationAction.performAction():"
1878 + "\nmTarget=%s\nCurrent=%s",
1879 Conversation.toString(mTarget), mCurrentConversation);
1880 return;
1881 }
1882
Mindy Pereirafbe40192012-03-20 10:40:45 -07001883 switch (mAction) {
Mindy Pereira0692baf2012-03-23 17:34:31 -07001884 case R.id.archive:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001885 LogUtils.d(LOG_TAG, "Archiving");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001886 mConversationListCursor.archive(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001887 break;
1888 case R.id.delete:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001889 LogUtils.d(LOG_TAG, "Deleting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001890 mConversationListCursor.delete(mContext, mTarget);
Mindy Pereira695d6962012-06-18 13:02:10 -07001891 if (mFolder.supportsCapability(FolderCapabilities.DELETE_ACTION_FINAL)) {
Marc Blank386243f2012-05-25 10:40:59 -07001892 undoEnabled = false;
1893 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001894 break;
1895 case R.id.mute:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001896 LogUtils.d(LOG_TAG, "Muting");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001897 if (mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE)) {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001898 for (Conversation c : mTarget) {
1899 c.localDeleteOnUpdate = true;
1900 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001901 }
1902 mConversationListCursor.mute(mContext, mTarget);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001903 break;
1904 case R.id.report_spam:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001905 LogUtils.d(LOG_TAG, "Reporting spam");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001906 mConversationListCursor.reportSpam(mContext, mTarget);
1907 break;
Paul Westbrook77eee622012-07-10 13:41:57 -07001908 case R.id.mark_not_spam:
1909 LogUtils.d(LOG_TAG, "Marking not spam");
1910 mConversationListCursor.reportNotSpam(mContext, mTarget);
1911 break;
Paul Westbrook76b20622012-07-12 11:45:43 -07001912 case R.id.report_phishing:
1913 LogUtils.d(LOG_TAG, "Reporting phishing");
1914 mConversationListCursor.reportPhishing(mContext, mTarget);
1915 break;
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001916 case R.id.remove_star:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001917 LogUtils.d(LOG_TAG, "Removing star");
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001918 // Star removal is destructive in the Starred folder.
1919 mConversationListCursor.updateBoolean(mContext, mTarget,
1920 ConversationColumns.STARRED, false);
1921 break;
1922 case R.id.mark_not_important:
Vikram Aggarwal7dd054e2012-05-21 14:43:10 -07001923 LogUtils.d(LOG_TAG, "Marking not-important");
Mindy Pereira445be212012-08-15 08:50:10 -07001924 // Marking not important is destructive in a mailbox
1925 // containing only important messages
Mindy Pereira0d03ef82012-08-15 09:05:48 -07001926 if (mFolder != null && mFolder.isImportantOnly()) {
Mindy Pereira445be212012-08-15 08:50:10 -07001927 for (Conversation conv : mTarget) {
1928 conv.localDeleteOnUpdate = true;
1929 }
1930 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001931 mConversationListCursor.updateInt(mContext, mTarget,
1932 ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001933 break;
Paul Westbrookef362542012-08-27 14:53:32 -07001934 case R.id.discard_drafts:
1935 LogUtils.d(LOG_TAG, "Discarding draft messages");
1936 // Discarding draft messages is destructive in a "draft" mailbox
1937 if (mFolder != null && mFolder.isDraft()) {
1938 for (Conversation conv : mTarget) {
1939 conv.localDeleteOnUpdate = true;
1940 }
1941 }
1942 mConversationListCursor.discardDrafts(mContext, mTarget);
1943 // We don't support undoing discarding drafts
1944 undoEnabled = false;
1945 break;
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001946 }
1947 if (undoEnabled) {
mindypead50392012-08-23 11:03:53 -07001948 mHandler.postDelayed(new Runnable() {
1949 @Override
1950 public void run() {
1951 onUndoAvailable(new ToastBarOperation(mTarget.size(), mAction,
1952 ToastBarOperation.UNDO));
1953 }
1954 }, mShowUndoBarDelay);
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001955 }
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07001956 refreshConversationList();
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001957 if (mIsSelectedSet) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07001958 mSelectedSet.clear();
1959 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001960 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001961
1962 /**
1963 * Returns true if this action has been performed, false otherwise.
Andy Huang839ada22012-07-20 15:48:40 -07001964 *
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07001965 */
1966 private synchronized boolean isPerformed() {
1967 if (mCompleted) {
1968 return true;
1969 }
1970 mCompleted = true;
1971 return false;
1972 }
Mindy Pereirafbe40192012-03-20 10:40:45 -07001973 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001974
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001975 /**
1976 * Get a destructive action for a menu action.
1977 * This is a temporary method, to control the profusion of {@link DestructiveAction} classes
1978 * that are created. Please do not copy this paradigm.
1979 * @param action the resource ID of the menu action: R.id.delete, for example
1980 * @param target the conversations to act upon.
1981 * @return a {@link DestructiveAction} that performs the specified action.
1982 */
Vikram Aggarwal531488e2012-05-29 16:36:52 -07001983 private final DestructiveAction getAction(int action, Collection<Conversation> target) {
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07001984 final DestructiveAction da = new ConversationAction(action, target, false);
1985 registerDestructiveAction(da);
1986 return da;
1987 }
1988
Vikram Aggarwald503df42012-05-11 10:13:35 -07001989 // Called from the FolderSelectionDialog after a user is done selecting folders to assign the
1990 // conversations to.
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07001991 @Override
Mindy Pereira8db7e402012-07-13 10:32:47 -07001992 public final void assignFolder(Collection<FolderOperation> folderOps,
1993 Collection<Conversation> target, boolean batch, boolean showUndo) {
1994 // Actions are destructive only when the current folder can be assigned
1995 // to (which is the same as being able to un-assign a conversation from the folder) and
1996 // when the list of folders contains the current folder.
1997 final boolean isDestructive = mFolder
1998 .supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
1999 && FolderOperation.isDestructive(folderOps, mFolder);
Vikram Aggarwald503df42012-05-11 10:13:35 -07002000 LogUtils.d(LOG_TAG, "onFolderChangesCommit: isDestructive = %b", isDestructive);
2001 if (isDestructive) {
2002 for (final Conversation c : target) {
2003 c.localDeleteOnUpdate = true;
Mindy Pereira6778f462012-03-23 18:01:55 -07002004 }
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07002005 }
mindypc84759c2012-08-29 09:51:53 -07002006 final DestructiveAction folderChange;
Vikram Aggarwald503df42012-05-11 10:13:35 -07002007 // Update the UI elements depending no their visibility and availability
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07002008 // TODO(viki): Consolidate this into a single method requestDelete.
Vikram Aggarwald503df42012-05-11 10:13:35 -07002009 if (isDestructive) {
mindypc84759c2012-08-29 09:51:53 -07002010 folderChange = getDeferredFolderChange(target, folderOps, isDestructive,
2011 batch, showUndo);
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07002012 delete(target, folderChange);
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07002013 } else {
mindypc84759c2012-08-29 09:51:53 -07002014 folderChange = getFolderChange(target, folderOps, isDestructive,
2015 batch, showUndo);
Vikram Aggarwald503df42012-05-11 10:13:35 -07002016 requestUpdate(target, folderChange);
Mindy Pereirae5f4dc02012-03-21 16:08:53 -07002017 }
2018 }
2019
Mindy Pereira967ede62012-03-22 09:29:09 -07002020 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07002021 public final void onRefreshRequired() {
mindyp5390fca2012-08-22 12:12:25 -07002022 if (isAnimating() || isDragging()) {
Mindy Pereira69e88dd2012-08-10 09:30:18 -07002023 LogUtils.d(LOG_TAG, "onRefreshRequired: delay until animating done");
2024 return;
2025 }
Marc Blankbf128eb2012-04-18 15:58:45 -07002026 // Refresh the query in the background
Paul Westbrookcff1aea2012-08-10 11:51:00 -07002027 if (mConversationListCursor.isRefreshRequired()) {
2028 mConversationListCursor.refresh();
2029 }
Marc Blankbf128eb2012-04-18 15:58:45 -07002030 }
2031
mindyp5390fca2012-08-22 12:12:25 -07002032 @Override
2033 public void startDragMode() {
2034 mIsDragHappening = true;
2035 }
2036
2037 @Override
2038 public void stopDragMode() {
2039 mIsDragHappening = false;
2040 if (mConversationListCursor.isRefreshReady()) {
2041 LogUtils.d(LOG_TAG, "Stopped animating: try sync");
2042 onRefreshReady();
2043 }
2044
2045 if (mConversationListCursor.isRefreshRequired()) {
2046 LogUtils.d(LOG_TAG, "Stopped animating: refresh");
2047 mConversationListCursor.refresh();
2048 }
2049 }
2050
2051 private boolean isDragging() {
2052 return mIsDragHappening;
2053 }
2054
Mindy Pereira69e88dd2012-08-10 09:30:18 -07002055 private boolean isAnimating() {
2056 boolean isAnimating = false;
2057 ConversationListFragment convListFragment = getConversationListFragment();
2058 if (convListFragment != null) {
2059 AnimatedAdapter adapter = convListFragment.getAnimatedAdapter();
2060 if (adapter != null) {
2061 isAnimating = adapter.isAnimating();
2062 }
2063 }
2064 return isAnimating;
2065 }
2066
Marc Blankbf128eb2012-04-18 15:58:45 -07002067 /**
2068 * Called when the {@link ConversationCursor} is changed or has new data in it.
2069 * <p>
2070 * {@inheritDoc}
2071 */
2072 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07002073 public final void onRefreshReady() {
Paul Westbrookcff1aea2012-08-10 11:51:00 -07002074 if (!isAnimating()) {
Marc Blankbf128eb2012-04-18 15:58:45 -07002075 // Swap cursors
2076 mConversationListCursor.sync();
Marc Blankbf128eb2012-04-18 15:58:45 -07002077 }
Paul Westbrook937c94f2012-08-16 13:01:18 -07002078 mTracker.onCursorUpdated();
Marc Blankbf128eb2012-04-18 15:58:45 -07002079 }
2080
2081 @Override
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07002082 public final void onDataSetChanged() {
Paul Westbrook9f119c72012-04-24 16:10:59 -07002083 updateConversationListFragment();
Andy Huang632721e2012-04-11 16:57:26 -07002084 mConversationListObservable.notifyChanged();
Marc Blankbf128eb2012-04-18 15:58:45 -07002085 }
2086
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07002087 /**
2088 * If the Conversation List Fragment is visible, updates the fragment.
2089 */
2090 private final void updateConversationListFragment() {
Marc Blankbf128eb2012-04-18 15:58:45 -07002091 final ConversationListFragment convList = getConversationListFragment();
2092 if (convList != null) {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07002093 refreshConversationList();
Paul Westbrook9f119c72012-04-24 16:10:59 -07002094 if (convList.isVisible()) {
Paul Westbrook9f119c72012-04-24 16:10:59 -07002095 Utils.setConversationCursorVisibility(mConversationListCursor, true);
2096 }
Marc Blankbf128eb2012-04-18 15:58:45 -07002097 }
2098 }
2099
2100 /**
2101 * This class handles throttled refresh of the conversation list
2102 */
2103 static class RefreshTimerTask extends TimerTask {
2104 final Handler mHandler;
2105 final AbstractActivityController mController;
2106
2107 RefreshTimerTask(AbstractActivityController controller, Handler handler) {
2108 mHandler = handler;
2109 mController = controller;
2110 }
2111
2112 @Override
2113 public void run() {
2114 mHandler.post(new Runnable() {
2115 @Override
2116 public void run() {
2117 LogUtils.d(LOG_TAG, "Delay done... calling onRefreshRequired");
2118 mController.onRefreshRequired();
2119 }});
2120 }
2121 }
2122
2123 /**
2124 * Cancel the refresh task, if it's running
2125 */
2126 private void cancelRefreshTask () {
2127 if (mConversationListRefreshTask != null) {
2128 mConversationListRefreshTask.cancel();
2129 mConversationListRefreshTask = null;
2130 }
2131 }
2132
2133 @Override
Paul Westbrookcff1aea2012-08-10 11:51:00 -07002134 public void onAnimationEnd(AnimatedAdapter animatedAdapter) {
2135 if (mConversationListCursor.isRefreshReady()) {
mindyp52544862012-08-20 12:05:36 -07002136 LogUtils.d(LOG_TAG, "Stopped animating: try sync");
Paul Westbrookcff1aea2012-08-10 11:51:00 -07002137 onRefreshReady();
Marc Blankbf128eb2012-04-18 15:58:45 -07002138 }
Marc Blankbf128eb2012-04-18 15:58:45 -07002139
Paul Westbrookcff1aea2012-08-10 11:51:00 -07002140 if (mConversationListCursor.isRefreshRequired()) {
mindyp52544862012-08-20 12:05:36 -07002141 LogUtils.d(LOG_TAG, "Stopped animating: refresh");
Paul Westbrookcff1aea2012-08-10 11:51:00 -07002142 mConversationListCursor.refresh();
2143 }
Marc Blankbf128eb2012-04-18 15:58:45 -07002144 }
2145
2146 @Override
Mindy Pereira967ede62012-03-22 09:29:09 -07002147 public void onSetEmpty() {
Mindy Pereira967ede62012-03-22 09:29:09 -07002148 }
2149
2150 @Override
2151 public void onSetPopulated(ConversationSelectionSet set) {
Vikram Aggarwal6902dcf2012-04-11 08:57:42 -07002152 final ConversationListFragment convList = getConversationListFragment();
2153 if (convList == null) {
2154 return;
2155 }
Vikram Aggarwal7c401b72012-08-13 16:43:47 -07002156 mCabActionMenu = new SelectedConversationsActionMenu(mActivity, set, mFolder,
Vikram Aggarwal531488e2012-05-29 16:36:52 -07002157 (SwipeableListView) convList.getListView());
Vikram Aggarwale128fc22012-04-04 12:33:34 -07002158 enableCabMode();
Mindy Pereira967ede62012-03-22 09:29:09 -07002159 }
2160
Mindy Pereira967ede62012-03-22 09:29:09 -07002161 @Override
2162 public void onSetChanged(ConversationSelectionSet set) {
2163 // Do nothing. We don't care about changes to the set.
2164 }
2165
2166 @Override
2167 public ConversationSelectionSet getSelectedSet() {
2168 return mSelectedSet;
2169 }
2170
Vikram Aggarwale128fc22012-04-04 12:33:34 -07002171 /**
2172 * Disable the Contextual Action Bar (CAB). The selected set is not changed.
2173 */
2174 protected void disableCabMode() {
Mindy Pereira8937bf12012-07-23 14:05:02 -07002175 // Commit any previous destructive actions when entering/ exiting CAB mode.
mindypc6adce32012-08-22 18:46:42 -07002176 commitDestructiveActions(true);
Vikram Aggarwale128fc22012-04-04 12:33:34 -07002177 if (mCabActionMenu != null) {
2178 mCabActionMenu.deactivate();
2179 }
2180 }
2181
2182 /**
2183 * Re-enable the CAB menu if required. The selection set is not changed.
2184 */
2185 protected void enableCabMode() {
Mindy Pereira8937bf12012-07-23 14:05:02 -07002186 // Commit any previous destructive actions when entering/ exiting CAB mode.
mindypc6adce32012-08-22 18:46:42 -07002187 commitDestructiveActions(true);
Vikram Aggarwale128fc22012-04-04 12:33:34 -07002188 if (mCabActionMenu != null) {
2189 mCabActionMenu.activate();
2190 }
2191 }
2192
Vikram Aggarwal4eb52712012-06-19 16:24:50 -07002193 /**
2194 * Unselect conversations and exit CAB mode.
2195 */
2196 protected final void exitCabMode() {
2197 mSelectedSet.clear();
2198 }
2199
Mindy Pereira967ede62012-03-22 09:29:09 -07002200 @Override
Mindy Pereirafd0c2972012-03-27 13:50:39 -07002201 public void startSearch() {
Vikram Aggarwal35f19d72012-04-24 13:24:48 -07002202 if (mAccount == null) {
2203 // We cannot search if there is no account. Drop the request to the floor.
2204 LogUtils.d(LOG_TAG, "AbstractActivityController.startSearch(): null account");
2205 return;
2206 }
Mindy Pereirafd0c2972012-03-27 13:50:39 -07002207 if (mAccount.supportsCapability(UIProvider.AccountCapabilities.LOCAL_SEARCH)
2208 | mAccount.supportsCapability(UIProvider.AccountCapabilities.SERVER_SEARCH)) {
Vikram Aggarwal70f130e2012-04-03 12:32:14 -07002209 onSearchRequested(mActionBarView.getQuery());
Mindy Pereirafd0c2972012-03-27 13:50:39 -07002210 } else {
2211 Toast.makeText(mActivity.getActivityContext(), mActivity.getActivityContext()
Mindy Pereiraa46c2992012-03-27 14:12:39 -07002212 .getString(R.string.search_unsupported), Toast.LENGTH_SHORT).show();
Mindy Pereirafd0c2972012-03-27 13:50:39 -07002213 }
2214 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07002215
Vikram Aggarwal0dda5732012-04-06 11:20:16 -07002216 @Override
2217 public void exitSearchMode() {
2218 if (mViewMode.getMode() == ViewMode.SEARCH_RESULTS_LIST) {
2219 mActivity.finish();
2220 }
2221 }
2222
Mindy Pereiraacf60392012-04-06 09:11:00 -07002223 /**
2224 * Supports dragging conversations to a folder.
2225 */
2226 @Override
2227 public boolean supportsDrag(DragEvent event, Folder folder) {
2228 return (folder != null
2229 && event != null
2230 && event.getClipDescription() != null
2231 && folder.supportsCapability
2232 (UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
2233 && folder.supportsCapability
2234 (UIProvider.FolderCapabilities.CAN_HOLD_MAIL)
2235 && !mFolder.uri.equals(folder.uri));
2236 }
2237
2238 /**
Mindy Pereira6c2663d2012-07-20 15:37:29 -07002239 * Handles dropping conversations to a folder.
Mindy Pereiraacf60392012-04-06 09:11:00 -07002240 */
2241 @Override
2242 public void handleDrop(DragEvent event, final Folder folder) {
Mindy Pereiraacf60392012-04-06 09:11:00 -07002243 if (!supportsDrag(event, folder)) {
2244 return;
2245 }
Mindy Pereiraacf60392012-04-06 09:11:00 -07002246 final Collection<Conversation> conversations = mSelectedSet.values();
Mindy Pereira8db7e402012-07-13 10:32:47 -07002247 final Collection<FolderOperation> dropTarget = FolderOperation.listOf(new FolderOperation(
2248 folder, true));
2249 // Drag and drop is destructive: we remove conversations from the
2250 // current folder.
Mindy Pereira06642fa2012-07-12 16:23:27 -07002251 final DestructiveAction action = getFolderChange(conversations, dropTarget, true, true,
2252 true);
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07002253 delete(conversations, action);
Mindy Pereiraacf60392012-04-06 09:11:00 -07002254 }
Mindy Pereira0963ef82012-04-10 11:43:01 -07002255
2256 @Override
Mindy Pereira0963ef82012-04-10 11:43:01 -07002257 public void onTouchEvent(MotionEvent event) {
2258 if (event.getAction() == MotionEvent.ACTION_DOWN) {
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002259 if (mToastBar != null && !mToastBar.isEventInToastBar(event)) {
2260 mToastBar.hide(true);
Mindy Pereira0963ef82012-04-10 11:43:01 -07002261 }
2262 }
2263 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07002264
Andy Huang632721e2012-04-11 16:57:26 -07002265 @Override
2266 public void onConversationSeen(Conversation conv) {
2267 mPagerController.onConversationSeen(conv);
2268 }
2269
Andy Huangb1c34dc2012-04-17 16:36:19 -07002270 private class ConversationListLoaderCallbacks implements
2271 LoaderManager.LoaderCallbacks<ConversationCursor> {
2272
2273 @Override
2274 public Loader<ConversationCursor> onCreateLoader(int id, Bundle args) {
2275 Loader<ConversationCursor> result = new ConversationCursorLoader((Activity) mActivity,
Paul Westbrook9a70e912012-08-17 15:53:20 -07002276 mAccount, mFolder.conversationListUri, mFolder.name);
Andy Huangb1c34dc2012-04-17 16:36:19 -07002277 return result;
2278 }
2279
2280 @Override
2281 public void onLoadFinished(Loader<ConversationCursor> loader, ConversationCursor data) {
Andy Huang632721e2012-04-11 16:57:26 -07002282 LogUtils.d(LOG_TAG, "IN AAC.ConversationCursor.onLoadFinished, data=%s loader=%s",
2283 data, loader);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002284 // Clear our all pending destructive actions before swapping the conversation cursor
2285 destroyPending(null);
Andy Huangb1c34dc2012-04-17 16:36:19 -07002286 mConversationListCursor = data;
Paul Westbrookbf232c32012-04-18 03:17:41 -07002287 mConversationListCursor.addListener(AbstractActivityController.this);
Andy Huangb1c34dc2012-04-17 16:36:19 -07002288
Paul Westbrook937c94f2012-08-16 13:01:18 -07002289 mTracker.onCursorUpdated();
2290
Andy Huange3df1ad2012-04-24 17:15:23 -07002291 mConversationListObservable.notifyChanged();
Andy Huangb1c34dc2012-04-17 16:36:19 -07002292 // Register the AbstractActivityController as a listener to changes in
2293 // data in the cursor.
2294 final ConversationListFragment convList = getConversationListFragment();
2295 if (convList != null) {
2296 convList.onCursorUpdated();
Paul Westbrook9f119c72012-04-24 16:10:59 -07002297
2298 if (convList.isVisible()) {
2299 // The conversation list is visible.
2300 Utils.setConversationCursorVisibility(mConversationListCursor, true);
2301 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07002302 }
2303 // Shown for search results in two-pane mode only.
2304 if (shouldShowFirstConversation()) {
2305 if (mConversationListCursor.getCount() > 0) {
2306 mConversationListCursor.moveToPosition(0);
Andy Huangb1c34dc2012-04-17 16:36:19 -07002307 final Conversation conv = new Conversation(mConversationListCursor);
2308 conv.position = 0;
Andy Huang1ee96b22012-08-24 20:19:53 -07002309 onConversationSelected(conv, true /* checkSafeToModifyFragments */);
Andy Huangb1c34dc2012-04-17 16:36:19 -07002310 }
2311 }
2312 }
2313
2314 @Override
2315 public void onLoaderReset(Loader<ConversationCursor> loader) {
Paul Westbrook9a70e912012-08-17 15:53:20 -07002316 LogUtils.d(LOG_TAG, "IN AAC.ConversationCursor.onLoaderReset, data=%s loader=%s",
2317 mConversationListCursor, loader);
2318
2319 if (mConversationListCursor != null) {
2320 // Unregister the listener
2321 mConversationListCursor.removeListener(AbstractActivityController.this);
2322 mConversationListCursor = null;
2323
2324 // Inform anyone who is interested about the change
2325 mTracker.onCursorUpdated();
2326 mConversationListObservable.notifyChanged();
2327
2328 final ConversationListFragment convList = getConversationListFragment();
2329 if (convList != null) {
2330 convList.onCursorUpdated();
2331 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07002332 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07002333 }
Paul Westbrookbf232c32012-04-18 03:17:41 -07002334 }
Andy Huangb1c34dc2012-04-17 16:36:19 -07002335
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002336 /**
2337 * Destroy the pending {@link DestructiveAction} till now and assign the given action as the
2338 * next destructive action..
2339 * @param nextAction the next destructive action to be performed. This can be null.
2340 */
2341 private final void destroyPending(DestructiveAction nextAction) {
2342 // If there is a pending action, perform that first.
2343 if (mPendingDestruction != null) {
2344 mPendingDestruction.performAction();
2345 }
2346 mPendingDestruction = nextAction;
2347 }
2348
2349 /**
2350 * Register a destructive action with the controller. This performs the previous destructive
Vikram Aggarwalacaa3c02012-04-24 12:45:27 -07002351 * action as a side effect. This method is final because we don't want the child classes to
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07002352 * embellish this method any more.
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002353 * @param action
2354 */
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -07002355 private final void registerDestructiveAction(DestructiveAction action) {
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002356 // TODO(viki): This is not a good idea. The best solution is for clients to request a
2357 // destructive action from the controller and for the controller to own the action. This is
2358 // a half-way solution while refactoring DestructiveAction.
2359 destroyPending(action);
2360 return;
2361 }
2362
Vikram Aggarwal531488e2012-05-29 16:36:52 -07002363 @Override
2364 public final DestructiveAction getBatchAction(int action) {
Vikram Aggarwalc4113952012-05-11 14:14:56 -07002365 final DestructiveAction da = new ConversationAction(action, mSelectedSet.values(), true);
Vikram Aggarwale8a85322012-04-24 09:01:38 -07002366 registerDestructiveAction(da);
2367 return da;
2368 }
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002369
Mindy Pereirade3e74a2012-07-24 09:43:10 -07002370 @Override
2371 public final DestructiveAction getDeferredBatchAction(int action) {
2372 final DestructiveAction da = new ConversationAction(action, mSelectedSet.values(), true);
2373 return da;
2374 }
2375
Vikram Aggarwalbc67bb12012-04-30 14:05:35 -07002376 /**
2377 * Class to change the folders that are assigned to a set of conversations. This is destructive
2378 * because the user can remove the current folder from the conversation, in which case it has
2379 * to be animated away from the current folder.
2380 */
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002381 private class FolderDestruction implements DestructiveAction {
Paul Westbrook77eee622012-07-10 13:41:57 -07002382 private final Collection<Conversation> mTarget;
Mindy Pereira8db7e402012-07-13 10:32:47 -07002383 private final ArrayList<FolderOperation> mFolderOps = new ArrayList<FolderOperation>();
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002384 private final boolean mIsDestructive;
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002385 /** Whether this destructive action has already been performed */
2386 private boolean mCompleted;
Mindy Pereiraf3a45562012-05-24 16:30:19 -07002387 private boolean mIsSelectedSet;
Mindy Pereira06642fa2012-07-12 16:23:27 -07002388 private boolean mShowUndo;
Mindy Pereira01f30502012-08-14 10:30:51 -07002389 private int mAction;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002390
2391 /**
2392 * Create a new folder destruction object to act on the given conversations.
2393 * @param target
2394 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002395 private FolderDestruction(final Collection<Conversation> target,
Mindy Pereira8db7e402012-07-13 10:32:47 -07002396 final Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
Mindy Pereira01f30502012-08-14 10:30:51 -07002397 boolean showUndo, int action) {
Paul Westbrook77eee622012-07-10 13:41:57 -07002398 mTarget = ImmutableList.copyOf(target);
Mindy Pereira8db7e402012-07-13 10:32:47 -07002399 mFolderOps.addAll(folders);
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002400 mIsDestructive = isDestructive;
Mindy Pereiraf3a45562012-05-24 16:30:19 -07002401 mIsSelectedSet = isBatch;
Mindy Pereira06642fa2012-07-12 16:23:27 -07002402 mShowUndo = showUndo;
Mindy Pereira01f30502012-08-14 10:30:51 -07002403 mAction = action;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002404 }
2405
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002406 @Override
2407 public void performAction() {
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002408 if (isPerformed()) {
2409 return;
2410 }
Mindy Pereira06642fa2012-07-12 16:23:27 -07002411 if (mIsDestructive && mShowUndo) {
Mindy Pereirad33674992012-06-25 16:26:30 -07002412 ToastBarOperation undoOp = new ToastBarOperation(mTarget.size(),
Mindy Pereira01f30502012-08-14 10:30:51 -07002413 mAction, ToastBarOperation.UNDO);
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002414 onUndoAvailable(undoOp);
2415 }
Mindy Pereira8db7e402012-07-13 10:32:47 -07002416 // For each conversation, for each operation, add/ remove the
2417 // appropriate folders.
mindyp389f0b22012-08-29 11:12:54 -07002418 ArrayList<String> updatedTargetFolders = new ArrayList<String>(mTarget.size());
Mindy Pereira8db7e402012-07-13 10:32:47 -07002419 for (Conversation target : mTarget) {
2420 HashMap<Uri, Folder> targetFolders = Folder
Mindy Pereira68f83842012-07-27 09:43:31 -07002421 .hashMapForFolders(target.getRawFolders());
Mindy Pereira01f30502012-08-14 10:30:51 -07002422 if (mIsDestructive) {
2423 target.localDeleteOnUpdate = true;
2424 }
Mindy Pereira8db7e402012-07-13 10:32:47 -07002425 for (FolderOperation op : mFolderOps) {
2426 if (op.mAdd) {
2427 targetFolders.put(op.mFolder.uri, op.mFolder);
2428 } else {
2429 targetFolders.remove(op.mFolder.uri);
2430 }
2431 }
mindyp389f0b22012-08-29 11:12:54 -07002432 updatedTargetFolders.add(Folder.getSerializedFolderString(targetFolders.values()));
2433 }
2434 if (mConversationListCursor != null) {
2435 mConversationListCursor.updateStrings(mContext, mTarget,
2436 Conversation.UPDATE_FOLDER_COLUMN, updatedTargetFolders);
Mindy Pereira8db7e402012-07-13 10:32:47 -07002437 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002438 refreshConversationList();
Mindy Pereiraf3a45562012-05-24 16:30:19 -07002439 if (mIsSelectedSet) {
2440 mSelectedSet.clear();
2441 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002442 }
Mindy Pereira8db7e402012-07-13 10:32:47 -07002443
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002444 /**
2445 * Returns true if this action has been performed, false otherwise.
Andy Huang839ada22012-07-20 15:48:40 -07002446 *
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002447 */
2448 private synchronized boolean isPerformed() {
2449 if (mCompleted) {
2450 return true;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002451 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002452 mCompleted = true;
2453 return false;
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002454 }
2455 }
Vikram Aggarwal7f602f72012-04-30 16:04:06 -07002456
mindypc84759c2012-08-29 09:51:53 -07002457 public final DestructiveAction getFolderChange(Collection<Conversation> target,
2458 Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
2459 boolean showUndo) {
2460 final DestructiveAction da = getDeferredFolderChange(target, folders, isDestructive,
2461 isBatch, showUndo);
2462 registerDestructiveAction(da);
2463 return da;
2464 }
2465
2466 public final DestructiveAction getDeferredFolderChange(Collection<Conversation> target,
Mindy Pereira8db7e402012-07-13 10:32:47 -07002467 Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
2468 boolean showUndo) {
Mindy Pereira06642fa2012-07-12 16:23:27 -07002469 final DestructiveAction da = new FolderDestruction(target, folders, isDestructive, isBatch,
Mindy Pereira01f30502012-08-14 10:30:51 -07002470 showUndo, R.id.change_folder);
Mindy Pereira01f30502012-08-14 10:30:51 -07002471 return da;
2472 }
2473
2474 @Override
2475 public final DestructiveAction getDeferredRemoveFolder(Collection<Conversation> target,
2476 Folder toRemove, boolean isDestructive, boolean isBatch,
2477 boolean showUndo) {
2478 Collection<FolderOperation> folderOps = new ArrayList<FolderOperation>();
2479 folderOps.add(new FolderOperation(toRemove, false));
2480 return new FolderDestruction(target, folderOps, isDestructive, isBatch,
2481 showUndo, R.id.remove_folder);
2482 }
2483
2484 private final DestructiveAction getRemoveFolder(Collection<Conversation> target,
mindypc84759c2012-08-29 09:51:53 -07002485 Folder toRemove, boolean isDestructive, boolean isBatch, boolean showUndo) {
2486 DestructiveAction da = getDeferredRemoveFolder(target, toRemove, isDestructive, isBatch,
2487 showUndo);
Vikram Aggarwal41e6e712012-04-24 11:22:57 -07002488 registerDestructiveAction(da);
2489 return da;
2490 }
Vikram Aggarwal75daee52012-04-30 11:13:09 -07002491
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -07002492 @Override
2493 public final void refreshConversationList() {
Vikram Aggarwal75daee52012-04-30 11:13:09 -07002494 final ConversationListFragment convList = getConversationListFragment();
2495 if (convList == null) {
2496 return;
2497 }
2498 convList.requestListRefresh();
2499 }
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002500
2501 protected final ActionClickedListener getUndoClickedListener(
2502 final AnimatedAdapter listAdapter) {
2503 return new ActionClickedListener() {
2504 @Override
2505 public void onActionClicked() {
2506 if (mAccount.undoUri != null) {
2507 // NOTE: We might want undo to return the messages affected, in which case
2508 // the resulting cursor might be interesting...
2509 // TODO: Use UIProvider.SEQUENCE_QUERY_PARAMETER to indicate the set of
2510 // commands to undo
2511 if (mConversationListCursor != null) {
2512 mConversationListCursor.undo(
2513 mActivity.getActivityContext(), mAccount.undoUri);
2514 }
2515 if (listAdapter != null) {
2516 listAdapter.setUndo(true);
2517 }
2518 }
2519 }
2520 };
2521 }
2522
Andrew Sapperstein9d7519d2012-07-16 14:03:53 -07002523 protected final void showErrorToast(final Folder folder, boolean replaceVisibleToast) {
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002524 mToastBar.setConversationMode(false);
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002525
2526 ActionClickedListener listener = null;
2527 int actionTextResourceId;
2528 final int lastSyncResult = folder.lastSyncResult;
2529 switch (lastSyncResult) {
2530 case UIProvider.LastSyncResult.CONNECTION_ERROR:
2531 listener = getRetryClickedListener(folder);
2532 actionTextResourceId = R.string.retry;
2533 break;
2534 case UIProvider.LastSyncResult.AUTH_ERROR:
2535 listener = getSignInClickedListener();
2536 actionTextResourceId = R.string.signin;
2537 break;
2538 case UIProvider.LastSyncResult.SECURITY_ERROR:
2539 return; // Currently we do nothing for security errors.
2540 case UIProvider.LastSyncResult.STORAGE_ERROR:
2541 listener = getStorageErrorClickedListener();
2542 actionTextResourceId = R.string.info;
2543 break;
2544 case UIProvider.LastSyncResult.INTERNAL_ERROR:
2545 listener = getInternalErrorClickedListener();
2546 actionTextResourceId = R.string.report;
2547 break;
2548 default:
2549 return;
2550 }
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002551 mToastBar.show(
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002552 listener,
Andrew Sapperstein5d420962012-07-12 16:43:10 -07002553 R.drawable.ic_alert_white,
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002554 Utils.getSyncStatusText(mActivity.getActivityContext(),
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002555 lastSyncResult),
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002556 false, /* showActionIcon */
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002557 actionTextResourceId,
Andrew Sapperstein9d7519d2012-07-16 14:03:53 -07002558 replaceVisibleToast,
2559 new ToastBarOperation(1, 0, ToastBarOperation.ERROR));
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002560 }
2561
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002562 private ActionClickedListener getRetryClickedListener(final Folder folder) {
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002563 return new ActionClickedListener() {
2564 @Override
2565 public void onActionClicked() {
2566 final Uri uri = folder.refreshUri;
2567
2568 if (uri != null) {
Paul Westbrook4969e0c2012-08-20 14:38:39 -07002569 startAsyncRefreshTask(uri);
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -07002570 }
2571 }
2572 };
2573 }
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002574
2575 private ActionClickedListener getSignInClickedListener() {
2576 return new ActionClickedListener() {
2577 @Override
2578 public void onActionClicked() {
Paul Westbrook122f7c22012-08-20 17:50:31 -07002579 promptUserForAuthentication(mAccount);
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002580 }
2581 };
2582 }
2583
2584 private ActionClickedListener getStorageErrorClickedListener() {
2585 return new ActionClickedListener() {
2586 @Override
2587 public void onActionClicked() {
Paul Westbrook4969e0c2012-08-20 14:38:39 -07002588 showStorageErrorDialog();
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002589 }
2590 };
2591 }
2592
Paul Westbrook4969e0c2012-08-20 14:38:39 -07002593 private void showStorageErrorDialog() {
2594 DialogFragment fragment = (DialogFragment)
2595 mFragmentManager.findFragmentByTag(SYNC_ERROR_DIALOG_FRAGMENT_TAG);
2596 if (fragment == null) {
2597 fragment = SyncErrorDialogFragment.newInstance();
2598 }
2599 fragment.show(mFragmentManager, SYNC_ERROR_DIALOG_FRAGMENT_TAG);
2600 }
2601
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002602 private ActionClickedListener getInternalErrorClickedListener() {
2603 return new ActionClickedListener() {
2604 @Override
2605 public void onActionClicked() {
Paul Westbrook17beb0b2012-08-20 13:34:37 -07002606 Utils.sendFeedback(
2607 mActivity.getActivityContext(), mAccount, true /* reportingProblem */);
Andrew Sapperstein00179f12012-08-09 15:15:40 -07002608 }
2609 };
2610 }
Paul Westbrook4969e0c2012-08-20 14:38:39 -07002611
2612 @Override
2613 public void onFooterViewErrorActionClick(Folder folder, int errorStatus) {
2614 Uri uri = null;
2615 switch (errorStatus) {
2616 case UIProvider.LastSyncResult.CONNECTION_ERROR:
2617 if (folder != null && folder.refreshUri != null) {
2618 uri = folder.refreshUri;
2619 }
2620 break;
2621 case UIProvider.LastSyncResult.AUTH_ERROR:
Paul Westbrook122f7c22012-08-20 17:50:31 -07002622 promptUserForAuthentication(mAccount);
Paul Westbrook4969e0c2012-08-20 14:38:39 -07002623 return;
2624 case UIProvider.LastSyncResult.SECURITY_ERROR:
2625 return; // Currently we do nothing for security errors.
2626 case UIProvider.LastSyncResult.STORAGE_ERROR:
2627 showStorageErrorDialog();
2628 return;
2629 case UIProvider.LastSyncResult.INTERNAL_ERROR:
2630 Utils.sendFeedback(
2631 mActivity.getActivityContext(), mAccount, true /* reportingProblem */);
2632 return;
2633 default:
2634 return;
2635 }
2636
2637 if (uri != null) {
2638 startAsyncRefreshTask(uri);
2639 }
2640 }
2641
2642 @Override
2643 public void onFooterViewLoadMoreClick(Folder folder) {
2644 if (folder != null && folder.loadMoreUri != null) {
2645 startAsyncRefreshTask(folder.loadMoreUri);
2646 }
2647 }
2648
2649 private void startAsyncRefreshTask(Uri uri) {
2650 if (mFolderSyncTask != null) {
2651 mFolderSyncTask.cancel(true);
2652 }
2653 mFolderSyncTask = new AsyncRefreshTask(mActivity.getActivityContext(), uri);
2654 mFolderSyncTask.execute();
2655 }
Paul Westbrook122f7c22012-08-20 17:50:31 -07002656
2657 private void promptUserForAuthentication(Account account) {
Paul Westbrook429399b2012-08-24 11:19:17 -07002658 if (account != null && !Utils.isEmpty(account.reauthenticationIntentUri)) {
Paul Westbrook122f7c22012-08-20 17:50:31 -07002659 final Intent authenticationIntent =
2660 new Intent(Intent.ACTION_VIEW, account.reauthenticationIntentUri);
2661 mActivity.startActivityForResult(authenticationIntent, REAUTHENTICATE_REQUEST_CODE);
2662 }
2663 }
Vikram Aggarwal4a5c5302012-01-12 15:07:13 -08002664}