MailboxFragmentAdapter.java revision a6212c88e0fb5906cb4065c775c1f4c0b83b7ca4
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.Email; 20import com.android.email.FolderProperties; 21import com.android.email.R; 22import com.android.email.ResourceHelper; 23import com.android.email.data.ClosingMatrixCursor; 24import com.android.email.data.ThrottlingCursorLoader; 25import com.android.emailcommon.Logging; 26import com.android.emailcommon.provider.Account; 27import com.android.emailcommon.provider.EmailContent; 28import com.android.emailcommon.provider.EmailContent.AccountColumns; 29import com.android.emailcommon.provider.EmailContent.MailboxColumns; 30import com.android.emailcommon.provider.EmailContent.Message; 31import com.android.emailcommon.provider.Mailbox; 32import com.android.emailcommon.utility.Utility; 33import com.google.common.annotations.VisibleForTesting; 34 35import android.content.ContentUris; 36import android.content.Context; 37import android.content.Loader; 38import android.database.Cursor; 39import android.database.CursorWrapper; 40import android.database.MatrixCursor; 41import android.database.MatrixCursor.RowBuilder; 42import android.database.MergeCursor; 43import android.util.Log; 44import android.view.LayoutInflater; 45import android.view.View; 46import android.view.ViewGroup; 47import android.widget.AdapterView; 48import android.widget.CursorAdapter; 49import android.widget.ImageView; 50import android.widget.TextView; 51 52import java.util.ArrayList; 53 54/** 55 * Mailbox cursor adapter for the mailbox list fragment. 56 * 57 * A mailbox cursor may contain one of several different types of data. Currently, this 58 * adapter supports the following views: 59 * 1. The standard inbox, mailbox view 60 * 2. The combined mailbox view 61 * 3. Nested folder navigation 62 * 63 * TODO At a minimum, we should break out the loaders. They have no relation to the view code 64 * and only serve to confuse the user. 65 * TODO Determine if we actually need a separate adapter / view / loader for nested folder 66 * navigation. It's a little convoluted at the moment, but, still manageable. 67 */ 68class MailboxFragmentAdapter extends CursorAdapter { 69 /** 70 * Callback interface used to report clicks other than the basic list item click or long press. 71 */ 72 interface Callback { 73 /** Callback for setting background of mailbox list items during a drag */ 74 public void onBind(MailboxListItem listItem); 75 } 76 77 /** Do-nothing callback to avoid null tests for <code>mCallback</code>. */ 78 private static final class EmptyCallback implements Callback { 79 public static final Callback INSTANCE = new EmptyCallback(); 80 @Override public void onBind(MailboxListItem listItem) { } 81 } 82 83 /* 84 * The type of the row to present to the user. There are 4 defined rows that each 85 * have a slightly different look. These are typically used in the constant column 86 * {@link #ROW_TYPE} specified in {@link #PROJECTION} and {@link #SUBMAILBOX_PROJECTION}. 87 */ 88 /** Both regular and combined mailboxes */ 89 private static final int ROW_TYPE_MAILBOX = 0; 90 /** Account "mailboxes" in the combined view */ 91 private static final int ROW_TYPE_ACCOUNT = 1; 92 // STOPSHIP Need to determine if these types are sufficient for nested folders 93 // The following types are used when drilling into a mailbox 94 /** The current mailbox */ 95 private static final int ROW_TYPE_CURMAILBOX = 2; 96 /** Sub mailboxes */ 97 private static final int ROW_TYPE_SUBMAILBOX = 3; 98 /** Header */ 99 private static final int ROW_TYPE_HEADER = 4; 100 101 /** The type of data contained in the cursor row. */ 102 private static final String ROW_TYPE = "rowType"; 103 /** The original ID of the cursor row. May be negative. */ 104 private static final String ORIGINAL_ID = "orgMailboxId"; 105 /** 106 * Projection for a typical mailbox or account row. 107 * <p><em>NOTE</em> This projection contains two ID columns. The first, named "_id", is used 108 * by the framework ListView implementation. Since ListView does not handle negative IDs in 109 * this column, we define a "mailbox_id" column that contains the real mailbox ID; which 110 * may be negative for special mailboxes. 111 */ 112 private static final String[] PROJECTION = new String[] { MailboxColumns.ID, 113 MailboxColumns.ID + " AS " + ORIGINAL_ID, 114 MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT, 115 MailboxColumns.MESSAGE_COUNT, ROW_TYPE_MAILBOX + " AS " + ROW_TYPE, 116 MailboxColumns.FLAGS, MailboxColumns.ACCOUNT_KEY }; 117 // STOPSHIP May need to adjust sub-folder projection depending upon final UX 118 /** 119 * Projection used to retrieve immediate children for a mailbox. The columns need to 120 * be identical to those in {@link #PROJECTION}. We are only changing the constant 121 * column {@link #ROW_TYPE}. 122 */ 123 private static final String[] SUBMAILBOX_PROJECTION = new String[] { MailboxColumns.ID, 124 MailboxColumns.ID + " AS " + ORIGINAL_ID, 125 MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT, 126 MailboxColumns.MESSAGE_COUNT, ROW_TYPE_SUBMAILBOX + " AS " + ROW_TYPE, 127 MailboxColumns.FLAGS, MailboxColumns.ACCOUNT_KEY }; 128 private static final String[] CURMAILBOX_PROJECTION = new String[] { MailboxColumns.ID, 129 MailboxColumns.ID + " AS " + ORIGINAL_ID, 130 MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT, 131 MailboxColumns.MESSAGE_COUNT, ROW_TYPE_CURMAILBOX + " AS " + ROW_TYPE, 132 MailboxColumns.FLAGS, MailboxColumns.ACCOUNT_KEY }; 133 /** Project to use for matrix cursors; rows MUST be identical to {@link #PROJECTION} */ 134 private static final String[] MATRIX_PROJECTION = new String[] { 135 MailboxColumns.ID, ORIGINAL_ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, 136 MailboxColumns.UNREAD_COUNT, MailboxColumns.MESSAGE_COUNT, ROW_TYPE, MailboxColumns.FLAGS, 137 MailboxColumns.ACCOUNT_KEY }; 138 139 /** All mailboxes for the account */ 140 private static final String ALL_MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" + 141 " AND " + Mailbox.USER_VISIBLE_MAILBOX_SELECTION; 142 /** All system mailboxes for an account */ 143 private static final String SYSTEM_MAILBOX_SELECTION = ALL_MAILBOX_SELECTION 144 + " AND " + MailboxColumns.TYPE + "!=" + Mailbox.TYPE_MAIL; 145 /** All mailboxes with the given parent */ 146 private static final String USER_MAILBOX_SELECTION_WITH_PARENT = ALL_MAILBOX_SELECTION 147 + " AND " + MailboxColumns.PARENT_KEY + "=?" 148 + " AND " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_MAIL; 149 /** Selection for a specific mailbox */ 150 private static final String MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" 151 + " AND " + MailboxColumns.ID + "=?"; 152 153 private static final String MAILBOX_ORDER_BY = "CASE " + MailboxColumns.TYPE 154 + " WHEN " + Mailbox.TYPE_INBOX + " THEN 0" 155 + " WHEN " + Mailbox.TYPE_DRAFTS + " THEN 1" 156 + " WHEN " + Mailbox.TYPE_OUTBOX + " THEN 2" 157 + " WHEN " + Mailbox.TYPE_SENT + " THEN 3" 158 + " WHEN " + Mailbox.TYPE_TRASH + " THEN 4" 159 + " WHEN " + Mailbox.TYPE_JUNK + " THEN 5" 160 // Other mailboxes (i.e. of Mailbox.TYPE_MAIL) are shown in alphabetical order. 161 + " ELSE 10 END" 162 + " ," + MailboxColumns.DISPLAY_NAME; 163 164 /** View is of a "normal" row */ 165 private static final int ITEM_VIEW_TYPE_NORMAL = 0; 166 /** View is of a separator row */ 167 private static final int ITEM_VIEW_TYPE_HEADER = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; 168 169 private static boolean sEnableUpdate = true; 170 private final LayoutInflater mInflater; 171 private final ResourceHelper mResourceHelper; 172 private final Callback mCallback; 173 174 public MailboxFragmentAdapter(Context context, Callback callback) { 175 super(context, null, 0 /* flags; no content observer */); 176 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 177 mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback; 178 mResourceHelper = ResourceHelper.getInstance(context); 179 } 180 181 @Override 182 public int getViewTypeCount() { 183 return 2; 184 } 185 186 @Override 187 public int getItemViewType(int position) { 188 return isHeader(position) ? ITEM_VIEW_TYPE_HEADER : ITEM_VIEW_TYPE_NORMAL; 189 } 190 191 @Override 192 public boolean isEnabled(int position) { 193 return !isHeader(position); 194 } 195 196 @Override 197 public void bindView(View view, Context context, Cursor cursor) { 198 if (view instanceof MailboxListItem) { 199 bindListItem(view, context, cursor); 200 } else { 201 bindListHeader(view, context, cursor); 202 } 203 } 204 205 @Override 206 public View newView(Context context, Cursor cursor, ViewGroup parent) { 207 if (cursor.getInt(cursor.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) { 208 return mInflater.inflate(R.layout.mailbox_list_header, parent, false); 209 } 210 return mInflater.inflate(R.layout.mailbox_list_item, parent, false); 211 } 212 213 private boolean isHeader(int position) { 214 Cursor c = getCursor(); 215 c.moveToPosition(position); 216 int rowType = c.getInt(c.getColumnIndex(ROW_TYPE)); 217 return rowType == ROW_TYPE_HEADER; 218 } 219 220 /** Returns {@code true} if the specified row is of an account in the combined view. */ 221 boolean isAccountRow(int position) { 222 return isAccountRow((Cursor) getItem(position)); 223 } 224 225 /** Returns {@code true} if the current row is of an account in the combined view. */ 226 private static boolean isAccountRow(Cursor cursor) { 227 return getRowType(cursor) == ROW_TYPE_ACCOUNT; 228 } 229 230 /** Returns {@code true} if the current row is a header */ 231 private static boolean isHeaderRow(Cursor cursor) { 232 return getRowType(cursor) == ROW_TYPE_HEADER; 233 } 234 235 /** 236 * Returns the ID of the given row. It may be a mailbox or account ID depending upon the 237 * result of {@link #isAccountRow}. 238 */ 239 long getId(int position) { 240 Cursor c = (Cursor) getItem(position); 241 return getId(c); 242 } 243 244 /** 245 * Returns the account ID of the mailbox owner for the given row. If the given row is a 246 * combined mailbox, {@link Account#ACCOUNT_ID_COMBINED_VIEW} is returned. If the given 247 * row is an account, returns the account's ID [the same as {@link #ORIGINAL_ID}]. 248 */ 249 long getAccountId(int position) { 250 Cursor c = (Cursor) getItem(position); 251 return getAccountId(c); 252 } 253 254 /** 255 * Turn on and off list updates; during a drag operation, we do NOT want to the list of 256 * mailboxes to update, as this would be visually jarring 257 * @param state whether or not the MailboxList can be updated 258 */ 259 static void enableUpdates(boolean state) { 260 sEnableUpdate = state; 261 } 262 263 private static String getDisplayName(Context context, Cursor cursor) { 264 final String name = cursor.getString(cursor.getColumnIndex(MailboxColumns.DISPLAY_NAME)); 265 if (isHeaderRow(cursor) || isAccountRow(cursor)) { 266 // Always use actual name 267 return name; 268 } else { 269 // Use this method for two purposes: 270 // - Set combined mailbox names 271 // - Rewrite special mailbox names (e.g. trash) 272 FolderProperties fp = FolderProperties.getInstance(context); 273 return fp.getDisplayName(getType(cursor), getId(cursor), name); 274 } 275 } 276 277 static long getId(Cursor cursor) { 278 return cursor.getLong(cursor.getColumnIndex(ORIGINAL_ID)); 279 } 280 281 static int getType(Cursor cursor) { 282 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.TYPE)); 283 } 284 285 static int getMessageCount(Cursor cursor) { 286 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.MESSAGE_COUNT)); 287 } 288 289 static int getUnreadCount(Cursor cursor) { 290 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.UNREAD_COUNT)); 291 } 292 293 static long getAccountId(Cursor cursor) { 294 return cursor.getLong(cursor.getColumnIndex(MailboxColumns.ACCOUNT_KEY)); 295 } 296 297 private static int getRowType(Cursor cursor) { 298 return cursor.getInt(cursor.getColumnIndex(ROW_TYPE)); 299 } 300 301 private static int getFlags(Cursor cursor) { 302 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.FLAGS)); 303 } 304 305 /** 306 * {@link Cursor} with extra information which is returned by the loader created by 307 * {@link MailboxFragmentAdapter#createMailboxesLoader}. 308 */ 309 static class CursorWithExtras extends CursorWrapper { 310 /** 311 * The number of mailboxes in the cursor if the cursor contains top-level mailboxes. 312 * Otherwise, the number of *child* mailboxes. 313 */ 314 public final int mChildCount; 315 316 CursorWithExtras(Cursor cursor, int childCount) { 317 super(cursor); 318 mChildCount = childCount; 319 } 320 } 321 322 private void bindListHeader(View view, Context context, Cursor cursor) { 323 final TextView nameView = (TextView) view.findViewById(R.id.display_name); 324 nameView.setText(getDisplayName(context, cursor)); 325 } 326 327 private void bindListItem(View view, Context context, Cursor cursor) { 328 final boolean isAccount = isAccountRow(cursor); 329 final int type = getType(cursor); 330 final long id = getId(cursor); 331 final long accountId = getAccountId(cursor); 332 final int flags = getFlags(cursor); 333 final int rowType = getRowType(cursor); 334 final boolean hasVisibleChildren = (flags & Mailbox.FLAG_HAS_CHILDREN) != 0 335 && (flags & Mailbox.FLAG_CHILDREN_VISIBLE) != 0; 336 337 MailboxListItem listItem = (MailboxListItem)view; 338 listItem.mMailboxId = isAccountRow(cursor) ? Mailbox.NO_MAILBOX : id; 339 listItem.mMailboxType = type; 340 listItem.mAccountId = accountId; 341 listItem.mIsValidDropTarget = (id >= 0) 342 && !Utility.arrayContains(Mailbox.INVALID_DROP_TARGETS, type) 343 && (flags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) != 0; 344 listItem.mIsNavigable = hasVisibleChildren; 345 346 listItem.mAdapter = this; 347 // Set the background depending on whether we're in drag mode, the mailbox is a valid 348 // target, etc. 349 mCallback.onBind(listItem); 350 351 // Set mailbox name 352 final TextView nameView = (TextView) view.findViewById(R.id.mailbox_name); 353 nameView.setText(getDisplayName(context, cursor)); 354 // Set count 355 final int count; 356 if (isAccountRow(cursor)) { 357 count = getUnreadCount(cursor); 358 } else { 359 FolderProperties fp = FolderProperties.getInstance(context); 360 count = fp.getMessageCount(type, getUnreadCount(cursor), getMessageCount(cursor)); 361 } 362 final TextView countView = (TextView) view.findViewById(R.id.message_count); 363 364 // Set folder icon 365 final ImageView folderIcon = (ImageView) view.findViewById(R.id.folder_icon); 366 folderIcon.setImageDrawable( 367 FolderProperties.getInstance(context).getIcon(type, id, flags)); 368 369 final ImageView mailboxExpandedIcon = 370 (ImageView) view.findViewById(R.id.folder_expanded_icon); 371 switch (rowType) { 372 case ROW_TYPE_SUBMAILBOX: 373 if (hasVisibleChildren) { 374 mailboxExpandedIcon.setVisibility(View.VISIBLE); 375 mailboxExpandedIcon.setImageResource( 376 R.drawable.ic_mailbox_collapsed_holo_light); 377 } else { 378 mailboxExpandedIcon.setVisibility(View.INVISIBLE); 379 mailboxExpandedIcon.setImageDrawable(null); 380 } 381 folderIcon.setVisibility(View.INVISIBLE); 382 break; 383 case ROW_TYPE_CURMAILBOX: 384 mailboxExpandedIcon.setVisibility(View.GONE); 385 mailboxExpandedIcon.setImageDrawable(null); 386 folderIcon.setVisibility(View.GONE); 387 break; 388 case ROW_TYPE_MAILBOX: 389 default: // Includes ROW_TYPE_ACCOUNT 390 if (hasVisibleChildren) { 391 mailboxExpandedIcon.setVisibility(View.VISIBLE); 392 mailboxExpandedIcon.setImageResource( 393 R.drawable.ic_mailbox_collapsed_holo_light); 394 } else { 395 mailboxExpandedIcon.setVisibility(View.GONE); 396 mailboxExpandedIcon.setImageDrawable(null); 397 } 398 folderIcon.setVisibility(View.VISIBLE); 399 break; 400 } 401 402 // If the unread count is zero, not to show countView. 403 if (count > 0) { 404 countView.setVisibility(View.VISIBLE); 405 countView.setText(Integer.toString(count)); 406 } else { 407 countView.setVisibility(View.GONE); 408 } 409 410 final View chipView = view.findViewById(R.id.color_chip); 411 if (isAccount) { 412 chipView.setVisibility(View.VISIBLE); 413 chipView.setBackgroundColor(mResourceHelper.getAccountColor(id)); 414 } else { 415 chipView.setVisibility(View.GONE); 416 } 417 } 418 419 /** 420 * Returns a cursor loader for the mailboxes of the given account. If <code>parentKey</code> 421 * refers to a valid mailbox ID [e.g. non-zero], restrict the loader to only those mailboxes 422 * contained by this parent mailbox. 423 * 424 * Note the returned loader always returns a {@link CursorWithExtras}. 425 */ 426 static Loader<Cursor> createMailboxesLoader(Context context, long accountId, 427 long parentMailboxId) { 428 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 429 Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#CursorWithExtras accountId=" + accountId 430 + " parentMailboxId=" + parentMailboxId); 431 } 432 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 433 throw new IllegalArgumentException(); 434 } 435 return new MailboxFragmentLoader(context, accountId, parentMailboxId); 436 } 437 438 /** 439 * Returns a cursor loader for the combined view. 440 */ 441 static Loader<Cursor> createCombinedViewLoader(Context context) { 442 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 443 Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#createCombinedViewLoader"); 444 } 445 return new CombinedMailboxLoader(context); 446 } 447 448 /** 449 * Adds a new row into the given cursor. 450 */ 451 private static void addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName, 452 int mailboxType, int unreadCount, int messageCount, int rowType, int flags, 453 long accountId) { 454 long listId = mailboxId; 455 if (mailboxId < 0) { 456 listId = Long.MAX_VALUE + mailboxId; // IDs for the list view must be positive 457 } 458 RowBuilder row = cursor.newRow(); 459 row.add(listId); 460 row.add(mailboxId); 461 row.add(displayName); 462 row.add(mailboxType); 463 row.add(unreadCount); 464 row.add(messageCount); 465 row.add(rowType); 466 row.add(flags); 467 row.add(accountId); 468 } 469 470 private static void addCombinedMailboxRow(Context context, MatrixCursor cursor, long id, 471 int mailboxType, boolean showAlways) { 472 if (id >= 0) { 473 throw new IllegalArgumentException(); // Must be QUERY_ALL_*, which are all negative 474 } 475 int count = FolderProperties.getMessageCountForCombinedMailbox(context, id); 476 if (showAlways || (count > 0)) { 477 addMailboxRow( 478 cursor, id, "", mailboxType, count, count, ROW_TYPE_MAILBOX, Mailbox.FLAG_NONE, 479 Account.ACCOUNT_ID_COMBINED_VIEW); 480 } 481 } 482 483 /** 484 * Loads mailboxes that are the children of a given mailbox ID. 485 * 486 * The returned {@link Cursor} is always a {@link CursorWithExtras}. 487 */ 488 private static class MailboxFragmentLoader extends ThrottlingCursorLoader { 489 private final Context mContext; 490 private final long mAccountId; 491 private final long mParentKey; 492 493 MailboxFragmentLoader(Context context, long accountId, long parentKey) { 494 super(context, Mailbox.CONTENT_URI, 495 (parentKey != Mailbox.NO_MAILBOX) 496 ? SUBMAILBOX_PROJECTION 497 : PROJECTION, 498 USER_MAILBOX_SELECTION_WITH_PARENT, 499 new String[] { Long.toString(accountId), Long.toString(parentKey) }, 500 MAILBOX_ORDER_BY); 501 mContext = context; 502 mAccountId = accountId; 503 mParentKey = parentKey; 504 } 505 506 @Override 507 public void onContentChanged() { 508 if (sEnableUpdate) { 509 super.onContentChanged(); 510 } 511 } 512 513 @Override 514 public Cursor loadInBackground() { 515 boolean parentRemoved = false; 516 517 final Cursor userMailboxCursor = super.loadInBackground(); 518 final Cursor returnCursor; 519 520 final int childCount = userMailboxCursor.getCount(); 521 522 if (mParentKey != Mailbox.NO_MAILBOX) { 523 // If we're not showing the top level mailboxes, add the "parent" mailbox. 524 final Cursor parentCursor = getContext().getContentResolver().query( 525 Mailbox.CONTENT_URI, CURMAILBOX_PROJECTION, MAILBOX_SELECTION, 526 new String[] { Long.toString(mAccountId), Long.toString(mParentKey) }, 527 null); 528 returnCursor = new MergeCursor(new Cursor[] { parentCursor, userMailboxCursor }); 529 } else { 530 // TODO Add per-account starred mailbox support 531 final MatrixCursor starredCursor = new MatrixCursor(MATRIX_PROJECTION); 532 final Cursor systemMailboxCursor = mContext.getContentResolver().query( 533 Mailbox.CONTENT_URI, PROJECTION, SYSTEM_MAILBOX_SELECTION, 534 new String[] { Long.toString(mAccountId) }, MAILBOX_ORDER_BY); 535 final MatrixCursor recentCursor = new MatrixCursor(MATRIX_PROJECTION); 536 final MatrixCursor headerCursor = new MatrixCursor(MATRIX_PROJECTION); 537 if (childCount > 0) { 538 final String name = mContext.getString(R.string.mailbox_list_user_mailboxes); 539 addMailboxRow(headerCursor, 0L, name, 0, 0, 0, ROW_TYPE_HEADER, 0, 0L); 540 } 541 ArrayList<Long> recentList = null; 542 boolean useTwoPane = UiUtilities.useTwoPane(mContext); 543 if (useTwoPane) { 544 recentList = RecentMailboxManager.getInstance(mContext) 545 .getMostRecent(mAccountId, true); 546 } 547 if (recentList != null && recentList.size() > 0) { 548 final String name = mContext.getString(R.string.mailbox_list_recent_mailboxes); 549 addMailboxRow(recentCursor, 0L, name, 0, 0, 0, ROW_TYPE_HEADER, 0, 0L); 550 for (long mailboxId : recentList) { 551 final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId); 552 if (mailbox == null) continue; 553 final int messageCount = Utility.getFirstRowInt(mContext, 554 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 555 new String[] { MailboxColumns.MESSAGE_COUNT }, null, null, null, 0); 556 final int unreadCount = Utility.getFirstRowInt(mContext, 557 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 558 new String[] { MailboxColumns.UNREAD_COUNT }, null, null, null, 0); 559 addMailboxRow(recentCursor, mailboxId, mailbox.mDisplayName, mailbox.mType, 560 unreadCount, messageCount, ROW_TYPE_MAILBOX, mailbox.mFlags, 561 mailbox.mAccountKey); 562 } 563 } 564 int accountStarredCount = Message.getFavoriteMessageCount(mContext, mAccountId); 565 if (accountStarredCount > 0) { 566 // Only add "Starred", if there is at least one starred message 567 addCombinedMailboxRow(mContext, starredCursor, Mailbox.QUERY_ALL_FAVORITES, 568 Mailbox.TYPE_MAIL, true); 569 } 570 returnCursor = new MergeCursor(new Cursor[] { 571 starredCursor, systemMailboxCursor, recentCursor, headerCursor, 572 userMailboxCursor, }); 573 } 574 return new CursorWithExtras(returnCursor, childCount); 575 } 576 } 577 578 /** 579 * Loader for mailboxes in "Combined view". 580 */ 581 @VisibleForTesting 582 static class CombinedMailboxLoader extends ThrottlingCursorLoader { 583 private static final String[] ACCOUNT_PROJECTION = new String[] { 584 EmailContent.RECORD_ID, AccountColumns.DISPLAY_NAME, 585 }; 586 private static final int COLUMN_ACCOUND_ID = 0; 587 private static final int COLUMN_ACCOUNT_DISPLAY_NAME = 1; 588 589 private final Context mContext; 590 591 private CombinedMailboxLoader(Context context) { 592 super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null); 593 mContext = context; 594 } 595 596 @Override 597 public Cursor loadInBackground() { 598 final Cursor accounts = super.loadInBackground(); 599 600 // Build combined mailbox rows. 601 final MatrixCursor returnCursor = buildCombinedMailboxes(mContext, accounts); 602 603 // Add account rows. 604 accounts.moveToPosition(-1); 605 while (accounts.moveToNext()) { 606 final long accountId = accounts.getLong(COLUMN_ACCOUND_ID); 607 final String accountName = accounts.getString(COLUMN_ACCOUNT_DISPLAY_NAME); 608 final int unreadCount = Mailbox.getUnreadCountByAccountAndMailboxType( 609 mContext, accountId, Mailbox.TYPE_INBOX); 610 addMailboxRow(returnCursor, accountId, accountName, Mailbox.TYPE_NONE, 611 unreadCount, unreadCount, ROW_TYPE_ACCOUNT, Mailbox.FLAG_NONE, 612 accountId); 613 } 614 return returnCursor; 615 } 616 617 @VisibleForTesting 618 static MatrixCursor buildCombinedMailboxes(Context c, Cursor innerCursor) { 619 MatrixCursor cursor = new ClosingMatrixCursor(MATRIX_PROJECTION, innerCursor); 620 // Combined inbox -- show unread count 621 addCombinedMailboxRow(c, cursor, Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX, true); 622 623 // Favorite (starred) -- show # of favorites 624 addCombinedMailboxRow(c, cursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL, false); 625 626 // Drafts -- show # of drafts 627 addCombinedMailboxRow(c, cursor, Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS, false); 628 629 // Outbox -- # of outstanding messages 630 addCombinedMailboxRow(c, cursor, Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX, false); 631 632 return cursor; 633 } 634 } 635} 636