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