ShortcutPickerFragment.java revision 0f84ff2c082dc4ea774f53fe6877752a769ca258
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.activity; 18 19import com.android.email.R; 20import com.android.emailcommon.provider.EmailContent.Account; 21import com.android.emailcommon.provider.EmailContent.AccountColumns; 22import com.android.emailcommon.provider.EmailContent.MailboxColumns; 23import com.android.emailcommon.provider.HostAuth; 24import com.android.emailcommon.provider.Mailbox; 25 26import android.app.Activity; 27import android.app.ListFragment; 28import android.app.LoaderManager.LoaderCallbacks; 29import android.content.ContentValues; 30import android.content.Context; 31import android.content.CursorLoader; 32import android.content.Loader; 33import android.database.Cursor; 34import android.database.MatrixCursor; 35import android.database.MergeCursor; 36import android.database.MatrixCursor.RowBuilder; 37import android.net.Uri; 38import android.os.Bundle; 39import android.view.View; 40import android.widget.AdapterView; 41import android.widget.ListView; 42import android.widget.SimpleCursorAdapter; 43import android.widget.AdapterView.OnItemClickListener; 44 45/** 46 * Fragment containing a list of accounts to show during shortcut creation. 47 * <p> 48 * NOTE: In order to receive callbacks, the activity containing this fragment must implement 49 * the {@link PickerCallback} interface. 50 */ 51public abstract class ShortcutPickerFragment extends ListFragment 52 implements OnItemClickListener, LoaderCallbacks<Cursor> { 53 /** Callback methods. Enclosing activities must implement to receive fragment notifications. */ 54 public static interface PickerCallback { 55 /** Invoked when an account and mailbox have been selected. */ 56 public void onSelected(Account account, long mailboxId); 57 /** Required data is missing; either the account and/or mailbox */ 58 public void onMissingData(boolean missingAccount, boolean missingMailbox); 59 } 60 61 /** A no-op callback */ 62 private final PickerCallback EMPTY_CALLBACK = new PickerCallback() { 63 @Override public void onSelected(Account account, long mailboxId){ getActivity().finish(); } 64 @Override public void onMissingData(boolean missingAccount, boolean missingMailbox) { } 65 }; 66 private final static int LOADER_ID = 0; 67 private final static int[] TO_VIEWS = new int[] { 68 android.R.id.text1, 69 }; 70 71 PickerCallback mCallback = EMPTY_CALLBACK; 72 /** Cursor adapter that provides either the account or mailbox list */ 73 private SimpleCursorAdapter mAdapter; 74 75 @Override 76 public void onAttach(Activity activity) { 77 super.onAttach(activity); 78 79 if (activity instanceof PickerCallback) { 80 mCallback = (PickerCallback) activity; 81 } 82 final String[] fromColumns = getFromColumns(); 83 mAdapter = new SimpleCursorAdapter(activity, 84 android.R.layout.simple_expandable_list_item_1, null, fromColumns, TO_VIEWS, 0); 85 setListAdapter(mAdapter); 86 87 getLoaderManager().initLoader(LOADER_ID, null, this); 88 } 89 90 @Override 91 public void onActivityCreated(Bundle savedInstanceState) { 92 super.onActivityCreated(savedInstanceState); 93 94 ListView listView = getListView(); 95 listView.setOnItemClickListener(this); 96 listView.setItemsCanFocus(false); 97 } 98 99 @Override 100 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 101 mAdapter.swapCursor(data); 102 } 103 104 @Override 105 public void onLoaderReset(Loader<Cursor> loader) { 106 mAdapter.swapCursor(null); 107 } 108 109 /** Returns the cursor columns to map into list */ 110 abstract String[] getFromColumns(); 111 112 // TODO if we add meta-accounts to the database, remove this class entirely 113 private static final class AccountPickerLoader extends CursorLoader { 114 public AccountPickerLoader(Context context, Uri uri, String[] projection, String selection, 115 String[] selectionArgs, String sortOrder) { 116 super(context, uri, projection, selection, selectionArgs, sortOrder); 117 } 118 119 @Override 120 public Cursor loadInBackground() { 121 Cursor parentCursor = super.loadInBackground(); 122 Cursor returnCursor; 123 124 if (parentCursor.getCount() > 1) { 125 // Only add "All accounts" if there is more than 1 account defined 126 MatrixCursor allAccountCursor = new MatrixCursor(getProjection()); 127 addCombinedAccountRow(allAccountCursor); 128 returnCursor = new MergeCursor(new Cursor[] { allAccountCursor, parentCursor }); 129 } else { 130 returnCursor = parentCursor; 131 } 132 return returnCursor; 133 } 134 135 /** Adds a row for "All Accounts" into the given cursor */ 136 private void addCombinedAccountRow(MatrixCursor cursor) { 137 Context context = getContext(); 138 Account account = new Account(); 139 account.mId = Account.ACCOUNT_ID_COMBINED_VIEW; 140 account.mDisplayName = context.getString(R.string.account_name_display_all); 141 ContentValues values = account.toContentValues(); 142 RowBuilder row = cursor.newRow(); 143 for (String rowName : cursor.getColumnNames()) { 144 // special case some of the rows ... 145 if (AccountColumns.ID.equals(rowName)) { 146 row.add(Account.ACCOUNT_ID_COMBINED_VIEW); 147 continue; 148 } else if (AccountColumns.IS_DEFAULT.equals(rowName)) { 149 row.add(0); 150 continue; 151 } 152 row.add(values.get(rowName)); 153 } 154 } 155 } 156 157 /** Account picker */ 158 public static class AccountShortcutPickerFragment extends ShortcutPickerFragment { 159 private final static String[] ACCOUNT_FROM_COLUMNS = new String[] { 160 AccountColumns.DISPLAY_NAME, 161 }; 162 163 @Override 164 public void onActivityCreated(Bundle savedInstanceState) { 165 super.onActivityCreated(savedInstanceState); 166 getActivity().setTitle(R.string.account_shortcut_picker_title); 167 } 168 169 @Override 170 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 171 Cursor cursor = (Cursor) parent.getItemAtPosition(position); 172 selectAccountCursor(cursor); 173 } 174 175 @Override 176 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 177 Context context = getActivity(); 178 return new AccountPickerLoader( 179 context, Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, null); 180 } 181 182 @Override 183 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 184 // if there is only one account, auto-select it 185 // No accounts; close the dialog 186 if (data.getCount() == 0) { 187 mCallback.onMissingData(true, false); 188 return; 189 } 190 if (data.getCount() == 1 && data.moveToFirst()) { 191 selectAccountCursor(data); 192 return; 193 } 194 super.onLoadFinished(loader, data); 195 } 196 197 @Override 198 String[] getFromColumns() { 199 return ACCOUNT_FROM_COLUMNS; 200 } 201 202 /** Selects the account specified by the given cursor */ 203 private void selectAccountCursor(Cursor cursor) { 204 Account account = new Account(); 205 account.restore(cursor); 206 ShortcutPickerFragment fragment = new MailboxShortcutPickerFragment(); 207 final Bundle args = new Bundle(); 208 args.putParcelable(MailboxShortcutPickerFragment.ARG_ACCOUNT, account); 209 fragment.setArguments(args); 210 getFragmentManager() 211 .beginTransaction() 212 .replace(R.id.shortcut_list, fragment) 213 .addToBackStack(null) 214 .commit(); 215 } 216 } 217 218 // TODO if we add meta-mailboxes to the database, remove this class entirely 219 private static final class MailboxPickerLoader extends CursorLoader { 220 private final long mAccountId; 221 public MailboxPickerLoader(Context context, Uri uri, String[] projection, String selection, 222 String[] selectionArgs, String sortOrder, long accountId) { 223 super(context, uri, projection, selection, selectionArgs, sortOrder); 224 mAccountId = accountId; 225 } 226 227 @Override 228 public Cursor loadInBackground() { 229 if (mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 230 // Do something special for the "combined" view 231 Context context = getContext(); 232 MatrixCursor combinedMailboxesCursor = new MatrixCursor(getProjection()); 233 // For the special mailboxes, their ID is < 0. The UI list does not deal with 234 // negative values very well, so, add MAX_VALUE to ensure they're positive, but, 235 // don't clash with legitimate mailboxes. 236 String mailboxName = context.getString(R.string.mailbox_name_display_inbox); 237 combinedMailboxesCursor.addRow( 238 new Object[] {Integer.MAX_VALUE + Mailbox.QUERY_ALL_INBOXES, mailboxName}); 239 // TODO Temporarily commented out. This will be added to the widget selection, so 240 // keeping the code around for now. If this isn't uncommented by July 1, 2011, 241 // feel free to remove it and this TODO 242// mailboxName = context.getString(R.string.mailbox_name_display_unread); 243// combinedMailboxesCursor.addRow( 244// new Object[] {Integer.MAX_VALUE + Mailbox.QUERY_ALL_UNREAD, mailboxName}); 245 return combinedMailboxesCursor; 246 } 247 248 // Loading for a regular account; perform a normal load 249 return super.loadInBackground(); 250 } 251 } 252 253 /** Mailbox picker */ 254 public static class MailboxShortcutPickerFragment extends ShortcutPickerFragment { 255 static final String ARG_ACCOUNT = "MailboxShortcutPickerFragment.account"; 256 private final static String[] MAILBOX_FROM_COLUMNS = new String[] { 257 MailboxColumns.DISPLAY_NAME, 258 }; 259 /** Loader projection used for IMAP & POP3 accounts */ 260 private final static String[] IMAP_PROJECTION = new String [] { 261 MailboxColumns.ID, MailboxColumns.SERVER_ID + " as " + MailboxColumns.DISPLAY_NAME 262 }; 263 /** Loader projection used for EAS accounts */ 264 private final static String[] EAS_PROJECTION = new String [] { 265 MailboxColumns.ID, MailboxColumns.DISPLAY_NAME 266 }; 267 // TODO This is identical to MailboxesAdapter#ALL_MAILBOX_SELECTION; any way we can create a 268 // common selection? Move this to the Mailbox class? 269 private final static String ALL_MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" + 270 " AND " + Mailbox.USER_VISIBLE_MAILBOX_SELECTION; 271 272 /** The currently selected account */ 273 private Account mAccount; 274 275 @Override 276 public void onAttach(Activity activity) { 277 // Need to setup the account first thing 278 mAccount = getArguments().getParcelable(ARG_ACCOUNT); 279 super.onAttach(activity); 280 } 281 282 @Override 283 public void onActivityCreated(Bundle savedInstanceState) { 284 super.onActivityCreated(savedInstanceState); 285 getActivity().setTitle(R.string.mailbox_shortcut_picker_title); 286 } 287 288 @Override 289 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 290 Cursor cursor = (Cursor) parent.getItemAtPosition(position); 291 long mailboxId = cursor.getLong(Mailbox.CONTENT_ID_COLUMN); 292 mCallback.onSelected(mAccount, mailboxId); 293 } 294 295 @Override 296 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 297 Context context = getActivity(); 298 // TODO Create a fully-qualified path name for Exchange accounts [code should also work 299 // for MoveMessageToDialog.java] 300 HostAuth recvAuth = mAccount.getOrCreateHostAuthRecv(context); 301 final String[] projection; 302 final String orderBy; 303 if (recvAuth.isEasConnection()) { 304 projection = EAS_PROJECTION; 305 orderBy = MailboxColumns.DISPLAY_NAME; 306 } else { 307 projection = IMAP_PROJECTION; 308 orderBy = MailboxColumns.SERVER_ID; 309 } 310 return new MailboxPickerLoader( 311 context, Mailbox.CONTENT_URI, projection, ALL_MAILBOX_SELECTION, 312 new String[] { Long.toString(mAccount.mId) }, orderBy, mAccount.mId); 313 } 314 315 @Override 316 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 317 // No accounts; close the dialog 318 if (data.getCount() == 0) { 319 mCallback.onMissingData(false, true); 320 return; 321 } 322 // if there is only one mailbox, auto-select it 323 if (data.getCount() == 1 && data.moveToFirst()) { 324 long mailboxId = data.getLong(Mailbox.CONTENT_ID_COLUMN); 325 mCallback.onSelected(mAccount, mailboxId); 326 return; 327 } 328 super.onLoadFinished(loader, data); 329 } 330 331 @Override 332 String[] getFromColumns() { 333 return MAILBOX_FROM_COLUMNS; 334 } 335 } 336} 337