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