replicant-packages_apps_Email/src/com/android/email/activity/ShortcutPickerFragment.java

402 lines
17 KiB
Java

/*
* Copyright (C) 2011 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.R;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import android.app.Activity;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentValues;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Loader;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.database.MatrixCursor.RowBuilder;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.AdapterView.OnItemClickListener;
/**
* Fragment containing a list of accounts to show during shortcut creation.
* <p>
* NOTE: In order to receive callbacks, the activity containing this fragment must implement
* the {@link PickerCallback} interface.
*/
public abstract class ShortcutPickerFragment extends ListFragment
implements OnItemClickListener, LoaderCallbacks<Cursor> {
/** Allow all mailboxes in the mailbox list */
public static int FILTER_ALLOW_ALL = 0;
/** Only allow an account's INBOX */
public static int FILTER_INBOX_ONLY = 1 << 0;
/** Allow an "unread" mailbox; this is not affected by {@link #FILTER_INBOX_ONLY} */
public static int FILTER_ALLOW_UNREAD = 1 << 1;
/** Fragment argument to set filter values */
public static final String ARG_FILTER = "ShortcutPickerFragment.filter";
/** The filter values; default to allow all mailboxes */
private Integer mFilter;
/** Callback methods. Enclosing activities must implement to receive fragment notifications. */
public static interface PickerCallback {
/** Invoked when an account and mailbox have been selected. */
public void onSelected(Account account, long mailboxId);
/** Required data is missing; either the account and/or mailbox */
public void onMissingData(boolean missingAccount, boolean missingMailbox);
}
/** A no-op callback */
private final PickerCallback EMPTY_CALLBACK = new PickerCallback() {
@Override public void onSelected(Account account, long mailboxId){ getActivity().finish(); }
@Override public void onMissingData(boolean missingAccount, boolean missingMailbox) { }
};
private final static int LOADER_ID = 0;
private final static int[] TO_VIEWS = new int[] {
android.R.id.text1,
};
PickerCallback mCallback = EMPTY_CALLBACK;
/** Cursor adapter that provides either the account or mailbox list */
private SimpleCursorAdapter mAdapter;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof PickerCallback) {
mCallback = (PickerCallback) activity;
}
final String[] fromColumns = getFromColumns();
mAdapter = new SimpleCursorAdapter(activity,
android.R.layout.simple_expandable_list_item_1, null, fromColumns, TO_VIEWS, 0);
setListAdapter(mAdapter);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ListView listView = getListView();
listView.setOnItemClickListener(this);
listView.setItemsCanFocus(false);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
/** Returns the cursor columns to map into list */
abstract String[] getFromColumns();
/** Returns the mailbox filter */
int getFilter() {
if (mFilter == null) {
Bundle args = getArguments();
if (args != null) {
mFilter = args.getInt(ARG_FILTER, FILTER_ALLOW_ALL);
} else {
// No arguments set on fragment, use a default value
mFilter = FILTER_ALLOW_ALL;
}
}
return mFilter;
}
// TODO if we add meta-accounts to the database, remove this class entirely
private static final class AccountPickerLoader extends CursorLoader {
public AccountPickerLoader(Context context, Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
super(context, uri, projection, selection, selectionArgs, sortOrder);
}
@Override
public Cursor loadInBackground() {
Cursor parentCursor = super.loadInBackground();
int cursorCount = parentCursor.getCount();
final Cursor returnCursor;
if (cursorCount > 1) {
// Only add "All accounts" if there is more than 1 account defined
MatrixCursor allAccountCursor = new MatrixCursor(getProjection());
addCombinedAccountRow(allAccountCursor, cursorCount);
returnCursor = new MergeCursor(new Cursor[] { allAccountCursor, parentCursor });
} else {
returnCursor = parentCursor;
}
return returnCursor;
}
/** Adds a row for "All Accounts" into the given cursor */
private void addCombinedAccountRow(MatrixCursor cursor, int accountCount) {
Context context = getContext();
Account account = new Account();
account.mId = Account.ACCOUNT_ID_COMBINED_VIEW;
Resources res = context.getResources();
String countString = res.getQuantityString(R.plurals.picker_combined_view_account_count,
accountCount, accountCount);
account.mDisplayName = res.getString(R.string.picker_combined_view_fmt, countString);
ContentValues values = account.toContentValues();
RowBuilder row = cursor.newRow();
for (String rowName : cursor.getColumnNames()) {
// special case some of the rows ...
if (AccountColumns.ID.equals(rowName)) {
row.add(Account.ACCOUNT_ID_COMBINED_VIEW);
continue;
} else if (AccountColumns.IS_DEFAULT.equals(rowName)) {
row.add(0);
continue;
}
row.add(values.get(rowName));
}
}
}
/** Account picker */
public static class AccountShortcutPickerFragment extends ShortcutPickerFragment {
private final static String[] ACCOUNT_FROM_COLUMNS = new String[] {
AccountColumns.DISPLAY_NAME,
};
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().setTitle(R.string.account_shortcut_picker_title);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
selectAccountCursor(cursor, true);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Context context = getActivity();
return new AccountPickerLoader(
context, Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// if there is only one account, auto-select it
// No accounts; close the dialog
if (data.getCount() == 0) {
mCallback.onMissingData(true, false);
return;
}
if (data.getCount() == 1 && data.moveToFirst()) {
selectAccountCursor(data, false);
return;
}
super.onLoadFinished(loader, data);
}
@Override
String[] getFromColumns() {
return ACCOUNT_FROM_COLUMNS;
}
/** Selects the account specified by the given cursor */
private void selectAccountCursor(Cursor cursor, boolean allowBack) {
Account account = new Account();
account.restore(cursor);
ShortcutPickerFragment fragment = new MailboxShortcutPickerFragment();
final Bundle args = new Bundle();
args.putParcelable(MailboxShortcutPickerFragment.ARG_ACCOUNT, account);
args.putInt(ARG_FILTER, getFilter());
fragment.setArguments(args);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.shortcut_list, fragment);
if (allowBack) {
transaction.addToBackStack(null);
}
transaction.commitAllowingStateLoss();
}
}
// TODO if we add meta-mailboxes to the database, remove this class entirely
private static final class MailboxPickerLoader extends CursorLoader {
private final long mAccountId;
private final boolean mAllowUnread;
public MailboxPickerLoader(Context context, Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder, long accountId, boolean allowUnread) {
super(context, uri, projection, selection, selectionArgs, sortOrder);
mAccountId = accountId;
mAllowUnread = allowUnread;
}
@Override
public Cursor loadInBackground() {
MatrixCursor unreadCursor =
new MatrixCursor(MailboxShortcutPickerFragment.MATRIX_PROJECTION);
Context context = getContext();
if (mAllowUnread) {
// For the special mailboxes, their ID is < 0. The UI list does not deal with
// negative values very well, so, add MAX_VALUE to ensure they're positive, but,
// don't clash with legitimate mailboxes.
String mailboxName = context.getString(R.string.picker_mailbox_name_all_unread);
unreadCursor.addRow(
new Object[] {
Integer.MAX_VALUE + Mailbox.QUERY_ALL_UNREAD,
Mailbox.QUERY_ALL_UNREAD,
mailboxName,
});
}
if (mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
// Do something special for the "combined" view
MatrixCursor combinedMailboxesCursor =
new MatrixCursor(MailboxShortcutPickerFragment.MATRIX_PROJECTION);
// For the special mailboxes, their ID is < 0. The UI list does not deal with
// negative values very well, so, add MAX_VALUE to ensure they're positive, but,
// don't clash with legitimate mailboxes.
String mailboxName = context.getString(R.string.picker_mailbox_name_all_inbox);
combinedMailboxesCursor.addRow(
new Object[] {
Integer.MAX_VALUE + Mailbox.QUERY_ALL_INBOXES,
Mailbox.QUERY_ALL_INBOXES,
mailboxName
});
return new MergeCursor(new Cursor[] { combinedMailboxesCursor, unreadCursor });
}
// Loading for a regular account; perform a normal load
return new MergeCursor(new Cursor[] { super.loadInBackground(), unreadCursor });
}
}
/** Mailbox picker */
public static class MailboxShortcutPickerFragment extends ShortcutPickerFragment {
static final String ARG_ACCOUNT = "MailboxShortcutPickerFragment.account";
private final static String REAL_ID = "realId";
private final static String[] MAILBOX_FROM_COLUMNS = new String[] {
MailboxColumns.DISPLAY_NAME,
};
/** Loader projection used for IMAP & POP3 accounts */
private final static String[] IMAP_PROJECTION = new String [] {
MailboxColumns.ID, MailboxColumns.ID + " as " + REAL_ID,
MailboxColumns.SERVER_ID + " as " + MailboxColumns.DISPLAY_NAME
};
/** Loader projection used for EAS accounts */
private final static String[] EAS_PROJECTION = new String [] {
MailboxColumns.ID, MailboxColumns.ID + " as " + REAL_ID,
MailboxColumns.DISPLAY_NAME
};
/** Loader projection used for a matrix cursor */
private final static String[] MATRIX_PROJECTION = new String [] {
MailboxColumns.ID, REAL_ID, MailboxColumns.DISPLAY_NAME
};
// TODO #ALL_MAILBOX_SELECTION is identical to MailboxesAdapter#ALL_MAILBOX_SELECTION;
// create a common selection. Move this to the Mailbox class?
/** Selection for all visible mailboxes for an account */
private final static String ALL_MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" +
" AND " + Mailbox.USER_VISIBLE_MAILBOX_SELECTION;
/** Selection for just the INBOX of an account */
private final static String INBOX_ONLY_SELECTION = ALL_MAILBOX_SELECTION +
" AND " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX;
/** The currently selected account */
private Account mAccount;
@Override
public void onAttach(Activity activity) {
// Need to setup the account first thing
mAccount = getArguments().getParcelable(ARG_ACCOUNT);
super.onAttach(activity);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().setTitle(R.string.mailbox_shortcut_picker_title);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
long mailboxId = cursor.getLong(cursor.getColumnIndex(REAL_ID));
mCallback.onSelected(mAccount, mailboxId);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Context context = getActivity();
// TODO Create a fully-qualified path name for Exchange accounts [code should also work
// for MoveMessageToDialog.java]
HostAuth recvAuth = mAccount.getOrCreateHostAuthRecv(context);
final String[] projection;
final String orderBy;
final String selection;
if (recvAuth.isEasConnection()) {
projection = EAS_PROJECTION;
orderBy = MailboxColumns.DISPLAY_NAME;
} else {
projection = IMAP_PROJECTION;
orderBy = MailboxColumns.SERVER_ID;
}
if ((getFilter() & FILTER_INBOX_ONLY) == 0) {
selection = ALL_MAILBOX_SELECTION;
} else {
selection = INBOX_ONLY_SELECTION;
}
return new MailboxPickerLoader(
context, Mailbox.CONTENT_URI, projection, selection,
new String[] { Long.toString(mAccount.mId) }, orderBy, mAccount.mId,
(getFilter() & FILTER_ALLOW_UNREAD) != 0);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// No accounts; close the dialog
if (data.getCount() == 0) {
mCallback.onMissingData(false, true);
return;
}
// if there is only one mailbox, auto-select it
if (data.getCount() == 1 && data.moveToFirst()) {
long mailboxId = data.getLong(data.getColumnIndex(REAL_ID));
mCallback.onSelected(mAccount, mailboxId);
return;
}
super.onLoadFinished(loader, data);
}
@Override
String[] getFromColumns() {
return MAILBOX_FROM_COLUMNS;
}
}
}