blob: 4152638d975a86715a265e5c04a773270ece0b8e [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;
Andy Huang30e2c242012-01-06 18:14:30 -080049import com.android.mail.providers.UIProvider;
Marc Blank8d69d4e2012-01-25 12:04:28 -080050import com.android.mail.providers.UIProvider.ConversationColumns;
Marc Blank4015c182012-01-31 12:38:36 -080051import com.android.mail.ui.ActionCompleteListener;
Mindy Pereira9879d4c2012-01-31 09:34:18 -080052import com.android.mail.ui.AnimatedAdapter;
53import com.android.mail.ui.AnimatedListView;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080054import com.android.mail.ui.ConversationSelectionSet;
55import com.android.mail.ui.ConversationSetObserver;
Vikram Aggarwald247dc92012-02-10 15:49:01 -080056import com.android.mail.ui.RestrictedActivity;
Mindy Pereira96b5c352012-02-01 11:33:40 -080057import com.android.mail.ui.UndoBarView;
Mindy Pereira6f92de62011-12-19 11:31:48 -080058
Marc Blank97bca7b2012-01-24 11:17:00 -080059import java.util.ArrayList;
60
Mindy Pereira015c2692012-01-05 15:01:18 -080061public class ConversationListActivity extends Activity implements OnItemSelectedListener,
Paul Westbrookf3196882012-02-09 16:00:49 -080062 OnItemClickListener, ConversationSetObserver, ConversationListener, ActionCompleteListener,
Vikram Aggarwald247dc92012-02-10 15:49:01 -080063 LoaderManager.LoaderCallbacks<Cursor>, RestrictedActivity {
Mindy Pereira6f92de62011-12-19 11:31:48 -080064
Mindy Pereira9879d4c2012-01-31 09:34:18 -080065 private AnimatedListView mListView;
66 private AnimatedAdapter mListAdapter;
Marc Blank48eba7a2012-01-27 16:16:19 -080067 private ConversationCursor mConversationListCursor;
Mindy Pereira015c2692012-01-05 15:01:18 -080068 private Spinner mAccountsSpinner;
69 private AccountsSpinnerAdapter mAccountsAdapter;
70 private ContentResolver mResolver;
Mindy Pereira33fe9082012-01-09 16:24:30 -080071 private Account mSelectedAccount;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080072 /** The selected conversations. */
73 protected ConversationSelectionSet mBatchConversations = new ConversationSelectionSet();
74 private SelectedConversationsActionMenu mSelectedConversationsActionMenu;
Mindy Pereira96b5c352012-02-01 11:33:40 -080075 private UndoBarView mUndoView;
Mindy Pereira6f92de62011-12-19 11:31:48 -080076
Andy Huang98b4e902012-01-06 18:00:07 -080077 @Override
Mindy Pereira6f92de62011-12-19 11:31:48 -080078 public void onCreate(Bundle savedInstanceState) {
79 super.onCreate(savedInstanceState);
Mindy Pereira0944e5e2012-01-03 11:14:58 -080080 setContentView(R.layout.conversation_list_activity);
Mindy Pereira9879d4c2012-01-31 09:34:18 -080081 mListView = (AnimatedListView) findViewById(R.id.browse_list);
Mindy Pereira015c2692012-01-05 15:01:18 -080082 mListView.setOnItemClickListener(this);
83 mAccountsSpinner = (Spinner) findViewById(R.id.accounts_spinner);
84 mResolver = getContentResolver();
Paul Westbrookf3196882012-02-09 16:00:49 -080085 // TODO: determine if we need to create a fake cursor that contains the account list from
86 // last time that the application was run. If getting the list of accounts take a long
87 // time, this would prevent an empty spinner from being shown.
88 mAccountsAdapter = new AccountsSpinnerAdapter(this, null);
Mindy Pereira015c2692012-01-05 15:01:18 -080089 mAccountsSpinner.setAdapter(mAccountsAdapter);
90 mAccountsSpinner.setOnItemSelectedListener(this);
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080091 mBatchConversations.addObserver(this);
Mindy Pereira6f92de62011-12-19 11:31:48 -080092 }
Mindy Pereira67d75722012-02-13 10:46:21 -080093
Mindy Pereira015c2692012-01-05 15:01:18 -080094 @Override
Paul Westbrookf3196882012-02-09 16:00:49 -080095 public void onResume() {
96 super.onResume();
97 // Prepare the loader. Either re-connect with an existing one,
98 // or start a new one.
99 getLoaderManager().initLoader(0, null, this);
100 }
101
102 @Override
Mindy Pereira015c2692012-01-05 15:01:18 -0800103 public boolean onCreateOptionsMenu(Menu menu) {
104 super.onCreateOptionsMenu(menu);
105 MenuInflater inflater = getMenuInflater();
106 inflater.inflate(R.menu.conversation_list_menu, menu);
107 return true;
108 }
Mindy Pereira6f92de62011-12-19 11:31:48 -0800109
Andy Huang98b4e902012-01-06 18:00:07 -0800110 @Override
Mindy Pereira015c2692012-01-05 15:01:18 -0800111 public boolean onOptionsItemSelected(MenuItem item) {
112 boolean handled = true;
113 int id = item.getItemId();
114 switch (id) {
115 case R.id.compose:
116 ComposeActivity.compose(this, mSelectedAccount);
117 break;
118 default:
119 handled = false;
120 break;
121 }
122 return handled;
123 }
124
Paul Westbrookf3196882012-02-09 16:00:49 -0800125 @Override
126 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
127 // Currently this activity only creates a single loader (Account List)
128 return new CursorLoader(this, AccountCacheProvider.getAccountsUri(),
129 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
130 }
131
132 @Override
133 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
134 // Set the new data in the adapter.
135 mAccountsAdapter.changeCursor(data);
136 }
137
138 @Override
139 public void onLoaderReset(Loader<Cursor> loader) {
140 // Clear the data in the adapter.
141 mAccountsAdapter.changeCursor(null);
142 }
143
Mindy Pereira015c2692012-01-05 15:01:18 -0800144 class AccountsSpinnerAdapter extends SimpleCursorAdapter implements SpinnerAdapter {
145
146 private LayoutInflater mLayoutInflater;
147
148 public AccountsSpinnerAdapter(Context context, Cursor cursor) {
149 super(context, android.R.layout.simple_dropdown_item_1line, cursor,
150 UIProvider.ACCOUNTS_PROJECTION, null, 0);
151 mLayoutInflater = LayoutInflater.from(context);
Mindy Pereira6f92de62011-12-19 11:31:48 -0800152 }
153
Mindy Pereira015c2692012-01-05 15:01:18 -0800154 @Override
155 public View getDropDownView(int position, View convertView, ViewGroup parent) {
156 return getView(position, convertView, parent);
157 }
158
Andy Huang98b4e902012-01-06 18:00:07 -0800159 @Override
Mindy Pereira015c2692012-01-05 15:01:18 -0800160 public View newView(Context context, Cursor cursor, ViewGroup parent) {
161 return mLayoutInflater.inflate(android.R.layout.simple_dropdown_item_1line, null);
162 }
163
164 @Override
165 public void bindView(View view, Context context, Cursor cursor) {
166 int accountNameCol = cursor.getColumnIndex(UIProvider.AccountColumns.NAME);
167 ((TextView) view.findViewById(android.R.id.text1)).setText(cursor
168 .getString(accountNameCol));
169 }
170 }
171
Mindy Pereira015c2692012-01-05 15:01:18 -0800172 @Override
173 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Marc Blankc8a99422012-01-19 14:27:47 -0800174 // Get an account and a folder list
Mindy Pereira015c2692012-01-05 15:01:18 -0800175 Uri foldersUri = null;
176 Cursor cursor = mAccountsAdapter.getCursor();
177 if (cursor != null && cursor.moveToPosition(position)) {
178 int uriCol = cursor.getColumnIndex(UIProvider.AccountColumns.FOLDER_LIST_URI);
179 foldersUri = Uri.parse(cursor.getString(uriCol));
Mindy Pereira33fe9082012-01-09 16:24:30 -0800180 mSelectedAccount = new Account(cursor);
Mindy Pereira015c2692012-01-05 15:01:18 -0800181 cursor.close();
182 }
183 Uri conversationListUri = null;
184 if (foldersUri != null) {
185 cursor = mResolver.query(foldersUri, UIProvider.FOLDERS_PROJECTION, null, null, null);
186 if (cursor != null) {
187 int uriCol = cursor.getColumnIndex(UIProvider.FolderColumns.CONVERSATION_LIST_URI);
188 cursor.moveToFirst();
189 conversationListUri = Uri.parse(cursor.getString(uriCol));
190 cursor.close();
191 }
192 }
Marc Blankc8a99422012-01-19 14:27:47 -0800193 // We need to have a conversation list here...
194 if (conversationListUri == null) {
195 throw new IllegalStateException("No conversation list for this account");
Mindy Pereira015c2692012-01-05 15:01:18 -0800196 }
Marc Blankc8a99422012-01-19 14:27:47 -0800197 // Create the cursor for the list using the update cache
Marc Blank3b2d9132012-02-05 12:21:10 -0800198 // Make this asynchronous
Marc Blank7ac01cc2012-02-08 09:19:36 -0800199 if (mConversationListCursor != null) {
200 mConversationListCursor.close();
201 }
Marc Blank48eba7a2012-01-27 16:16:19 -0800202 mConversationListCursor =
Marc Blankc43bc0a2012-02-02 11:25:18 -0800203 ConversationCursor.create(this, UIProvider.ConversationColumns.URI,
Marc Blank48eba7a2012-01-27 16:16:19 -0800204 conversationListUri, UIProvider.CONVERSATION_PROJECTION, null, null, null);
Mindy Pereira9879d4c2012-01-31 09:34:18 -0800205 mListAdapter = new AnimatedAdapter(this, position, mConversationListCursor,
206 mBatchConversations, mSelectedAccount);
Mindy Pereira015c2692012-01-05 15:01:18 -0800207 mListView.setAdapter(mListAdapter);
Marc Blank48eba7a2012-01-27 16:16:19 -0800208 mConversationListCursor.setListener(this);
Mindy Pereira015c2692012-01-05 15:01:18 -0800209 }
210
211 @Override
212 public void onNothingSelected(AdapterView<?> parent) {
213 }
214
215 @Override
216 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Andy Huang732600e2012-01-10 17:47:17 -0800217 Conversation conv = ((ConversationItemView) view).getConversation();
218 ConversationViewActivity.viewConversation(this, conv, mSelectedAccount);
Marc Blank8d69d4e2012-01-25 12:04:28 -0800219 // Quick and dirty flag change
220 if (!conv.read) {
221 conv.read = true;
222 conv.updateBoolean(this, ConversationColumns.READ, true);
Marc Blank03bbaad2012-01-31 11:27:16 -0800223 // For now, update the display
224 mListAdapter.notifyDataSetChanged();
Marc Blank8d69d4e2012-01-25 12:04:28 -0800225 }
Mindy Pereira6f92de62011-12-19 11:31:48 -0800226 }
Marc Blankc8a99422012-01-19 14:27:47 -0800227
Marc Blankc8a99422012-01-19 14:27:47 -0800228 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800229 public void onSetEmpty() {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800230 mSelectedConversationsActionMenu = null;
231 }
232
233 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800234 public void onSetPopulated(ConversationSelectionSet set) {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800235 mSelectedConversationsActionMenu = new SelectedConversationsActionMenu(this,
Mindy Pereira67d75722012-02-13 10:46:21 -0800236 mBatchConversations, mListAdapter, this, mSelectedAccount, this);
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800237 mSelectedConversationsActionMenu.activate();
238 }
239
240 @Override
241 public void onSetChanged(ConversationSelectionSet set) {
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800242 // Do nothing, we don't care about changes to the selection set.
Marc Blankc8a99422012-01-19 14:27:47 -0800243 }
Marc Blank97bca7b2012-01-24 11:17:00 -0800244
245 // ConversationListener implementation
Marc Blank48eba7a2012-01-27 16:16:19 -0800246 // Underlying provider updates, etc.
247
248 /**
249 * Called when there is new data at the underlying provider
250 * refresh() here causes the new data to be retrieved asynchronously
251 * NOTE: The UI needn't take any action immediately (i.e. it might wait until a more
252 * convenient time to get the update from the provider)
253 */
Marc Blank8d69d4e2012-01-25 12:04:28 -0800254 @Override
Marc Blank48eba7a2012-01-27 16:16:19 -0800255 public void onRefreshRequired() {
256 // Refresh the query in the background
257 mConversationListCursor.refresh();
258 }
259
260 /**
Marc Blank4e25c942012-02-02 19:41:14 -0800261 * Complete the cursor refresh process by syncing to the underlying cursor and redrawing
Marc Blank4015c182012-01-31 12:38:36 -0800262 */
263 private void finishRefresh() {
264 // Swap cursors
Marc Blank4e25c942012-02-02 19:41:14 -0800265 mConversationListCursor.sync();
Marc Blank4015c182012-01-31 12:38:36 -0800266 // Redraw with new data
267 mListAdapter.notifyDataSetChanged();
268 }
269
270 /**
Marc Blank48eba7a2012-01-27 16:16:19 -0800271 * Called when new data from the underlying provider is ready for use
272 * swapCursors() causes the cursor to reflect the refreshed data
273 * notifyDataSetChanged() causes the list to redraw
274 * NOTE: The UI needn't take any action immediately if it's otherwise engaged (animating, for
275 * example)
276 */
277 @Override
278 public void onRefreshReady() {
Marc Blank4015c182012-01-31 12:38:36 -0800279 ArrayList<Integer> deletedRows = mConversationListCursor.getRefreshDeletions();
280 // If we have any deletions from the server, animate them away
281 if (!deletedRows.isEmpty()) {
Mindy Pereirafac92d72012-02-01 10:58:33 -0800282 mListAdapter.delete(deletedRows);
Marc Blank4015c182012-01-31 12:38:36 -0800283 } else {
284 finishRefresh();
285 }
286 }
287
288 @Override
289 public void onActionComplete() {
Mindy Pereira96b5c352012-02-01 11:33:40 -0800290 showUndo();
291 }
292
293 private void showUndo() {
294 mUndoView = (UndoBarView)findViewById(R.id.undo_view);
Marc Blank3b2d9132012-02-05 12:21:10 -0800295 mUndoView.show(true, this, "undo", mSelectedAccount, mListAdapter);
Marc Blank97bca7b2012-01-24 11:17:00 -0800296 }
Vikram Aggarwald247dc92012-02-10 15:49:01 -0800297
Mindy Pereira6f92de62011-12-19 11:31:48 -0800298}