AccountSelectorAdapter.java revision a26fe0f1507e754b8c1d31a587f84f043c9cee64
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 if (isAccountItem(position) || getAccountId(c) != Mailbox.NO_MAILBOX) { 166 unreadCountView.setVisibility(View.VISIBLE); 167 unreadCountView.setText(UiUtilities.getMessageCountForUi(mContext, 168 getAccountUnreadCount(position), false)); 169 } else { 170 unreadCountView.setVisibility(View.INVISIBLE); 171 } 172 } 173 return view; 174 } 175 176 @Override 177 public void bindView(View view, Context context, Cursor cursor) { 178 TextView textView = (TextView) view.findViewById(R.id.display_name); 179 textView.setText(getAccountDisplayName(cursor)); 180 } 181 182 @Override 183 public View newView(Context context, Cursor cursor, ViewGroup parent) { 184 return mInflater.inflate(R.layout.account_selector, parent, false); 185 } 186 187 @Override 188 public int getViewTypeCount() { 189 return 2; 190 } 191 192 @Override 193 public int getItemViewType(int position) { 194 Cursor c = getCursor(); 195 c.moveToPosition(position); 196 return c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER 197 ? AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER 198 : ITEM_VIEW_TYPE_ACCOUNT; 199 } 200 201 @Override 202 public boolean isEnabled(int position) { 203 return (getItemViewType(position) != AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER); 204 } 205 206 public boolean isAccountItem(int position) { 207 Cursor c = getCursor(); 208 c.moveToPosition(position); 209 return (c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_ACCOUNT); 210 } 211 212 private String getAccountDisplayName(int position) { 213 final Cursor c = getCursor(); 214 return c.moveToPosition(position) ? getAccountDisplayName(c) : null; 215 } 216 217 private String getAccountEmailAddress(int position) { 218 final Cursor c = getCursor(); 219 return c.moveToPosition(position) ? getAccountEmailAddress(c) : null; 220 } 221 222 private int getAccountUnreadCount(int position) { 223 final Cursor c = getCursor(); 224 return c.moveToPosition(position) ? getAccountUnreadCount(c) : 0; 225 } 226 227 int getAccountPosition(int position) { 228 final Cursor c = getCursor(); 229 return c.moveToPosition(position) ? getAccountPosition(c) : UNKNOWN_POSITION; 230 } 231 232 /** Returns the account ID extracted from the given cursor. */ 233 private static long getAccountId(Cursor c) { 234 return c.getLong(c.getColumnIndex(Account.ID)); 235 } 236 237 /** Returns the account name extracted from the given cursor. */ 238 static String getAccountDisplayName(Cursor cursor) { 239 return cursor.getString(cursor.getColumnIndex(Account.DISPLAY_NAME)); 240 } 241 242 /** Returns the email address extracted from the given cursor. */ 243 private static String getAccountEmailAddress(Cursor cursor) { 244 return cursor.getString(cursor.getColumnIndex(Account.EMAIL_ADDRESS)); 245 } 246 247 /** Returns the unread count extracted from the given cursor. */ 248 private static int getAccountUnreadCount(Cursor cursor) { 249 return cursor.getInt(cursor.getColumnIndex(UNREAD_COUNT)); 250 } 251 252 /** Returns the account position extracted from the given cursor. */ 253 private static int getAccountPosition(Cursor cursor) { 254 return cursor.getInt(cursor.getColumnIndex(ACCOUNT_POSITION)); 255 } 256 257 /** 258 * Load the account list. The resulting cursor contains 259 * - Account info 260 * - # of unread messages in inbox 261 * - The "Combined view" row if there's more than one account. 262 */ 263 @VisibleForTesting 264 static class AccountsLoader extends ThrottlingCursorLoader { 265 private final Context mContext; 266 private final long mAccountId; 267 public AccountsLoader(Context context, long accountId) { 268 // Super class loads a regular account cursor, but we replace it in loadInBackground(). 269 super(context, EmailContent.Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, 270 ORDER_BY); 271 mContext = context; 272 mAccountId = accountId; 273 } 274 275 @Override 276 public Cursor loadInBackground() { 277 final Cursor accountsCursor = super.loadInBackground(); 278 // Use ClosingMatrixCursor so that accountsCursor gets closed too when it's closed. 279 final CursorWithExtras resultCursor 280 = new CursorWithExtras(ADAPTER_PROJECTION, accountsCursor); 281 final int accountPosition = addAccountsToCursor(resultCursor, accountsCursor); 282 addRecentsToCursor(resultCursor, accountPosition); 283 return Utility.CloseTraceCursorWrapper.get(resultCursor); 284 } 285 286 /** Adds the account list [with extra meta data] to the given matrix cursor */ 287 private int addAccountsToCursor(MatrixCursor matrixCursor, Cursor accountCursor) { 288 int accountPosition = UNKNOWN_POSITION; 289 accountCursor.moveToPosition(-1); 290 // Add a header for the accounts 291 String header = 292 mContext.getString(R.string.mailbox_list_account_selector_account_header); 293 addRow(matrixCursor, ROW_TYPE_HEADER, 0L, header, null, 0, UNKNOWN_POSITION); 294 int totalUnread = 0; 295 int currentPosition = 1; 296 while (accountCursor.moveToNext()) { 297 // Add account, with its unread count. 298 final long accountId = accountCursor.getLong(0); 299 final int unread = Mailbox.getUnreadCountByAccountAndMailboxType( 300 mContext, accountId, Mailbox.TYPE_INBOX); 301 final String name = getAccountDisplayName(accountCursor); 302 final String emailAddress = getAccountEmailAddress(accountCursor); 303 addRow(matrixCursor, ROW_TYPE_ACCOUNT, accountId, name, emailAddress, unread, 304 UNKNOWN_POSITION); 305 totalUnread += unread; 306 if (accountId == mAccountId) { 307 accountPosition = currentPosition; 308 } 309 currentPosition++; 310 } 311 // Add "combined view" if more than one account exists 312 final int countAccounts = accountCursor.getCount(); 313 if (countAccounts > 1) { 314 final String name = mContext.getResources().getString( 315 R.string.mailbox_list_account_selector_combined_view); 316 final String accountCount = mContext.getResources().getQuantityString( 317 R.plurals.number_of_accounts, countAccounts, countAccounts); 318 addRow(matrixCursor, ROW_TYPE_ACCOUNT, Account.ACCOUNT_ID_COMBINED_VIEW, 319 name, accountCount, totalUnread, UNKNOWN_POSITION); 320 } 321 return accountPosition; 322 } 323 324 /** 325 * Adds the recent mailbox list to the given cursor. 326 * @param matrixCursor the cursor to add the list to 327 * @param accountPosition the cursor position of the currently selected account 328 */ 329 private void addRecentsToCursor(CursorWithExtras matrixCursor, int accountPosition) { 330 if (mAccountId <= 0L || mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 331 // Currently selected account isn't usable for our purposes 332 return; 333 } 334 String emailAddress = null; 335 if (accountPosition != UNKNOWN_POSITION) { 336 matrixCursor.moveToPosition(accountPosition); 337 emailAddress = 338 matrixCursor.getString(matrixCursor.getColumnIndex(Account.EMAIL_ADDRESS)); 339 } 340 RecentMailboxManager mailboxManager = RecentMailboxManager.getInstance(mContext); 341 ArrayList<Long> recentMailboxes = null; 342 boolean useTwoPane = UiUtilities.useTwoPane(mContext); 343 if (!useTwoPane) { 344 // Do not display recent mailboxes in the account spinner for the two pane view 345 recentMailboxes = mailboxManager.getMostRecent(mAccountId, useTwoPane); 346 } 347 if (recentMailboxes != null && recentMailboxes.size() > 0) { 348 matrixCursor.mRecentCount = recentMailboxes.size(); 349 String mailboxHeader = mContext.getString( 350 R.string.mailbox_list_account_selector_mailbox_header_fmt, emailAddress); 351 addRow(matrixCursor, ROW_TYPE_HEADER, 0L, mailboxHeader, null, 0, UNKNOWN_POSITION); 352 for (long mailboxId : recentMailboxes) { 353 final int unread = Utility.getFirstRowInt(mContext, 354 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 355 new String[] { MailboxColumns.UNREAD_COUNT }, null, null, null, 0); 356 final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId); 357 addRow(matrixCursor, ROW_TYPE_MAILBOX, mailboxId, mailbox.mDisplayName, null, 358 unread, accountPosition); 359 } 360 } 361 if (!useTwoPane) { 362 String name = mContext.getString( 363 R.string.mailbox_list_account_selector_show_all_folders); 364 addRow(matrixCursor, ROW_TYPE_MAILBOX, Mailbox.NO_MAILBOX, name, null, 0, 365 accountPosition); 366 } 367 } 368 369 /** Adds a row to the given cursor */ 370 private void addRow(MatrixCursor cursor, int rowType, long id, String name, 371 String emailAddress, int unreadCount, int listPosition) { 372 cursor.newRow() 373 .add(rowType) 374 .add(id) 375 .add(name) 376 .add(emailAddress) 377 .add(unreadCount) 378 .add(listPosition); 379 } 380 } 381 382 /** Cursor with some extra meta data. */ 383 static class CursorWithExtras extends ClosingMatrixCursor { 384 /** Number of account elements */ 385 final int mAccountCount; 386 /** Number of recent mailbox elements */ 387 int mRecentCount; 388 389 private CursorWithExtras(String[] columnNames, Cursor innerCursor) { 390 super(columnNames, innerCursor); 391 mAccountCount = (innerCursor == null) ? 0 : innerCursor.getCount(); 392 } 393 394 /** 395 * Returns the cursor position of the item with the given ID. Or {@link #UNKNOWN_POSITION} 396 * if the given ID does not exist. 397 */ 398 int getPosition(long id) { 399 moveToPosition(-1); 400 while(moveToNext()) { 401 if (id == getAccountId(this)) { 402 return getPosition(); 403 } 404 } 405 return UNKNOWN_POSITION; 406 } 407 } 408} 409