blob: 596b853f3333cca08a32031b128fa6169e3c3040 [file] [log] [blame]
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -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
Andy Huang30e2c242012-01-06 18:14:30 -080018package com.android.mail.browse;
Mindy Pereira6f92de62011-12-19 11:31:48 -080019
20import android.app.Activity;
Paul Westbrookf3196882012-02-09 16:00:49 -080021import android.app.LoaderManager;
Mindy Pereira015c2692012-01-05 15:01:18 -080022import android.content.ContentResolver;
Mindy Pereira6f92de62011-12-19 11:31:48 -080023import android.content.Context;
Paul Westbrookf3196882012-02-09 16:00:49 -080024import android.content.CursorLoader;
25import android.content.Loader;
Mindy Pereira015c2692012-01-05 15:01:18 -080026import android.database.Cursor;
27import android.net.Uri;
Mindy Pereira6f92de62011-12-19 11:31:48 -080028import android.os.Bundle;
Mindy Pereira015c2692012-01-05 15:01:18 -080029import android.view.LayoutInflater;
30import android.view.Menu;
31import android.view.MenuInflater;
32import android.view.MenuItem;
Mindy Pereira6f92de62011-12-19 11:31:48 -080033import android.view.View;
34import android.view.ViewGroup;
Mindy Pereira015c2692012-01-05 15:01:18 -080035import android.widget.AdapterView;
Mindy Pereira015c2692012-01-05 15:01:18 -080036import android.widget.SimpleCursorAdapter;
37import android.widget.Spinner;
38import android.widget.SpinnerAdapter;
39import android.widget.TextView;
40import android.widget.AdapterView.OnItemClickListener;
41import android.widget.AdapterView.OnItemSelectedListener;
Mindy Pereira6f92de62011-12-19 11:31:48 -080042
Andy Huang30e2c242012-01-06 18:14:30 -080043import com.android.mail.R;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080044import com.android.mail.browse.ConversationCursor.ConversationListener;
Andy Huang30e2c242012-01-06 18:14:30 -080045import com.android.mail.compose.ComposeActivity;
Mindy Pereira33fe9082012-01-09 16:24:30 -080046import com.android.mail.providers.Account;
Andy Huang30e2c242012-01-06 18:14:30 -080047import com.android.mail.providers.AccountCacheProvider;
Andy Huang732600e2012-01-10 17:47:17 -080048import com.android.mail.providers.Conversation;
Mindy Pereira4f166de2012-02-14 13:40:58 -080049import com.android.mail.providers.Folder;
Andy Huang30e2c242012-01-06 18:14:30 -080050import com.android.mail.providers.UIProvider;
Marc Blank8d69d4e2012-01-25 12:04:28 -080051import com.android.mail.providers.UIProvider.ConversationColumns;
Marc Blank4015c182012-01-31 12:38:36 -080052import com.android.mail.ui.ActionCompleteListener;
Mindy Pereira9879d4c2012-01-31 09:34:18 -080053import com.android.mail.ui.AnimatedAdapter;
54import com.android.mail.ui.AnimatedListView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080055import com.android.mail.ui.ConversationSelectionSet;
56import com.android.mail.ui.ConversationSetObserver;
Vikram Aggarwald247dc92012-02-10 15:49:01 -080057import com.android.mail.ui.RestrictedActivity;
Mindy Pereira96b5c352012-02-01 11:33:40 -080058import com.android.mail.ui.UndoBarView;
Mindy Pereira6f92de62011-12-19 11:31:48 -080059
Marc Blank97bca7b2012-01-24 11:17:00 -080060import java.util.ArrayList;
61
Mindy Pereira015c2692012-01-05 15:01:18 -080062public class ConversationListActivity extends Activity implements OnItemSelectedListener,
Paul Westbrookf3196882012-02-09 16:00:49 -080063 OnItemClickListener, ConversationSetObserver, ConversationListener, ActionCompleteListener,
Vikram Aggarwald247dc92012-02-10 15:49:01 -080064 LoaderManager.LoaderCallbacks<Cursor>, RestrictedActivity {
Mindy Pereira6f92de62011-12-19 11:31:48 -080065
Mindy Pereira9879d4c2012-01-31 09:34:18 -080066 private AnimatedListView mListView;
67 private AnimatedAdapter mListAdapter;
Marc Blank48eba7a2012-01-27 16:16:19 -080068 private ConversationCursor mConversationListCursor;
Mindy Pereira015c2692012-01-05 15:01:18 -080069 private Spinner mAccountsSpinner;
70 private AccountsSpinnerAdapter mAccountsAdapter;
71 private ContentResolver mResolver;
Mindy Pereira33fe9082012-01-09 16:24:30 -080072 private Account mSelectedAccount;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080073 /** The selected conversations. */
74 protected ConversationSelectionSet mBatchConversations = new ConversationSelectionSet();
75 private SelectedConversationsActionMenu mSelectedConversationsActionMenu;
Mindy Pereira96b5c352012-02-01 11:33:40 -080076 private UndoBarView mUndoView;
Mindy Pereira4f166de2012-02-14 13:40:58 -080077 private Folder mSelectedFolder;
Mindy Pereira6f92de62011-12-19 11:31:48 -080078
Andy Huang98b4e902012-01-06 18:00:07 -080079 @Override
Mindy Pereira6f92de62011-12-19 11:31:48 -080080 public void onCreate(Bundle savedInstanceState) {
81 super.onCreate(savedInstanceState);
Mindy Pereira0944e5e2012-01-03 11:14:58 -080082 setContentView(R.layout.conversation_list_activity);
Mindy Pereira9879d4c2012-01-31 09:34:18 -080083 mListView = (AnimatedListView) findViewById(R.id.browse_list);
Mindy Pereira015c2692012-01-05 15:01:18 -080084 mListView.setOnItemClickListener(this);
85 mAccountsSpinner = (Spinner) findViewById(R.id.accounts_spinner);
86 mResolver = getContentResolver();
Paul Westbrookf3196882012-02-09 16:00:49 -080087 // TODO: determine if we need to create a fake cursor that contains the account list from
88 // last time that the application was run. If getting the list of accounts take a long
89 // time, this would prevent an empty spinner from being shown.
90 mAccountsAdapter = new AccountsSpinnerAdapter(this, null);
Mindy Pereira015c2692012-01-05 15:01:18 -080091 mAccountsSpinner.setAdapter(mAccountsAdapter);
92 mAccountsSpinner.setOnItemSelectedListener(this);
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080093 mBatchConversations.addObserver(this);
Mindy Pereira6f92de62011-12-19 11:31:48 -080094 }
Mindy Pereira67d75722012-02-13 10:46:21 -080095
Mindy Pereira015c2692012-01-05 15:01:18 -080096 @Override
Paul Westbrookf3196882012-02-09 16:00:49 -080097 public void onResume() {
98 super.onResume();
99 // Prepare the loader. Either re-connect with an existing one,
100 // or start a new one.
101 getLoaderManager().initLoader(0, null, this);
102 }
103
104 @Override
Mindy Pereira015c2692012-01-05 15:01:18 -0800105 public boolean onCreateOptionsMenu(Menu menu) {
106 super.onCreateOptionsMenu(menu);
107 MenuInflater inflater = getMenuInflater();
108 inflater.inflate(R.menu.conversation_list_menu, menu);
109 return true;
110 }
Mindy Pereira6f92de62011-12-19 11:31:48 -0800111
Andy Huang98b4e902012-01-06 18:00:07 -0800112 @Override
Mindy Pereira015c2692012-01-05 15:01:18 -0800113 public boolean onOptionsItemSelected(MenuItem item) {
114 boolean handled = true;
115 int id = item.getItemId();
116 switch (id) {
117 case R.id.compose:
118 ComposeActivity.compose(this, mSelectedAccount);
119 break;
Mindy Pereiraf6452882012-02-14 18:13:13 -0800120 case R.id.show_all_folders:
121 showAllFolders();
122 break;
Mindy Pereira015c2692012-01-05 15:01:18 -0800123 default:
124 handled = false;
125 break;
126 }
127 return handled;
128 }
129
Mindy Pereiraf6452882012-02-14 18:13:13 -0800130 private void showAllFolders() {
131 FoldersListActivity.launch(this, mSelectedAccount);
132 }
133
Paul Westbrookf3196882012-02-09 16:00:49 -0800134 @Override
135 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
136 // Currently this activity only creates a single loader (Account List)
137 return new CursorLoader(this, AccountCacheProvider.getAccountsUri(),
138 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
139 }
140
141 @Override
142 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
143 // Set the new data in the adapter.
144 mAccountsAdapter.changeCursor(data);
145 }
146
147 @Override
148 public void onLoaderReset(Loader<Cursor> loader) {
149 // Clear the data in the adapter.
150 mAccountsAdapter.changeCursor(null);
151 }
152
Mindy Pereira015c2692012-01-05 15:01:18 -0800153 class AccountsSpinnerAdapter extends SimpleCursorAdapter implements SpinnerAdapter {
154
155 private LayoutInflater mLayoutInflater;
156
157 public AccountsSpinnerAdapter(Context context, Cursor cursor) {
158 super(context, android.R.layout.simple_dropdown_item_1line, cursor,
159 UIProvider.ACCOUNTS_PROJECTION, null, 0);
160 mLayoutInflater = LayoutInflater.from(context);
Mindy Pereira6f92de62011-12-19 11:31:48 -0800161 }
162
Mindy Pereira015c2692012-01-05 15:01:18 -0800163 @Override
164 public View getDropDownView(int position, View convertView, ViewGroup parent) {
165 return getView(position, convertView, parent);
166 }
167
Andy Huang98b4e902012-01-06 18:00:07 -0800168 @Override
Mindy Pereira015c2692012-01-05 15:01:18 -0800169 public View newView(Context context, Cursor cursor, ViewGroup parent) {
170 return mLayoutInflater.inflate(android.R.layout.simple_dropdown_item_1line, null);
171 }
172
173 @Override
174 public void bindView(View view, Context context, Cursor cursor) {
175 int accountNameCol = cursor.getColumnIndex(UIProvider.AccountColumns.NAME);
176 ((TextView) view.findViewById(android.R.id.text1)).setText(cursor
177 .getString(accountNameCol));
178 }
179 }
180
Mindy Pereira015c2692012-01-05 15:01:18 -0800181 @Override
182 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Marc Blankc8a99422012-01-19 14:27:47 -0800183 // Get an account and a folder list
Mindy Pereira015c2692012-01-05 15:01:18 -0800184 Uri foldersUri = null;
185 Cursor cursor = mAccountsAdapter.getCursor();
186 if (cursor != null && cursor.moveToPosition(position)) {
187 int uriCol = cursor.getColumnIndex(UIProvider.AccountColumns.FOLDER_LIST_URI);
188 foldersUri = Uri.parse(cursor.getString(uriCol));
Mindy Pereira33fe9082012-01-09 16:24:30 -0800189 mSelectedAccount = new Account(cursor);
Mindy Pereira015c2692012-01-05 15:01:18 -0800190 cursor.close();
191 }
192 Uri conversationListUri = null;
193 if (foldersUri != null) {
194 cursor = mResolver.query(foldersUri, UIProvider.FOLDERS_PROJECTION, null, null, null);
195 if (cursor != null) {
196 int uriCol = cursor.getColumnIndex(UIProvider.FolderColumns.CONVERSATION_LIST_URI);
197 cursor.moveToFirst();
Mindy Pereira4f166de2012-02-14 13:40:58 -0800198 mSelectedFolder = new Folder(cursor);
Mindy Pereira015c2692012-01-05 15:01:18 -0800199 conversationListUri = Uri.parse(cursor.getString(uriCol));
200 cursor.close();
201 }
202 }
Marc Blankc8a99422012-01-19 14:27:47 -0800203 // We need to have a conversation list here...
204 if (conversationListUri == null) {
205 throw new IllegalStateException("No conversation list for this account");
Mindy Pereira015c2692012-01-05 15:01:18 -0800206 }
Marc Blankc8a99422012-01-19 14:27:47 -0800207 // Create the cursor for the list using the update cache
Marc Blank3b2d9132012-02-05 12:21:10 -0800208 // Make this asynchronous
Marc Blank7ac01cc2012-02-08 09:19:36 -0800209 if (mConversationListCursor != null) {
210 mConversationListCursor.close();
211 }
Marc Blank48eba7a2012-01-27 16:16:19 -0800212 mConversationListCursor =
Marc Blankc43bc0a2012-02-02 11:25:18 -0800213 ConversationCursor.create(this, UIProvider.ConversationColumns.URI,
Marc Blank48eba7a2012-01-27 16:16:19 -0800214 conversationListUri, UIProvider.CONVERSATION_PROJECTION, null, null, null);
Mindy Pereira9879d4c2012-01-31 09:34:18 -0800215 mListAdapter = new AnimatedAdapter(this, position, mConversationListCursor,
216 mBatchConversations, mSelectedAccount);
Mindy Pereira015c2692012-01-05 15:01:18 -0800217 mListView.setAdapter(mListAdapter);
Marc Blankb600a832012-02-16 09:20:18 -0800218 mConversationListCursor.addListener(this);
Mindy Pereira015c2692012-01-05 15:01:18 -0800219 }
220
221 @Override
222 public void onNothingSelected(AdapterView<?> parent) {
223 }
224
225 @Override
226 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Andy Huang732600e2012-01-10 17:47:17 -0800227 Conversation conv = ((ConversationItemView) view).getConversation();
228 ConversationViewActivity.viewConversation(this, conv, mSelectedAccount);
Marc Blank8d69d4e2012-01-25 12:04:28 -0800229 // Quick and dirty flag change
230 if (!conv.read) {
231 conv.read = true;
232 conv.updateBoolean(this, ConversationColumns.READ, true);
Marc Blank03bbaad2012-01-31 11:27:16 -0800233 // For now, update the display
234 mListAdapter.notifyDataSetChanged();
Marc Blank8d69d4e2012-01-25 12:04:28 -0800235 }
Mindy Pereira6f92de62011-12-19 11:31:48 -0800236 }
Marc Blankc8a99422012-01-19 14:27:47 -0800237
Marc Blankc8a99422012-01-19 14:27:47 -0800238 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800239 public void onSetEmpty() {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800240 mSelectedConversationsActionMenu = null;
241 }
242
243 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800244 public void onSetPopulated(ConversationSelectionSet set) {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800245 mSelectedConversationsActionMenu = new SelectedConversationsActionMenu(this,
Mindy Pereira4f166de2012-02-14 13:40:58 -0800246 mBatchConversations, mListAdapter, this, mSelectedAccount, mSelectedFolder);
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800247 mSelectedConversationsActionMenu.activate();
248 }
249
250 @Override
251 public void onSetChanged(ConversationSelectionSet set) {
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800252 // Do nothing, we don't care about changes to the selection set.
Marc Blankc8a99422012-01-19 14:27:47 -0800253 }
Marc Blank97bca7b2012-01-24 11:17:00 -0800254
255 // ConversationListener implementation
Marc Blank48eba7a2012-01-27 16:16:19 -0800256 // Underlying provider updates, etc.
257
258 /**
259 * Called when there is new data at the underlying provider
260 * refresh() here causes the new data to be retrieved asynchronously
261 * NOTE: The UI needn't take any action immediately (i.e. it might wait until a more
262 * convenient time to get the update from the provider)
263 */
Marc Blank8d69d4e2012-01-25 12:04:28 -0800264 @Override
Marc Blank48eba7a2012-01-27 16:16:19 -0800265 public void onRefreshRequired() {
266 // Refresh the query in the background
267 mConversationListCursor.refresh();
268 }
269
270 /**
Marc Blank4e25c942012-02-02 19:41:14 -0800271 * Complete the cursor refresh process by syncing to the underlying cursor and redrawing
Marc Blank4015c182012-01-31 12:38:36 -0800272 */
273 private void finishRefresh() {
274 // Swap cursors
Marc Blank4e25c942012-02-02 19:41:14 -0800275 mConversationListCursor.sync();
Marc Blank4015c182012-01-31 12:38:36 -0800276 // Redraw with new data
277 mListAdapter.notifyDataSetChanged();
278 }
279
280 /**
Marc Blank48eba7a2012-01-27 16:16:19 -0800281 * Called when new data from the underlying provider is ready for use
282 * swapCursors() causes the cursor to reflect the refreshed data
283 * notifyDataSetChanged() causes the list to redraw
284 * NOTE: The UI needn't take any action immediately if it's otherwise engaged (animating, for
285 * example)
286 */
287 @Override
288 public void onRefreshReady() {
Marc Blank4015c182012-01-31 12:38:36 -0800289 ArrayList<Integer> deletedRows = mConversationListCursor.getRefreshDeletions();
290 // If we have any deletions from the server, animate them away
291 if (!deletedRows.isEmpty()) {
Mindy Pereirafac92d72012-02-01 10:58:33 -0800292 mListAdapter.delete(deletedRows);
Marc Blank4015c182012-01-31 12:38:36 -0800293 } else {
294 finishRefresh();
295 }
296 }
297
298 @Override
299 public void onActionComplete() {
Mindy Pereira96b5c352012-02-01 11:33:40 -0800300 showUndo();
301 }
302
303 private void showUndo() {
304 mUndoView = (UndoBarView)findViewById(R.id.undo_view);
Marc Blank3b2d9132012-02-05 12:21:10 -0800305 mUndoView.show(true, this, "undo", mSelectedAccount, mListAdapter);
Marc Blank97bca7b2012-01-24 11:17:00 -0800306 }
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800307
Vikram Aggarwalcd8bf0a2012-02-14 13:13:18 -0800308 @Override
309 public Context getActivityContext() {
310 return this;
311 }
Mindy Pereira4f166de2012-02-14 13:40:58 -0800312}