AccountSelectorAdapter.java revision 80d3875d306c60da83e547c573427627911f8a99
1/* 2 * Copyright (C) 2010 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.google.common.annotations.VisibleForTesting; 20 21import com.android.email.FolderProperties; 22import com.android.email.R; 23import com.android.email.data.ClosingMatrixCursor; 24import com.android.email.data.ThrottlingCursorLoader; 25import com.android.emailcommon.provider.Account; 26import com.android.emailcommon.provider.EmailContent; 27import com.android.emailcommon.provider.EmailContent.MailboxColumns; 28import com.android.emailcommon.provider.Mailbox; 29import com.android.emailcommon.utility.Utility; 30 31import java.util.ArrayList; 32 33import android.content.ContentUris; 34import android.content.Context; 35import android.content.Loader; 36import android.database.Cursor; 37import android.database.MatrixCursor; 38import android.view.LayoutInflater; 39import android.view.View; 40import android.view.ViewGroup; 41import android.widget.AdapterView; 42import android.widget.CursorAdapter; 43import android.widget.TextView; 44 45/** 46 * Account selector spinner. 47 * 48 * TODO Test it! 49 */ 50public class AccountSelectorAdapter extends CursorAdapter { 51 /** meta data column for an message count (unread or total, depending on row) */ 52 private static final String MESSAGE_COUNT = "unreadCount"; 53 /** meta data column for the row type; used for display purposes */ 54 private static final String ROW_TYPE = "rowType"; 55 /** meta data position of the currently selected account in the drop-down list */ 56 private static final String ACCOUNT_POSITION = "accountPosition"; 57 private static final int ROW_TYPE_HEADER = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; 58 @SuppressWarnings("unused") 59 private static final int ROW_TYPE_MAILBOX = 0; 60 private static final int ROW_TYPE_ACCOUNT = 1; 61 private static final int ITEM_VIEW_TYPE_ACCOUNT = 0; 62 static final int UNKNOWN_POSITION = -1; 63 /** Projection for account database query */ 64 private static final String[] ACCOUNT_PROJECTION = new String[] { 65 EmailContent.RECORD_ID, 66 Account.DISPLAY_NAME, 67 Account.EMAIL_ADDRESS, 68 }; 69 /** 70 * Projection used for the selector display; we add meta data that doesn't exist in the 71 * account database, so, this should be a super-set of {@link #ACCOUNT_PROJECTION}. 72 */ 73 private static final String[] ADAPTER_PROJECTION = new String[] { 74 ROW_TYPE, 75 EmailContent.RECORD_ID, 76 Account.DISPLAY_NAME, 77 Account.EMAIL_ADDRESS, 78 MESSAGE_COUNT, 79 ACCOUNT_POSITION, 80 }; 81 82 /** Sort order. Show the default account first. */ 83 private static final String ORDER_BY = 84 Account.IS_DEFAULT + " desc, " + Account.RECORD_ID; 85 86 private final LayoutInflater mInflater; 87 @SuppressWarnings("hiding") 88 private final Context mContext; 89 90 /** 91 * Returns a loader that can populate the account spinner. 92 * @param context a context 93 * @param accountId the ID of the currently viewed account 94 */ 95 public static Loader<Cursor> createLoader(Context context, long accountId) { 96 return new AccountsLoader(context, accountId, UiUtilities.useTwoPane(context)); 97 } 98 99 public AccountSelectorAdapter(Context context) { 100 super(context, null, 0 /* no auto-requery */); 101 mContext = context; 102 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 103 } 104 105 /** 106 * Invoked when the action bar needs the view of the text in the bar itself. The default 107 * is to show just the display name of the cursor at the given position. 108 */ 109 @Override 110 public View getView(int position, View convertView, ViewGroup parent) { 111 if (!isAccountItem(position)) { 112 // asked to show a recent mailbox; instead, show the account associated w/ the mailbox 113 int newPosition = getAccountPosition(position); 114 if (newPosition != UNKNOWN_POSITION) { 115 position = newPosition; 116 } 117 } 118 return super.getView(position, convertView, parent); 119 } 120 121 /** 122 * The account selector view can contain one of four types of row data: 123 * <ol> 124 * <li>headers</li> 125 * <li>accounts</li> 126 * <li>recent mailboxes</li> 127 * <li>"show all folders"</li> 128 * </ol> 129 * Headers are handled separately as they have a unique layout and cannot be interacted with. 130 * Accounts, recent mailboxes and "show all folders" all have the same interaction model and 131 * share a very similar layout. The single difference is that both accounts and recent 132 * mailboxes display an unread count; whereas "show all folders" does not. To determine 133 * if a particular row is "show all folders" verify that a) it's not an account row and 134 * b) it's ID is {@link Mailbox#NO_MAILBOX}. 135 */ 136 @Override 137 public View getDropDownView(int position, View convertView, ViewGroup parent) { 138 Cursor c = getCursor(); 139 c.moveToPosition(position); 140 141 View view; 142 if (c.getInt(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) { 143 view = mInflater.inflate(R.layout.account_selector_dropdown_header, parent, false); 144 final TextView displayNameView = (TextView) view.findViewById(R.id.display_name); 145 final String displayName = getDisplayName(c); 146 displayNameView.setText(displayName); 147 } else { 148 view = mInflater.inflate(R.layout.account_selector_dropdown, parent, false); 149 final TextView displayNameView = (TextView) view.findViewById(R.id.display_name); 150 final TextView emailAddressView = (TextView) view.findViewById(R.id.email_address); 151 final TextView unreadCountView = (TextView) view.findViewById(R.id.unread_count); 152 153 final String displayName = getDisplayName(position); 154 final String emailAddress = getAccountEmailAddress(position); 155 156 displayNameView.setText(displayName); 157 158 // Show the email address only when it's different from the display name. 159 if (displayName.equals(emailAddress)) { 160 emailAddressView.setVisibility(View.GONE); 161 } else { 162 emailAddressView.setVisibility(View.VISIBLE); 163 emailAddressView.setText(emailAddress); 164 } 165 166 boolean isAccount = isAccountItem(position); 167 long id = getId(c); 168 if (isAccount || id != Mailbox.NO_MAILBOX) { 169 unreadCountView.setVisibility(View.VISIBLE); 170 unreadCountView.setText(UiUtilities.getMessageCountForUi(mContext, 171 getAccountUnreadCount(position), true)); 172 } else { 173 unreadCountView.setVisibility(View.INVISIBLE); 174 } 175 } 176 return view; 177 } 178 179 @Override 180 public void bindView(View view, Context context, Cursor cursor) { 181 TextView textView = (TextView) view.findViewById(R.id.display_name); 182 textView.setText(getDisplayName(cursor)); 183 } 184 185 @Override 186 public View newView(Context context, Cursor cursor, ViewGroup parent) { 187 return mInflater.inflate(R.layout.account_selector, parent, false); 188 } 189 190 @Override 191 public int getViewTypeCount() { 192 return 2; 193 } 194 195 @Override 196 public int getItemViewType(int position) { 197 Cursor c = getCursor(); 198 c.moveToPosition(position); 199 return c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER 200 ? AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER 201 : ITEM_VIEW_TYPE_ACCOUNT; 202 } 203 204 @Override 205 public boolean isEnabled(int position) { 206 return (getItemViewType(position) != AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER); 207 } 208 209 public boolean isAccountItem(int position) { 210 Cursor c = getCursor(); 211 c.moveToPosition(position); 212 return (c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_ACCOUNT); 213 } 214 215 public boolean isMailboxItem(int position) { 216 Cursor c = getCursor(); 217 c.moveToPosition(position); 218 return (c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_MAILBOX); 219 } 220 221 private String getDisplayName(int position) { 222 final Cursor c = getCursor(); 223 return c.moveToPosition(position) ? getDisplayName(c) : null; 224 } 225 226 private String getAccountEmailAddress(int position) { 227 final Cursor c = getCursor(); 228 return c.moveToPosition(position) ? getAccountEmailAddress(c) : null; 229 } 230 231 private int getAccountUnreadCount(int position) { 232 final Cursor c = getCursor(); 233 return c.moveToPosition(position) ? getMessageCount(c) : 0; 234 } 235 236 int getAccountPosition(int position) { 237 final Cursor c = getCursor(); 238 return c.moveToPosition(position) ? getAccountPosition(c) : UNKNOWN_POSITION; 239 } 240 241 /** 242 * Returns the account/mailbox ID extracted from the given cursor. 243 */ 244 private static long getId(Cursor c) { 245 return c.getLong(c.getColumnIndex(EmailContent.RECORD_ID)); 246 } 247 248 /** Returns the account name extracted from the given cursor. */ 249 static String getDisplayName(Cursor cursor) { 250 return cursor.getString(cursor.getColumnIndex(Account.DISPLAY_NAME)); 251 } 252 253 /** Returns the email address extracted from the given cursor. */ 254 private static String getAccountEmailAddress(Cursor cursor) { 255 return cursor.getString(cursor.getColumnIndex(Account.EMAIL_ADDRESS)); 256 } 257 258 /** 259 * Returns the message count (unread or total, depending on row) extracted from the given 260 * cursor. 261 */ 262 private static int getMessageCount(Cursor cursor) { 263 return cursor.getInt(cursor.getColumnIndex(MESSAGE_COUNT)); 264 } 265 266 /** Returns the account position extracted from the given cursor. */ 267 private static int getAccountPosition(Cursor cursor) { 268 return cursor.getInt(cursor.getColumnIndex(ACCOUNT_POSITION)); 269 } 270 271 /** 272 * Load the account list. The resulting cursor contains 273 * - Account info 274 * - # of unread messages in inbox 275 * - The "Combined view" row if there's more than one account. 276 */ 277 @VisibleForTesting 278 static class AccountsLoader extends ThrottlingCursorLoader { 279 private final Context mContext; 280 private final long mAccountId; 281 private final boolean mUseTwoPane; // Injectable for test 282 private final FolderProperties mFolderProperties; 283 284 @VisibleForTesting 285 AccountsLoader(Context context, long accountId, boolean useTwoPane) { 286 // Super class loads a regular account cursor, but we replace it in loadInBackground(). 287 super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, 288 ORDER_BY); 289 mContext = context; 290 mAccountId = accountId; 291 mFolderProperties = FolderProperties.getInstance(mContext); 292 mUseTwoPane = useTwoPane; 293 } 294 295 @Override 296 public Cursor loadInBackground() { 297 final Cursor accountsCursor = super.loadInBackground(); 298 // Use ClosingMatrixCursor so that accountsCursor gets closed too when it's closed. 299 final CursorWithExtras resultCursor 300 = new CursorWithExtras(ADAPTER_PROJECTION, accountsCursor); 301 final int accountPosition = addAccountsToCursor(resultCursor, accountsCursor); 302 addRecentsToCursor(resultCursor, accountPosition); 303 return Utility.CloseTraceCursorWrapper.get(resultCursor); 304 } 305 306 /** Adds the account list [with extra meta data] to the given matrix cursor */ 307 private int addAccountsToCursor(CursorWithExtras matrixCursor, Cursor accountCursor) { 308 int accountPosition = UNKNOWN_POSITION; 309 accountCursor.moveToPosition(-1); 310 // Add a header for the accounts 311 String header = 312 mContext.getString(R.string.mailbox_list_account_selector_account_header); 313 addRow(matrixCursor, ROW_TYPE_HEADER, 0L, header, null, 0, UNKNOWN_POSITION); 314 315 matrixCursor.mAccountCount = accountCursor.getCount(); 316 int totalUnread = 0; 317 int currentPosition = 1; 318 while (accountCursor.moveToNext()) { 319 // Add account, with its unread count. 320 final long accountId = accountCursor.getLong(0); 321 final int unread = Mailbox.getUnreadCountByAccountAndMailboxType( 322 mContext, accountId, Mailbox.TYPE_INBOX); 323 final String name = getDisplayName(accountCursor); 324 final String emailAddress = getAccountEmailAddress(accountCursor); 325 addRow(matrixCursor, ROW_TYPE_ACCOUNT, accountId, name, emailAddress, unread, 326 UNKNOWN_POSITION); 327 totalUnread += unread; 328 if (accountId == mAccountId) { 329 accountPosition = currentPosition; 330 } 331 currentPosition++; 332 } 333 // Add "combined view" if more than one account exists 334 final int countAccounts = accountCursor.getCount(); 335 if (countAccounts > 1) { 336 final String name = mContext.getResources().getString( 337 R.string.mailbox_list_account_selector_combined_view); 338 final String accountCount = mContext.getResources().getQuantityString( 339 R.plurals.number_of_accounts, countAccounts, countAccounts); 340 addRow(matrixCursor, ROW_TYPE_ACCOUNT, Account.ACCOUNT_ID_COMBINED_VIEW, 341 name, accountCount, totalUnread, UNKNOWN_POSITION); 342 343 // Increment the account count for the combined account. 344 matrixCursor.mAccountCount++; 345 } 346 return accountPosition; 347 } 348 349 /** 350 * Adds the recent mailbox list to the given cursor. 351 * @param matrixCursor the cursor to add the list to 352 * @param accountPosition the cursor position of the currently selected account 353 */ 354 private void addRecentsToCursor(CursorWithExtras matrixCursor, int accountPosition) { 355 if (mAccountId <= 0L || mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 356 // Currently selected account isn't usable for our purposes 357 return; 358 } 359 String emailAddress = null; 360 if (accountPosition != UNKNOWN_POSITION) { 361 matrixCursor.moveToPosition(accountPosition); 362 emailAddress = 363 matrixCursor.getString(matrixCursor.getColumnIndex(Account.EMAIL_ADDRESS)); 364 } 365 RecentMailboxManager mailboxManager = RecentMailboxManager.getInstance(mContext); 366 ArrayList<Long> recentMailboxes = null; 367 if (!mUseTwoPane) { 368 // Do not display recent mailboxes in the account spinner for the two pane view 369 recentMailboxes = mailboxManager.getMostRecent(mAccountId, mUseTwoPane); 370 } 371 int recentCount = (recentMailboxes == null) ? 0 : recentMailboxes.size(); 372 matrixCursor.mRecentCount = recentCount; 373 374 if (!mUseTwoPane) { 375 // "Recent mailboxes" header 376 String mailboxHeader = mContext.getString( 377 R.string.mailbox_list_account_selector_mailbox_header_fmt, emailAddress); 378 addRow(matrixCursor, ROW_TYPE_HEADER, 0L, mailboxHeader, null, 0, UNKNOWN_POSITION); 379 } 380 381 if (recentCount > 0) { 382 for (long mailboxId : recentMailboxes) { 383 addMailboxRow(matrixCursor, accountPosition, mailboxId); 384 } 385 } 386 387 if (!mUseTwoPane) { 388 // TODO We need this for combined view too. 389 String name = mContext.getString( 390 R.string.mailbox_list_account_selector_show_all_folders); 391 addRow(matrixCursor, ROW_TYPE_MAILBOX, Mailbox.NO_MAILBOX, name, null, 0, 392 accountPosition); 393 } 394 } 395 396 397 private static final String[] RECENT_MAILBOX_INFO_PROJECTION = new String[] { 398 MailboxColumns.ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, 399 MailboxColumns.UNREAD_COUNT, MailboxColumns.MESSAGE_COUNT 400 }; 401 402 private void addMailboxRow(MatrixCursor matrixCursor, int accountPosition, long mailboxId) { 403 Cursor c = mContext.getContentResolver().query( 404 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 405 RECENT_MAILBOX_INFO_PROJECTION, null, null, null); 406 if (!c.moveToFirst()) { 407 return; 408 } 409 addRow(matrixCursor, ROW_TYPE_MAILBOX, mailboxId, 410 mFolderProperties.getDisplayName(c), null, 411 mFolderProperties.getMessageCount(c), accountPosition); 412 } 413 414 /** Adds a row to the given cursor */ 415 private void addRow(MatrixCursor cursor, int rowType, long id, String name, 416 String emailAddress, int messageCount, int listPosition) { 417 cursor.newRow() 418 .add(rowType) 419 .add(id) 420 .add(name) 421 .add(emailAddress) 422 .add(messageCount) 423 .add(listPosition); 424 } 425 } 426 427 /** Cursor with some extra meta data. */ 428 static class CursorWithExtras extends ClosingMatrixCursor { 429 /** Number of account elements, including the combined account row. */ 430 private int mAccountCount; 431 /** Number of recent mailbox elements */ 432 private int mRecentCount; 433 434 private CursorWithExtras(String[] columnNames, Cursor innerCursor) { 435 super(columnNames, innerCursor); 436 } 437 438 /** 439 * Returns the cursor position of the item with the given ID. Or {@link #UNKNOWN_POSITION} 440 * if the given ID does not exist. 441 */ 442 int getPosition(long id) { 443 moveToPosition(-1); 444 while(moveToNext()) { 445 if (id == getId(this)) { 446 return getPosition(); 447 } 448 } 449 return UNKNOWN_POSITION; 450 } 451 452 public int getAccountCount() { 453 return mAccountCount; 454 } 455 456 public int getRecentMailboxCount() { 457 return mRecentCount; 458 } 459 } 460} 461