472 lines
18 KiB
Java
472 lines
18 KiB
Java
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.email.activity;
|
|
|
|
import com.android.email.Controller;
|
|
import com.android.email.ControllerResultUiThreadWrapper;
|
|
import com.android.email.R;
|
|
import com.android.email.Utility;
|
|
import com.android.email.provider.EmailContent;
|
|
import com.android.email.provider.EmailContent.Account;
|
|
import com.android.email.provider.EmailContent.Mailbox;
|
|
import com.android.email.provider.EmailContent.MailboxColumns;
|
|
import com.android.email.provider.EmailContent.Message;
|
|
import com.android.email.provider.EmailContent.MessageColumns;
|
|
import com.android.emailcommon.mail.MessagingException;
|
|
|
|
import android.app.Activity;
|
|
import android.app.ListFragment;
|
|
import android.content.Context;
|
|
import android.database.Cursor;
|
|
import android.database.MatrixCursor;
|
|
import android.database.MatrixCursor.RowBuilder;
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.view.ContextMenu;
|
|
import android.view.ContextMenu.ContextMenuInfo;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.widget.AdapterView;
|
|
import android.widget.AdapterView.OnItemClickListener;
|
|
import android.widget.CursorAdapter;
|
|
import android.widget.ListAdapter;
|
|
import android.widget.ListView;
|
|
|
|
public class AccountFolderListFragment extends ListFragment
|
|
implements OnItemClickListener, AccountsAdapter.Callback {
|
|
|
|
// UI Support
|
|
private Activity mActivity;
|
|
private ListView mListView;
|
|
private Callback mCallback;
|
|
|
|
// Tasks and Data
|
|
private AccountsAdapter mListAdapter;
|
|
private LoadAccountsTask mLoadAccountsTask;
|
|
private Controller.Result mControllerCallback;
|
|
|
|
private static final String FAVORITE_COUNT_SELECTION =
|
|
MessageColumns.FLAG_FAVORITE + "= 1";
|
|
private static final String MAILBOX_TYPE_SELECTION =
|
|
MailboxColumns.TYPE + " =?";
|
|
private static final String MAILBOX_ID_SELECTION =
|
|
MessageColumns.MAILBOX_KEY + " =?";
|
|
private static final String[] MAILBOX_SUM_OF_UNREAD_COUNT_PROJECTION = new String [] {
|
|
"sum(" + MailboxColumns.UNREAD_COUNT + ")"
|
|
};
|
|
|
|
/**
|
|
* Callback interface that owning activities must implement
|
|
*/
|
|
public interface Callback {
|
|
/** Called when the user clicks on a specific account */
|
|
public void onOpenAccount(long accountId);
|
|
/** Called when the user clicks on a specific (currently, only magic mailbox) */
|
|
public void onOpenMailbox(long mailboxId);
|
|
/** Called when the user clicks to open the mailbox list for a specific account */
|
|
public void onOpenMailboxes(long accountId);
|
|
/** Begin composing a message in a specific account, or -1 for the default account */
|
|
public void onCompose(long accountId);
|
|
/** Begin refreshing a specific account, or -1 for all accounts */
|
|
public void onRefresh(long accountId);
|
|
/** Begin edit settings for a specific account */
|
|
public void onEditAccount(long accountId);
|
|
/** Delete a specific account */
|
|
public void onDeleteAccount(long accountId);
|
|
}
|
|
|
|
/**
|
|
* Called to do initial creation of a fragment. This is called after
|
|
* {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
|
|
*/
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
}
|
|
|
|
/**
|
|
* Called by activity during onCreate() to bind additional information
|
|
* @param callback if non-null, UI clicks (e.g. refresh or open) will be delivered here
|
|
*/
|
|
public void bindActivityInfo(Callback callback) {
|
|
mCallback = callback;
|
|
}
|
|
|
|
/**
|
|
* Called when the fragment is instantiated, but not yet displayed.
|
|
*/
|
|
@Override
|
|
public void onActivityCreated(Bundle savedInstanceState) {
|
|
super.onActivityCreated(savedInstanceState);
|
|
mActivity = getActivity();
|
|
|
|
mListView = getListView();
|
|
mListView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);
|
|
mListView.setOnItemClickListener(this);
|
|
mListView.setLongClickable(true);
|
|
registerForContextMenu(mListView);
|
|
|
|
mControllerCallback = new ControllerResultUiThreadWrapper<ControllerResults>(
|
|
new Handler(), new ControllerResults());
|
|
}
|
|
|
|
/**
|
|
* Called when the Fragment is visible to the user.
|
|
*/
|
|
@Override
|
|
public void onStart() {
|
|
super.onStart();
|
|
}
|
|
|
|
/**
|
|
* Called when the fragment is visible to the user and actively running.
|
|
*/
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
Controller.getInstance(mActivity).addResultCallback(mControllerCallback);
|
|
updateAccounts();
|
|
}
|
|
|
|
/**
|
|
* Called when the fragment is no longer displayed
|
|
*/
|
|
@Override
|
|
public void onPause() {
|
|
super.onPause();
|
|
Controller.getInstance(mActivity).removeResultCallback(mControllerCallback);
|
|
}
|
|
|
|
/**
|
|
* Called when the Fragment is no longer started.
|
|
*/
|
|
@Override
|
|
public void onStop() {
|
|
super.onStop();
|
|
Utility.cancelTaskInterrupt(mLoadAccountsTask);
|
|
mLoadAccountsTask = null;
|
|
}
|
|
|
|
/**
|
|
* Called when the fragment is no longer in use.
|
|
*/
|
|
@Override
|
|
public void onDestroy() {
|
|
super.onDestroy();
|
|
if (mListAdapter != null) {
|
|
mListAdapter.changeCursor(null);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info) {
|
|
super.onCreateContextMenu(menu, v, info);
|
|
AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) info;
|
|
if (mListAdapter.isMailbox(menuInfo.position)) {
|
|
Cursor c = (Cursor) mListView.getItemAtPosition(menuInfo.position);
|
|
String displayName = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN);
|
|
menu.setHeaderTitle(displayName);
|
|
mActivity.getMenuInflater()
|
|
.inflate(R.menu.account_folder_list_smart_folder_context, menu);
|
|
} else if (mListAdapter.isAccount(menuInfo.position)) {
|
|
Cursor c = (Cursor) mListView.getItemAtPosition(menuInfo.position);
|
|
String accountName = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN);
|
|
menu.setHeaderTitle(accountName);
|
|
mActivity.getMenuInflater().inflate(R.menu.account_folder_list_context, menu);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onContextItemSelected(MenuItem item) {
|
|
AdapterView.AdapterContextMenuInfo menuInfo =
|
|
(AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
|
|
|
|
// Drop the event if there's nowhere to send it (it's probably late-arriving)
|
|
if (mCallback == null) {
|
|
return false;
|
|
}
|
|
|
|
if (mListAdapter.isMailbox(menuInfo.position)) {
|
|
Cursor c = (Cursor) mListView.getItemAtPosition(menuInfo.position);
|
|
long id = c.getLong(AccountsAdapter.MAILBOX_COLUMN_ID);
|
|
switch (item.getItemId()) {
|
|
case R.id.open_folder:
|
|
mCallback.onOpenMailbox(id);
|
|
break;
|
|
case R.id.check_mail:
|
|
mCallback.onRefresh(-1);
|
|
break;
|
|
}
|
|
return false;
|
|
} else if (mListAdapter.isAccount(menuInfo.position)) {
|
|
Cursor c = (Cursor) mListView.getItemAtPosition(menuInfo.position);
|
|
long accountId = c.getLong(Account.CONTENT_ID_COLUMN);
|
|
switch (item.getItemId()) {
|
|
case R.id.open_folder:
|
|
mCallback.onOpenAccount(accountId);
|
|
break;
|
|
case R.id.compose:
|
|
mCallback.onCompose(accountId);
|
|
break;
|
|
case R.id.refresh_account:
|
|
mCallback.onRefresh(accountId);
|
|
break;
|
|
case R.id.edit_account:
|
|
mCallback.onEditAccount(accountId);
|
|
break;
|
|
case R.id.delete_account:
|
|
mCallback.onDeleteAccount(accountId);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Implements OnItemClickListener.onItemClick */
|
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
// Drop the event if there's nowhere to send it (it's probably late-arriving)
|
|
if (mCallback == null) {
|
|
return;
|
|
}
|
|
|
|
if (mListAdapter.isMailbox(position)) {
|
|
mCallback.onOpenMailbox(id);
|
|
} else if (mListAdapter.isAccount(position)) {
|
|
mCallback.onOpenAccount(id);
|
|
}
|
|
}
|
|
|
|
/* Implements AccountsAdapter.Controller */
|
|
public void onClickAccountFolders(long accountId) {
|
|
if (mCallback != null) {
|
|
mCallback.onOpenMailboxes(accountId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trigger accounts list reload
|
|
*/
|
|
private void updateAccounts() {
|
|
Utility.cancelTaskInterrupt(mLoadAccountsTask);
|
|
mLoadAccountsTask = (LoadAccountsTask) new LoadAccountsTask().execute();
|
|
}
|
|
|
|
/**
|
|
* Called by container to mark an account as "being deleted" (we quickly hide it)
|
|
*/
|
|
public void hideDeletingAccount(long accountId) {
|
|
mListAdapter.addOnDeletingAccount(accountId);
|
|
}
|
|
|
|
private static int getUnreadCountByMailboxType(Context context, int type) {
|
|
int count = 0;
|
|
Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI,
|
|
MAILBOX_SUM_OF_UNREAD_COUNT_PROJECTION,
|
|
MAILBOX_TYPE_SELECTION,
|
|
new String[] { String.valueOf(type) }, null);
|
|
|
|
try {
|
|
if (c.moveToFirst()) {
|
|
return c.getInt(0);
|
|
}
|
|
} finally {
|
|
c.close();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
private static int getCountByMailboxType(Context context, int type) {
|
|
int count = 0;
|
|
Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI,
|
|
EmailContent.ID_PROJECTION, MAILBOX_TYPE_SELECTION,
|
|
new String[] { String.valueOf(type) }, null);
|
|
|
|
try {
|
|
c.moveToPosition(-1);
|
|
while (c.moveToNext()) {
|
|
count += EmailContent.count(context, Message.CONTENT_URI,
|
|
MAILBOX_ID_SELECTION,
|
|
new String[] {
|
|
String.valueOf(c.getLong(EmailContent.ID_PROJECTION_COLUMN)) });
|
|
}
|
|
} finally {
|
|
c.close();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Build the group and child cursors that support the summary views (aka "at a glance").
|
|
*
|
|
* This is a placeholder implementation with significant problems that need to be addressed:
|
|
*
|
|
* TODO: We should only show summary mailboxes if they are non-empty. So there needs to be
|
|
* a more dynamic child-cursor here, probably listening for update notifications on a number
|
|
* of other internally-held queries such as count-of-inbox, count-of-unread, etc.
|
|
*
|
|
* TODO: This simple list is incomplete. For example, we probably want drafts, outbox, and
|
|
* (maybe) sent (again, these would be displayed only when non-empty).
|
|
*
|
|
* TODO: We need a way to count total unread in all inboxes (probably with some provider help)
|
|
*
|
|
* TODO: We need a way to count total # messages in all other summary boxes (probably with
|
|
* some provider help).
|
|
*
|
|
* TODO use narrower account projection (see LoadAccountsTask)
|
|
*/
|
|
private MatrixCursor getSummaryChildCursor() {
|
|
MatrixCursor childCursor = new MatrixCursor(AccountsAdapter.MAILBOX_PROJECTION);
|
|
int count;
|
|
RowBuilder row;
|
|
// TYPE_INBOX
|
|
count = getUnreadCountByMailboxType(mActivity, Mailbox.TYPE_INBOX);
|
|
row = childCursor.newRow();
|
|
row.add(Long.valueOf(Mailbox.QUERY_ALL_INBOXES)); // MAILBOX_COLUMN_ID = 0;
|
|
// MAILBOX_DISPLAY_NAME
|
|
row.add(mActivity.getString(R.string.account_folder_list_summary_inbox));
|
|
row.add(null); // MAILBOX_ACCOUNT_KEY = 2;
|
|
row.add(Integer.valueOf(Mailbox.TYPE_INBOX)); // MAILBOX_TYPE = 3;
|
|
row.add(Integer.valueOf(count)); // MAILBOX_UNREAD_COUNT = 4;
|
|
// TYPE_MAIL (FAVORITES)
|
|
count = EmailContent.count(mActivity, Message.CONTENT_URI, FAVORITE_COUNT_SELECTION, null);
|
|
if (count > 0) {
|
|
row = childCursor.newRow();
|
|
row.add(Long.valueOf(Mailbox.QUERY_ALL_FAVORITES)); // MAILBOX_COLUMN_ID = 0;
|
|
// MAILBOX_DISPLAY_NAME
|
|
row.add(mActivity.getString(R.string.account_folder_list_summary_starred));
|
|
row.add(null); // MAILBOX_ACCOUNT_KEY = 2;
|
|
row.add(Integer.valueOf(Mailbox.TYPE_MAIL)); // MAILBOX_TYPE = 3;
|
|
row.add(Integer.valueOf(count)); // MAILBOX_UNREAD_COUNT = 4;
|
|
}
|
|
// TYPE_DRAFTS
|
|
count = getCountByMailboxType(mActivity, Mailbox.TYPE_DRAFTS);
|
|
if (count > 0) {
|
|
row = childCursor.newRow();
|
|
row.add(Long.valueOf(Mailbox.QUERY_ALL_DRAFTS)); // MAILBOX_COLUMN_ID = 0;
|
|
// MAILBOX_DISPLAY_NAME
|
|
row.add(mActivity.getString(R.string.account_folder_list_summary_drafts));
|
|
row.add(null); // MAILBOX_ACCOUNT_KEY = 2;
|
|
row.add(Integer.valueOf(Mailbox.TYPE_DRAFTS)); // MAILBOX_TYPE = 3;
|
|
row.add(Integer.valueOf(count)); // MAILBOX_UNREAD_COUNT = 4;
|
|
}
|
|
// TYPE_OUTBOX
|
|
count = getCountByMailboxType(mActivity, Mailbox.TYPE_OUTBOX);
|
|
if (count > 0) {
|
|
row = childCursor.newRow();
|
|
row.add(Long.valueOf(Mailbox.QUERY_ALL_OUTBOX)); // MAILBOX_COLUMN_ID = 0;
|
|
// MAILBOX_DISPLAY_NAME
|
|
row.add(mActivity.getString(R.string.account_folder_list_summary_outbox));
|
|
row.add(null); // MAILBOX_ACCOUNT_KEY = 2;
|
|
row.add(Integer.valueOf(Mailbox.TYPE_OUTBOX)); // MAILBOX_TYPE = 3;
|
|
row.add(Integer.valueOf(count)); // MAILBOX_UNREAD_COUNT = 4;
|
|
}
|
|
return childCursor;
|
|
}
|
|
|
|
/**
|
|
* Async task to handle the accounts query outside of the UI thread
|
|
*/
|
|
private class LoadAccountsTask extends AsyncTask<Void, Void, Object[]> {
|
|
@Override
|
|
protected Object[] doInBackground(Void... params) {
|
|
// Create the summaries cursor
|
|
Cursor c1 = null;
|
|
Cursor c2 = null;
|
|
Long defaultAccount = null;
|
|
if (!isCancelled()) {
|
|
// Create the summaries cursor
|
|
c1 = getSummaryChildCursor();
|
|
}
|
|
|
|
if (!isCancelled()) {
|
|
// TODO use a custom projection and don't have to sample all of these columns
|
|
c2 = mActivity.getContentResolver().query(
|
|
EmailContent.Account.CONTENT_URI,
|
|
EmailContent.Account.CONTENT_PROJECTION, null, null, null);
|
|
}
|
|
|
|
if (!isCancelled()) {
|
|
defaultAccount = Account.getDefaultAccountId(mActivity);
|
|
}
|
|
|
|
if (isCancelled()) {
|
|
if (c1 != null) c1.close();
|
|
if (c2 != null) c2.close();
|
|
return null;
|
|
}
|
|
return new Object[] { c1, c2 , defaultAccount};
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Object[] params) {
|
|
if (isCancelled() || params == null) {
|
|
if (params != null) {
|
|
Cursor c1 = (Cursor)params[0];
|
|
if (c1 != null) {
|
|
c1.close();
|
|
}
|
|
Cursor c2 = (Cursor)params[1];
|
|
if (c2 != null) {
|
|
c2.close();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
// Before writing a new list adapter into the listview, we need to
|
|
// shut down the old one (if any).
|
|
ListAdapter oldAdapter = mListView.getAdapter();
|
|
if (oldAdapter != null && oldAdapter instanceof CursorAdapter) {
|
|
((CursorAdapter)oldAdapter).changeCursor(null);
|
|
}
|
|
// Now create a new list adapter and install it
|
|
mListAdapter = AccountsAdapter.getInstance((Cursor)params[0], (Cursor)params[1],
|
|
mActivity, (Long)params[2], AccountFolderListFragment.this);
|
|
setListAdapter(mListAdapter);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Controller results listener. We wrap it with {@link ControllerResultUiThreadWrapper},
|
|
* so all methods are called on the UI thread.
|
|
*/
|
|
private class ControllerResults extends Controller.Result {
|
|
@Override
|
|
public void updateMailboxCallback(MessagingException result, long accountKey,
|
|
long mailboxKey, int progress, int numNewMessages) {
|
|
if (progress == 100) {
|
|
updateAccounts();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void sendMailCallback(MessagingException result, long accountId, long messageId,
|
|
int progress) {
|
|
if (progress == 100) {
|
|
updateAccounts();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void deleteAccountCallback(long accountId) {
|
|
updateAccounts();
|
|
}
|
|
}
|
|
|
|
}
|