blob: 833814b2d931949987954aaf82e1dc20ece5db78 [file] [log] [blame]
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -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 Aggarwalb9e1a352012-01-24 15:23:38 -080020import android.app.Activity;
21import android.app.ListFragment;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070022import android.content.Context;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080023import android.content.res.Resources;
Mindy Pereira6d8e7fe2012-07-26 16:02:49 -070024import android.database.DataSetObserver;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080025import android.os.Bundle;
26import android.os.Handler;
27import android.os.Parcelable;
28import android.view.LayoutInflater;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080029import android.view.View;
30import android.view.ViewGroup;
31import android.view.ViewGroup.MarginLayoutParams;
32import android.widget.AdapterView;
33import android.widget.AdapterView.OnItemLongClickListener;
34import android.widget.ListView;
35import android.widget.TextView;
36
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080037import com.android.mail.ConversationListContext;
Marc Blankf3626952012-02-28 17:06:05 -080038import com.android.mail.R;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080039import com.android.mail.browse.ConversationCursor;
Vikram Aggarwald247dc92012-02-10 15:49:01 -080040import com.android.mail.browse.ConversationItemView;
Mindy Pereira12fe37a2012-08-15 10:02:57 -070041import com.android.mail.browse.ConversationItemViewModel;
Mindy Pereira6681f6b2012-03-09 13:55:54 -080042import com.android.mail.browse.ConversationListFooterView;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080043import com.android.mail.providers.Account;
Vikram Aggarwal7c401b72012-08-13 16:43:47 -070044import com.android.mail.providers.AccountObserver;
Vikram Aggarwald247dc92012-02-10 15:49:01 -080045import com.android.mail.providers.Conversation;
Mindy Pereira4f166de2012-02-14 13:40:58 -080046import com.android.mail.providers.Folder;
Vikram Aggarwal7d816002012-04-17 17:06:41 -070047import com.android.mail.providers.Settings;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080048import com.android.mail.providers.UIProvider;
Mindy Pereira4e812f42012-07-30 16:52:49 -070049import com.android.mail.providers.UIProvider.AccountCapabilities;
50import com.android.mail.providers.UIProvider.FolderCapabilities;
Mindy Pereirae58222b2012-07-25 14:33:18 -070051import com.android.mail.providers.UIProvider.Swipe;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070052import com.android.mail.ui.SwipeableListView.SwipeCompleteListener;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080053import com.android.mail.ui.ViewMode.ModeChangeListener;
Paul Westbrookb334c902012-06-25 11:42:46 -070054import com.android.mail.utils.LogTag;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080055import com.android.mail.utils.LogUtils;
Vikram Aggarwalfa131a22012-02-02 13:56:22 -080056import com.android.mail.utils.Utils;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080057
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070058import java.util.Collection;
Mindy Pereirafe06bea2012-02-16 08:15:14 -080059
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080060/**
61 * The conversation list UI component.
62 */
Vikram Aggarwald247dc92012-02-10 15:49:01 -080063public final class ConversationListFragment extends ListFragment implements
Vikram Aggarwal7c401b72012-08-13 16:43:47 -070064 OnItemLongClickListener, ModeChangeListener, SwipeCompleteListener {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080065 // Keys used to pass data to {@link ConversationListFragment}.
66 private static final String CONVERSATION_LIST_KEY = "conversation-list";
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080067 // Key used to keep track of the scroll state of the list.
68 private static final String LIST_STATE_KEY = "list-state";
Paul Westbrookb334c902012-06-25 11:42:46 -070069 private static final String LOG_TAG = LogTag.getLogTag();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080070
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -080071 // True if we are on a tablet device
72 private static boolean mTabletDevice;
73
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080074 /**
75 * Frequency of update of timestamps. Initialized in {@link #onCreate(Bundle)} and final
76 * afterwards.
77 */
78 private static int TIMESTAMP_UPDATE_INTERVAL = 0;
79
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080080 private ControllableActivity mActivity;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080081
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080082 // Control state.
83 private ConversationListCallbacks mCallbacks;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080084
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080085 private final Handler mHandler = new Handler();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080086 // List save state.
87 private Parcelable mListSavedState;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080088
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080089 // The internal view objects.
Mindy Pereiraf6a6b502012-03-15 15:24:26 -070090 private SwipeableListView mListView;
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -080091
92 private TextView mSearchResultCountTextView;
93 private TextView mSearchStatusTextView;
94
95 private View mSearchStatusView;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080096
Vikram Aggarwal80aeac52012-02-07 15:27:20 -080097 /**
98 * Current Account being viewed
99 */
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800100 private Account mAccount;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800101 /**
Mindy Pereira30fd47b2012-03-09 09:24:00 -0800102 * Current folder being viewed.
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800103 */
Mindy Pereira4f166de2012-02-14 13:40:58 -0800104 private Folder mFolder;
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800105
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800106 /**
107 * A simple method to update the timestamps of conversations periodically.
108 */
109 private Runnable mUpdateTimestampsRunnable = null;
110
111 private ConversationListContext mViewContext;
112
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800113 private AnimatedAdapter mListAdapter;
Vikram Aggarwal7d602882012-02-07 15:01:20 -0800114
Mindy Pereira6681f6b2012-03-09 13:55:54 -0800115 private ConversationListFooterView mFooterView;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700116 private int mSwipeAction;
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700117 private ErrorListener mErrorListener;
Mindy Pereirae3c7b0b2012-07-26 16:28:34 -0700118 private DataSetObserver mFolderObserver;
Mindy Pereira70a70c92012-08-02 08:39:45 -0700119 private DataSetObserver mConversationListStatusObserver;
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800120
Vikram Aggarwal4f1cc0b2012-08-02 15:16:30 -0700121 private ConversationSelectionSet mSelectedSet;
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700122 private final AccountObserver mAccountObserver = new AccountObserver() {
123 @Override
124 public void onChanged(Account newAccount) {
125 mAccount = newAccount;
126 setSwipeAction();
127 }
128 };
Vikram Aggarwal4f1cc0b2012-08-02 15:16:30 -0700129
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800130 /**
131 * Constructor needs to be public to handle orientation changes and activity lifecycle events.
132 */
Paul Westbrookefc9f122012-02-21 11:14:49 -0800133 public ConversationListFragment() {
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800134 super();
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800135 }
136
Mindy Pereira6d8e7fe2012-07-26 16:02:49 -0700137 // update the pager title strip as the Folder's conversation count changes
138 private class FolderObserver extends DataSetObserver {
139 @Override
140 public void onChanged() {
Andy Huang7591d2f2012-07-26 16:48:06 -0700141 onFolderUpdated(mActivity.getFolderController().getFolder());
Mindy Pereira6d8e7fe2012-07-26 16:02:49 -0700142 }
143 }
144
Mindy Pereira70a70c92012-08-02 08:39:45 -0700145 private class ConversationListStatusObserver extends DataSetObserver {
146 @Override
147 public void onChanged() {
148 // update footer
149 onConversationListStatusUpdated();
150 }
151 }
152
Vikram Aggarwal7d602882012-02-07 15:01:20 -0800153 /**
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800154 * Creates a new instance of {@link ConversationListFragment}, initialized to display
155 * conversation list context.
156 */
157 public static ConversationListFragment newInstance(ConversationListContext viewContext) {
158 ConversationListFragment fragment = new ConversationListFragment();
159 Bundle args = new Bundle();
160 args.putBundle(CONVERSATION_LIST_KEY, viewContext.toBundle());
161 fragment.setArguments(args);
162 return fragment;
163 }
164
165 /**
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800166 * Show the header if the current conversation list is showing search results.
167 */
Mindy Pereira967ede62012-03-22 09:29:09 -0700168 void configureSearchResultHeader() {
Mindy Pereira755fd6e2012-03-21 15:15:44 -0700169 if (mActivity == null) {
170 return;
171 }
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800172 // Only show the header if the context is for a search result
173 final Resources res = getResources();
Vikram Aggarwalae4ea992012-08-07 09:56:02 -0700174 final boolean showHeader = ConversationListContext.isSearchResult(mViewContext);
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800175 // TODO(viki): This code contains intimate understanding of the view. Much of this logic
176 // needs to reside in a separate class that handles the text view in isolation. Then,
177 // that logic can be reused in other fragments.
178 if (showHeader) {
179 mSearchStatusTextView.setText(res.getString(R.string.search_results_searching_header));
180 // Initially reset the count
181 mSearchResultCountTextView.setText("");
182 }
183 mSearchStatusView.setVisibility(showHeader ? View.VISIBLE : View.GONE);
184 int marginTop = showHeader ? (int) res.getDimension(R.dimen.notification_view_height) : 0;
185 MarginLayoutParams layoutParams = (MarginLayoutParams) mListView.getLayoutParams();
186 layoutParams.topMargin = marginTop;
187 mListView.setLayoutParams(layoutParams);
188 }
189
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800190 /**
Mindy Pereira68f2e222012-03-07 10:36:54 -0800191 * Show the header if the current conversation list is showing search results.
192 */
193 private void updateSearchResultHeader(int count) {
Mindy Pereira71a8f292012-07-26 16:30:48 -0700194 if (mActivity == null) {
195 return;
196 }
Mindy Pereira68f2e222012-03-07 10:36:54 -0800197 // Only show the header if the context is for a search result
198 final Resources res = getResources();
Vikram Aggarwalae4ea992012-08-07 09:56:02 -0700199 final boolean showHeader = ConversationListContext.isSearchResult(mViewContext);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800200 if (showHeader) {
201 mSearchStatusTextView.setText(res.getString(R.string.search_results_header));
202 mSearchResultCountTextView
203 .setText(res.getString(R.string.search_results_loaded, count));
204 }
205 }
206
207 /**
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800208 * Initializes all internal state for a rendering.
209 */
210 private void initializeUiForFirstDisplay() {
211 // TODO(mindyp): find some way to make the notification container more re-usable.
212 // TODO(viki): refactor according to comment in configureSearchResultHandler()
213 mSearchStatusView = mActivity.findViewById(R.id.search_status_view);
214 mSearchStatusTextView = (TextView) mActivity.findViewById(R.id.search_status_text_view);
215 mSearchResultCountTextView = (TextView) mActivity.findViewById(
216 R.id.search_result_count_view);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800217 }
218
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800219 @Override
220 public void onActivityCreated(Bundle savedInstanceState) {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800221 super.onActivityCreated(savedInstanceState);
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800222 // Strictly speaking, we get back an android.app.Activity from getActivity. However, the
223 // only activity creating a ConversationListContext is a MailActivity which is of type
224 // ControllableActivity, so this cast should be safe. If this cast fails, some other
225 // activity is creating ConversationListFragments. This activity must be of type
226 // ControllableActivity.
227 final Activity activity = getActivity();
Andy Huang5ff63742012-03-16 20:30:23 -0700228 if (! (activity instanceof ControllableActivity)) {
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800229 LogUtils.e(LOG_TAG, "ConversationListFragment expects only a ControllableActivity to" +
230 "create it. Cannot proceed.");
231 }
232 mActivity = (ControllableActivity) activity;
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700233 // Since we now have a controllable activity, load the account from it, and register for
234 // future account changes.
235 mAccount = mAccountObserver.initialize(mActivity.getAccountController());
Vikram Aggarwal7d602882012-02-07 15:01:20 -0800236 mCallbacks = mActivity.getListHandler();
Andrew Sappersteinc2c9dc12012-07-02 18:17:32 -0700237 mErrorListener = mActivity.getErrorListener();
Mindy Pereira278fd222012-07-26 15:23:10 -0700238 // Start off with the current state of the folder being viewed.
Mindy Pereira6681f6b2012-03-09 13:55:54 -0800239 mFooterView = (ConversationListFooterView) LayoutInflater.from(
240 mActivity.getActivityContext()).inflate(R.layout.conversation_list_footer_view,
241 null);
Andrew Sapperstein00179f12012-08-09 15:15:40 -0700242 mFooterView.setFragmentManager(getFragmentManager());
Mindy Pereira6d8e7fe2012-07-26 16:02:49 -0700243 mListAdapter = new AnimatedAdapter(mActivity.getApplicationContext(), -1,
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700244 getConversationListCursor(), mActivity.getSelectedSet(), mActivity, mListView);
Mindy Pereira6681f6b2012-03-09 13:55:54 -0800245 mListAdapter.addFooter(mFooterView);
Mindy Pereira6f4a6af2012-02-29 11:48:52 -0800246 mListView.setAdapter(mListAdapter);
Vikram Aggarwal4f1cc0b2012-08-02 15:16:30 -0700247 mSelectedSet = mActivity.getSelectedSet();
248 mListView.setSelectionSet(mSelectedSet);
Mindy Pereiradc8963b2012-03-28 17:51:05 -0700249 mListAdapter.hideFooter();
Mindy Pereirae3c7b0b2012-07-26 16:28:34 -0700250 mFolderObserver = new FolderObserver();
Andy Huang7591d2f2012-07-26 16:48:06 -0700251 mActivity.getFolderController().registerFolderObserver(mFolderObserver);
Mindy Pereira70a70c92012-08-02 08:39:45 -0700252 mConversationListStatusObserver = new ConversationListStatusObserver();
253 mActivity.getConversationUpdater().registerConversationListObserver(
254 mConversationListStatusObserver);
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800255 mTabletDevice = Utils.useTabletUI(mActivity.getApplicationContext());
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800256 initializeUiForFirstDisplay();
Mindy Pereiradc8963b2012-03-28 17:51:05 -0700257 configureSearchResultHeader();
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800258 // The onViewModeChanged callback doesn't get called when the mode object is created, so
259 // force setting the mode manually this time around.
Mindy Pereiraf96ec322012-03-02 14:06:33 -0800260 onViewModeChanged(mActivity.getViewMode().getMode());
Mindy Pereirab5901be2012-08-09 17:21:53 -0700261 mActivity.getViewMode().addListener(this);
Paul Westbrook0e3fd9d2012-04-20 02:02:23 -0700262 // Restore the list state
263 if (mListSavedState != null) {
264 mListView.onRestoreInstanceState(mListSavedState);
265
266 // TODO: find a better way to unset the selected item when restoring
267 mListView.clearChoices();
268 }
269
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800270 if (mActivity.isFinishing()) {
271 // Activity is finishing, just bail.
272 return;
273 }
274
275 // Show list and start loading list.
276 showList();
Mindy Pereira4765c5c2012-07-19 11:58:22 -0700277 ToastBarOperation pendingOp = mActivity.getPendingToastOperation();
278 if (pendingOp != null) {
279 // Clear the pending operation
280 mActivity.setPendingToastOperation(null);
281 mActivity.onUndoAvailable(pendingOp);
282 }
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800283 }
284
Mindy Pereira967ede62012-03-22 09:29:09 -0700285 public AnimatedAdapter getAnimatedAdapter() {
286 return mListAdapter;
287 }
288
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800289 @Override
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800290 public void onCreate(Bundle savedState) {
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800291 super.onCreate(savedState);
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800292
293 // Initialize fragment constants from resources
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800294 final Resources res = getResources();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800295 TIMESTAMP_UPDATE_INTERVAL = res.getInteger(R.integer.timestamp_update_interval);
296 mUpdateTimestampsRunnable = new Runnable(){
297 @Override
298 public void run() {
299 mListView.invalidateViews();
300 mHandler.postDelayed(mUpdateTimestampsRunnable, TIMESTAMP_UPDATE_INTERVAL);
301 }
302 };
303
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800304 // Get the context from the arguments
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800305 final Bundle args = getArguments();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800306 mViewContext = ConversationListContext.forBundle(args.getBundle(CONVERSATION_LIST_KEY));
Mindy Pereira3982e232012-02-29 15:00:34 -0800307 mAccount = mViewContext.account;
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700308 // TODO(mindyp): do we want this as a setting?
Mindy Pereira4e812f42012-07-30 16:52:49 -0700309 mSwipeAction = mAccount.supportsCapability(AccountCapabilities.ARCHIVE) ?
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700310 R.id.archive : R.id.delete;
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800311 if (savedState != null) {
312 mListSavedState = savedState.getParcelable(LIST_STATE_KEY);
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800313 }
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800314 setRetainInstance(true);
315 }
316
317 @Override
318 public View onCreateView(LayoutInflater inflater,
319 ViewGroup container, Bundle savedInstanceState) {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800320 View rootView = inflater.inflate(R.layout.conversation_list, null);
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700321 mListView = (SwipeableListView) rootView.findViewById(android.R.id.list);
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800322 mListView.setHeaderDividersEnabled(false);
Mindy Pereira14f64ec2012-08-14 17:14:02 -0700323 mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800324 mListView.setOnItemLongClickListener(this);
Mindy Pereira4e812f42012-07-30 16:52:49 -0700325 mListView.enableSwipe(mAccount.supportsCapability(AccountCapabilities.UNDO));
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800326 // Note - we manually save/restore the listview state.
327 mListView.setSaveEnabled(false);
Marc Blank2af0cf32012-03-01 19:24:13 -0800328
Mindy Pereira967ede62012-03-22 09:29:09 -0700329 ConversationCursor conversationListCursor = getConversationListCursor();
Marc Blank2af0cf32012-03-01 19:24:13 -0800330 // Belt and suspenders here; make sure we do any necessary sync of the ConversationCursor
Mindy Pereira967ede62012-03-22 09:29:09 -0700331 if (conversationListCursor != null && conversationListCursor.isRefreshReady()) {
332 conversationListCursor.sync();
Marc Blank2af0cf32012-03-01 19:24:13 -0800333 }
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800334 return rootView;
335 }
336
337 @Override
338 public void onDestroyView() {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800339 mListSavedState = mListView.onSaveInstanceState();
340
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700341 // Clear the list's adapter
342 mListAdapter.destroy();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800343 mListView.setAdapter(null);
344
Vikram Aggarwald7a12cd2012-02-03 09:36:20 -0800345 mActivity.unsetViewModeListener(this);
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800346 if (!mActivity.isChangingConfigurations()) {
347 mActivity.getLoaderManager().destroyLoader(mViewContext.hashCode());
348 }
Mindy Pereira71a8f292012-07-26 16:30:48 -0700349 if (mFolderObserver != null) {
Andy Huang7591d2f2012-07-26 16:48:06 -0700350 mActivity.getFolderController().unregisterFolderObserver(mFolderObserver);
Mindy Pereira71a8f292012-07-26 16:30:48 -0700351 mFolderObserver = null;
352 }
Mindy Pereira70a70c92012-08-02 08:39:45 -0700353 if (mConversationListStatusObserver != null) {
354 mActivity.getConversationUpdater().unregisterConversationListObserver(
355 mConversationListStatusObserver);
356 mConversationListStatusObserver = null;
357 }
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700358 mAccountObserver.unregisterAndDestroy();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800359 super.onDestroyView();
360 }
361
Vikram Aggarwal9730ea02012-08-02 12:46:19 -0700362 /**
363 * There are three binary variables, which determine what we do with a message.
364 * checkbEnabled: Whether check boxes are enabled or not (forced true on tablet)
365 * cabModeOn: Whether CAB mode is currently on or not.
366 * pressType: long or short tap
367 * (There is a third possibility: phone or tablet, but they have <em>identical</em> behavior)
368 * The matrix of possibilities is:
Vikram Aggarwal4f1cc0b2012-08-02 15:16:30 -0700369 * <p>Long tap:
Vikram Aggarwal9730ea02012-08-02 12:46:19 -0700370 * Always toggle selection of conversation. If CAB mode is not started, then start it.
371 * <pre>
372 * | Checkboxes | No Checkboxes
373 * ----------+------------+---------------
374 * CAB mode | Select | Select
375 * List mode | Select | Select
376 *
Vikram Aggarwal9730ea02012-08-02 12:46:19 -0700377 * </pre>
378 * Reference: http://b/issue?id=6392199
379 * <p>
380 * {@inheritDoc}
381 */
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800382 @Override
383 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Mindy Pereira1e231a62012-03-23 12:42:15 -0700384 // Ignore anything that is not a conversation item. Could be a footer.
385 if (!(view instanceof ConversationItemView)) {
386 return true;
387 }
Vikram Aggarwal4f1cc0b2012-08-02 15:16:30 -0700388 ((ConversationItemView) view).toggleCheckMark();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800389 return true;
390 }
391
Vikram Aggarwal4f1cc0b2012-08-02 15:16:30 -0700392 /**
393 * See the comment for {@link #onItemLongClick(AdapterView, View, int, long)}.
394 * <p>Short tap behavior:
395 * <pre>
396 * | Checkboxes | No Checkboxes
397 * ----------+------------+---------------
398 * CAB mode | Peek | Select
399 * List mode | Peek | Peek
400 * </pre>
401 * Reference: http://b/issue?id=6392199
402 * <p>
403 * {@inheritDoc}
404 */
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800405 @Override
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700406 public void onListItemClick(ListView l, View view, int position, long id) {
Mindy Pereira1e231a62012-03-23 12:42:15 -0700407 // Ignore anything that is not a conversation item. Could be a footer.
408 if (!(view instanceof ConversationItemView)) {
409 return;
410 }
Vikram Aggarwal4f1cc0b2012-08-02 15:16:30 -0700411 if (mAccount.settings.hideCheckboxes && !mSelectedSet.isEmpty()) {
412 ((ConversationItemView) view).toggleCheckMark();
413 } else {
414 viewConversation(position);
415 }
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800416 }
417
418 @Override
419 public void onPause() {
420 super.onPause();
421 }
422
423 @Override
424 public void onSaveInstanceState(Bundle outState) {
425 super.onSaveInstanceState(outState);
426 if (mListView != null) {
427 outState.putParcelable(LIST_STATE_KEY, mListView.onSaveInstanceState());
428 }
429 }
430
431 @Override
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800432 public void onStart() {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800433 super.onStart();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800434 mHandler.postDelayed(mUpdateTimestampsRunnable, TIMESTAMP_UPDATE_INTERVAL);
435 }
436
437 @Override
438 public void onStop() {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800439 super.onStop();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800440 mHandler.removeCallbacks(mUpdateTimestampsRunnable);
441 }
442
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800443 @Override
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800444 public void onViewModeChanged(int newMode) {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800445 // Change the divider based on view mode.
Vikram Aggarwalfa131a22012-02-02 13:56:22 -0800446 if (mTabletDevice) {
447 if (newMode == ViewMode.CONVERSATION) {
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800448 mListView.setBackgroundResource(R.drawable.panel_conversation_leftstroke);
Mindy Pereirade1c8592012-08-10 14:54:31 -0700449 } else if (newMode == ViewMode.CONVERSATION_LIST
450 || newMode == ViewMode.SEARCH_RESULTS_LIST) {
Mindy Pereirab5901be2012-08-09 17:21:53 -0700451 // There are no selected conversations when in conversation
452 // list mode.
Mindy Pereira62c5af82012-03-05 10:08:38 -0800453 mListView.clearChoices();
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800454 mListView.setBackgroundDrawable(null);
455 }
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800456 } else {
457 mListView.setBackgroundDrawable(null);
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800458 }
Mindy Pereirab5901be2012-08-09 17:21:53 -0700459 if (mFooterView != null) {
460 mFooterView.onViewModeChanged(newMode);
461 }
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800462 }
Mindy Pereirab5901be2012-08-09 17:21:53 -0700463
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800464 /**
465 * Handles a request to show a new conversation list, either from a search query or for viewing
Mindy Pereira30fd47b2012-03-09 09:24:00 -0800466 * a folder. This will initiate a data load, and hence must be called on the UI thread.
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800467 */
468 private void showList() {
Vikram Aggarwal80aeac52012-02-07 15:27:20 -0800469 mListView.setEmptyView(null);
Andy Huang7591d2f2012-07-26 16:48:06 -0700470 onFolderUpdated(mActivity.getFolderController().getFolder());
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800471 }
472
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800473 /**
474 * View the message at the given position.
475 * @param position
476 */
Mindy Pereirafbe40192012-03-20 10:40:45 -0700477 protected void viewConversation(int position) {
Vikram Aggarwalc7694222012-04-23 13:37:01 -0700478 LogUtils.d(LOG_TAG, "ConversationListFragment.viewConversation(%d)", position);
Vikram Aggarwal3d7ca9d2012-05-11 14:40:36 -0700479 setSelected(position);
480 final ConversationCursor cursor = getConversationListCursor();
481 if (cursor != null && cursor.moveToPosition(position)) {
482 final Conversation conv = new Conversation(cursor);
483 conv.position = position;
484 mCallbacks.onConversationSelected(conv);
485 }
486 }
487
488 /**
489 * Sets the selected position (the highlighted conversation) to the position provided here.
490 * @param position
491 */
492 protected final void setSelected(int position) {
Andy Huang864e7582012-06-29 16:42:34 -0700493 mListView.smoothScrollToPosition(position);
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800494 }
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800495
Mindy Pereira967ede62012-03-22 09:29:09 -0700496 private ConversationCursor getConversationListCursor() {
497 return mCallbacks != null ? mCallbacks.getConversationListCursor() : null;
Mindy Pereirafe06bea2012-02-16 08:15:14 -0800498 }
499
500 /**
Vikram Aggarwal54452ae2012-03-13 15:29:00 -0700501 * Request a refresh of the list. No sync is carried out and none is promised.
502 */
503 public void requestListRefresh() {
504 mListAdapter.notifyDataSetChanged();
505 }
506
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700507 /**
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700508 * Change the UI to delete the conversations provided and then call the
509 * {@link DestructiveAction} provided here <b>after</b> the UI has been updated.
510 * @param conversations
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700511 * @param action
512 */
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700513 public void requestDelete(Collection<Conversation> conversations, DestructiveAction action) {
Mindy Pereiraacf60392012-04-06 09:11:00 -0700514 for (Conversation conv : conversations) {
515 conv.localDeleteOnUpdate = true;
516 }
517 // Delete the local delete items (all for now) and when done,
518 // update...
Vikram Aggarwal75daee52012-04-30 11:13:09 -0700519 mListAdapter.delete(conversations, action);
Mindy Pereiraacf60392012-04-06 09:11:00 -0700520 }
Mindy Pereiraa3911aa2012-03-09 13:26:26 -0800521
Mindy Pereira11dd5ef2012-03-10 15:10:18 -0800522 public void onFolderUpdated(Folder folder) {
Mindy Pereiradc8963b2012-03-28 17:51:05 -0700523 mFolder = folder;
Mindy Pereira06642fa2012-07-12 16:23:27 -0700524 setSwipeAction();
Mindy Pereira82cea482012-03-27 16:45:00 -0700525 if (mFolder == null) {
526 return;
527 }
Mindy Pereira4584a0d2012-03-13 14:42:14 -0700528 mListAdapter.setFolder(mFolder);
Mindy Pereira70a70c92012-08-02 08:39:45 -0700529 mFooterView.setFolder(mFolder);
530 if (mFolder.lastSyncResult != UIProvider.LastSyncResult.SUCCESS) {
531 mErrorListener.onError(mFolder, false);
532 }
Mindy Pereira12fe37a2012-08-15 10:02:57 -0700533 // Blow away conversation items cache.
534 ConversationItemViewModel.onFolderUpdated(mFolder);
Mindy Pereira70a70c92012-08-02 08:39:45 -0700535 }
536
537 public void onConversationListStatusUpdated() {
538 ConversationCursor cursor = getConversationListCursor();
Paul Westbrook573b9e62012-08-02 14:56:17 -0700539 final boolean showFooter = mFooterView.updateStatus(cursor);
Mindy Pereira70a70c92012-08-02 08:39:45 -0700540 Bundle extras = cursor.getExtras();
541 int error = extras.containsKey(UIProvider.CursorExtraKeys.EXTRA_ERROR) ?
542 extras.getInt(UIProvider.CursorExtraKeys.EXTRA_ERROR)
543 : UIProvider.LastSyncResult.SUCCESS;
544 if (error != UIProvider.LastSyncResult.SUCCESS) {
Mindy Pereira6681f6b2012-03-09 13:55:54 -0800545 // Check the status of the folder to see if we are done loading.
Mindy Pereiraa3911aa2012-03-09 13:26:26 -0800546 updateSearchResultHeader(mFolder != null ? mFolder.totalCount : 0);
Mindy Pereira70a70c92012-08-02 08:39:45 -0700547 }
Paul Westbrook573b9e62012-08-02 14:56:17 -0700548 mListAdapter.setFooterVisibility(showFooter);
Mindy Pereiraa3911aa2012-03-09 13:26:26 -0800549 }
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700550
Mindy Pereira06642fa2012-07-12 16:23:27 -0700551 private void setSwipeAction() {
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700552 int swipeSetting = Settings.getSwipeSetting(mAccount.settings);
Mindy Pereirae58222b2012-07-25 14:33:18 -0700553 if (swipeSetting == Swipe.DISABLED
Mindy Pereirab8073372012-08-09 14:00:10 -0700554 || !mAccount.supportsCapability(AccountCapabilities.UNDO)
555 || (mFolder != null && mFolder.isTrash())) {
Mindy Pereirae58222b2012-07-25 14:33:18 -0700556 mListView.enableSwipe(false);
557 } else {
558 int action;
Vikram Aggarwalae4ea992012-08-07 09:56:02 -0700559 if (ConversationListContext.isSearchResult(mViewContext)) {
Mindy Pereirae58222b2012-07-25 14:33:18 -0700560 action = R.id.delete;
Mindy Pereira4e812f42012-07-30 16:52:49 -0700561 } else if (mFolder == null) {
Mindy Pereira01f30502012-08-14 10:30:51 -0700562 action = R.id.remove_folder;
Mindy Pereira4e812f42012-07-30 16:52:49 -0700563 } else {
564 // We have enough information to respect user settings.
565 switch (swipeSetting) {
566 case Swipe.ARCHIVE:
567 if (mAccount.supportsCapability(AccountCapabilities.ARCHIVE)) {
568 if (mFolder.supportsCapability(FolderCapabilities.ARCHIVE)) {
569 action = R.id.archive;
570 break;
571 } else if (mFolder.supportsCapability
572 (FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)) {
Mindy Pereira01f30502012-08-14 10:30:51 -0700573 action = R.id.remove_folder;
Mindy Pereira4e812f42012-07-30 16:52:49 -0700574 break;
575 }
576 }
577 case Swipe.DELETE:
578 default:
579 action = R.id.delete;
580 break;
581 }
Mindy Pereirae58222b2012-07-25 14:33:18 -0700582 }
583 mListView.setSwipeAction(action);
584 }
Mindy Pereira06642fa2012-07-12 16:23:27 -0700585 mListView.setCurrentFolder(mFolder);
586 }
587
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700588 @Override
589 public void onSwipeComplete(Collection<Conversation> conversations) {
590 Context context = getActivity().getApplicationContext();
Paul Westbrookbf232c32012-04-18 03:17:41 -0700591 ConversationCursor cc = getConversationListCursor();
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700592 switch (mSwipeAction) {
593 case R.id.archive:
Paul Westbrookbf232c32012-04-18 03:17:41 -0700594 cc.archive(context, conversations);
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700595 break;
596 case R.id.delete:
Paul Westbrookbf232c32012-04-18 03:17:41 -0700597 cc.delete(context, conversations);
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700598 break;
599 }
Mindy Pereira175cd3d2012-03-16 11:09:18 -0700600 mListAdapter.notifyDataSetChanged();
Mindy Pereira866d3192012-03-26 09:50:00 -0700601 if (!mActivity.getSelectedSet().isEmpty()) {
602 mActivity.getSelectedSet().clear();
603 }
Mindy Pereirad33674992012-06-25 16:26:30 -0700604 mActivity.onUndoAvailable(new ToastBarOperation(conversations.size(), mSwipeAction,
605 ToastBarOperation.UNDO));
Mindy Pereiraf6a6b502012-03-15 15:24:26 -0700606 }
Mindy Pereira21ab4902012-03-19 18:48:03 -0700607
Mindy Pereira967ede62012-03-22 09:29:09 -0700608 public void onCursorUpdated() {
609 if (mListAdapter != null) {
610 mListAdapter.swapCursor(mCallbacks.getConversationListCursor());
611 }
612 onFolderUpdated(mFolder);
Mindy Pereira21ab4902012-03-19 18:48:03 -0700613 }
Mindy Pereira1e2573b2012-04-17 14:34:36 -0700614
Mindy Pereira8937bf12012-07-23 14:05:02 -0700615 public void commitDestructiveActions() {
616 if (mListView != null) {
617 mListView.commitDestructiveActions();
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700618
619 }
620 }
621
Vikram Aggarwalb9e1a352012-01-24 15:23:38 -0800622}