MailboxFragmentAdapter.java revision 80d3875d306c60da83e547c573427627911f8a99
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 count to use. Different mailboxes have different counts. */ 102 private static final int COUNT_TYPE_UNREAD = 0; 103 private static final int COUNT_TYPE_TOTAL = 1; 104 private static final int COUNT_TYPE_NO_COUNT = 2; 105 106 /** The type of data contained in the cursor row. */ 107 private static final String ROW_TYPE = "rowType"; 108 /** The original ID of the cursor row. May be negative. */ 109 private static final String ORIGINAL_ID = "orgMailboxId"; 110 /** 111 * Projection for a typical mailbox or account row. 112 * <p><em>NOTE</em> This projection contains two ID columns. The first, named "_id", is used 113 * by the framework ListView implementation. Since ListView does not handle negative IDs in 114 * this column, we define a "mailbox_id" column that contains the real mailbox ID; which 115 * may be negative for special mailboxes. 116 */ 117 private static final String[] PROJECTION = new String[] { MailboxColumns.ID, 118 MailboxColumns.ID + " AS " + ORIGINAL_ID, 119 MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT, 120 MailboxColumns.MESSAGE_COUNT, ROW_TYPE_MAILBOX + " AS " + ROW_TYPE, 121 MailboxColumns.FLAGS, MailboxColumns.ACCOUNT_KEY }; 122 // STOPSHIP May need to adjust sub-folder projection depending upon final UX 123 /** 124 * Projection used to retrieve immediate children for a mailbox. The columns need to 125 * be identical to those in {@link #PROJECTION}. We are only changing the constant 126 * column {@link #ROW_TYPE}. 127 */ 128 private static final String[] SUBMAILBOX_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_SUBMAILBOX + " AS " + ROW_TYPE, 132 MailboxColumns.FLAGS, MailboxColumns.ACCOUNT_KEY }; 133 private static final String[] CURMAILBOX_PROJECTION = new String[] { MailboxColumns.ID, 134 MailboxColumns.ID + " AS " + ORIGINAL_ID, 135 MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT, 136 MailboxColumns.MESSAGE_COUNT, ROW_TYPE_CURMAILBOX + " AS " + ROW_TYPE, 137 MailboxColumns.FLAGS, MailboxColumns.ACCOUNT_KEY }; 138 /** Project to use for matrix cursors; rows MUST be identical to {@link #PROJECTION} */ 139 private static final String[] MATRIX_PROJECTION = new String[] { 140 MailboxColumns.ID, ORIGINAL_ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, 141 MailboxColumns.UNREAD_COUNT, MailboxColumns.MESSAGE_COUNT, ROW_TYPE, MailboxColumns.FLAGS, 142 MailboxColumns.ACCOUNT_KEY }; 143 144 /** All mailboxes for the account */ 145 private static final String ALL_MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" + 146 " AND " + Mailbox.USER_VISIBLE_MAILBOX_SELECTION; 147 /** All system mailboxes for an account */ 148 private static final String SYSTEM_MAILBOX_SELECTION = ALL_MAILBOX_SELECTION + 149 " AND " + MailboxColumns.TYPE + "!=" + Mailbox.TYPE_MAIL; 150 /** All mailboxes with the given parent */ 151 private static final String USER_MAILBOX_SELECTION_WITH_PARENT = ALL_MAILBOX_SELECTION + 152 " AND " + MailboxColumns.PARENT_KEY + "=?" + 153 " AND " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_MAIL; 154 /** Selection for a specific mailbox */ 155 private static final String MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" + 156 " AND " + MailboxColumns.ID + "=?"; 157 158 private static final String MAILBOX_ORDER_BY = "CASE " + MailboxColumns.TYPE + 159 " WHEN " + Mailbox.TYPE_INBOX + " THEN 0" + 160 " WHEN " + Mailbox.TYPE_DRAFTS + " THEN 1" + 161 " WHEN " + Mailbox.TYPE_OUTBOX + " THEN 2" + 162 " WHEN " + Mailbox.TYPE_SENT + " THEN 3" + 163 " WHEN " + Mailbox.TYPE_TRASH + " THEN 4" + 164 " WHEN " + Mailbox.TYPE_JUNK + " THEN 5" + 165 // Other mailboxes (i.e. of Mailbox.TYPE_MAIL) are shown in alphabetical order. 166 " ELSE 10 END" + 167 " ," + MailboxColumns.DISPLAY_NAME; 168 169 /** View is of a "normal" row */ 170 private static final int ITEM_VIEW_TYPE_NORMAL = 0; 171 /** View is of a separator row */ 172 private static final int ITEM_VIEW_TYPE_HEADER = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; 173 174 private static boolean sEnableUpdate = true; 175 private final LayoutInflater mInflater; 176 private final ResourceHelper mResourceHelper; 177 private final Callback mCallback; 178 179 public MailboxFragmentAdapter(Context context, Callback callback) { 180 super(context, null, 0 /* flags; no content observer */); 181 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 182 mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback; 183 mResourceHelper = ResourceHelper.getInstance(context); 184 } 185 186 @Override 187 public int getViewTypeCount() { 188 return 2; 189 } 190 191 @Override 192 public int getItemViewType(int position) { 193 return isHeader(position) ? ITEM_VIEW_TYPE_HEADER : ITEM_VIEW_TYPE_NORMAL; 194 } 195 196 @Override 197 public boolean isEnabled(int position) { 198 return !isHeader(position); 199 } 200 201 @Override 202 public void bindView(View view, Context context, Cursor cursor) { 203 if (view instanceof MailboxListItem) { 204 bindListItem(view, context, cursor); 205 } else { 206 bindListHeader(view, context, cursor); 207 } 208 } 209 210 @Override 211 public View newView(Context context, Cursor cursor, ViewGroup parent) { 212 if (cursor.getInt(cursor.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) { 213 return mInflater.inflate(R.layout.mailbox_list_header, parent, false); 214 } 215 return mInflater.inflate(R.layout.mailbox_list_item, parent, false); 216 } 217 218 private boolean isHeader(int position) { 219 Cursor c = getCursor(); 220 c.moveToPosition(position); 221 int rowType = c.getInt(c.getColumnIndex(ROW_TYPE)); 222 return rowType == ROW_TYPE_HEADER; 223 } 224 225 /** Returns {@code true} if the specified row is of an account in the combined view. */ 226 boolean isAccountRow(int position) { 227 return isAccountRow((Cursor) getItem(position)); 228 } 229 230 /** Returns {@code true} if the current row is of an account in the combined view. */ 231 private static boolean isAccountRow(Cursor cursor) { 232 return getRowType(cursor) == ROW_TYPE_ACCOUNT; 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 (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(MatrixCursor cursor, long id, int mailboxType, 471 int count, boolean showAlways) { 472 if (id >= 0) { 473 throw new IllegalArgumentException(); // Must be QUERY_ALL_*, which are all negative 474 } 475 if (showAlways || (count > 0)) { 476 addMailboxRow( 477 cursor, id, "", mailboxType, count, count, ROW_TYPE_MAILBOX, Mailbox.FLAG_NONE, 478 Account.ACCOUNT_ID_COMBINED_VIEW); 479 } 480 } 481 482 /** 483 * Loads mailboxes that are the children of a given mailbox ID. 484 * 485 * The returned {@link Cursor} is always a {@link CursorWithExtras}. 486 */ 487 private static class MailboxFragmentLoader extends ThrottlingCursorLoader { 488 private final Context mContext; 489 private final long mAccountId; 490 private final long mParentKey; 491 492 MailboxFragmentLoader(Context context, long accountId, long parentKey) { 493 super(context, Mailbox.CONTENT_URI, 494 (parentKey != Mailbox.NO_MAILBOX) 495 ? SUBMAILBOX_PROJECTION 496 : PROJECTION, 497 USER_MAILBOX_SELECTION_WITH_PARENT, 498 new String[] { Long.toString(accountId), Long.toString(parentKey) }, 499 MAILBOX_ORDER_BY); 500 mContext = context; 501 mAccountId = accountId; 502 mParentKey = parentKey; 503 } 504 505 @Override 506 public void onContentChanged() { 507 if (sEnableUpdate) { 508 super.onContentChanged(); 509 } 510 } 511 512 @Override 513 public Cursor loadInBackground() { 514 boolean parentRemoved = false; 515 516 final Cursor userMailboxCursor = super.loadInBackground(); 517 final Cursor returnCursor; 518 519 final int childCount = userMailboxCursor.getCount(); 520 521 if (mParentKey != Mailbox.NO_MAILBOX) { 522 // If we're not showing the top level mailboxes, add the "parent" mailbox. 523 final Cursor parentCursor = getContext().getContentResolver().query( 524 Mailbox.CONTENT_URI, CURMAILBOX_PROJECTION, MAILBOX_SELECTION, 525 new String[] { Long.toString(mAccountId), Long.toString(mParentKey) }, 526 null); 527 returnCursor = new MergeCursor(new Cursor[] { parentCursor, userMailboxCursor }); 528 } else { 529 // TODO Add per-account starred mailbox support 530 final MatrixCursor starredCursor = new MatrixCursor(MATRIX_PROJECTION); 531 final Cursor systemMailboxCursor = mContext.getContentResolver().query( 532 Mailbox.CONTENT_URI, PROJECTION, SYSTEM_MAILBOX_SELECTION, 533 new String[] { Long.toString(mAccountId) }, MAILBOX_ORDER_BY); 534 final MatrixCursor recentCursor = new MatrixCursor(MATRIX_PROJECTION); 535 final MatrixCursor headerCursor = new MatrixCursor(MATRIX_PROJECTION); 536 if (childCount > 0) { 537 final String name = mContext.getString(R.string.mailbox_list_user_mailboxes); 538 addMailboxRow(headerCursor, 0L, name, 0, 0, 0, ROW_TYPE_HEADER, 0, 0L); 539 } 540 ArrayList<Long> recentList = null; 541 boolean useTwoPane = UiUtilities.useTwoPane(mContext); 542 if (useTwoPane) { 543 recentList = RecentMailboxManager.getInstance(mContext) 544 .getMostRecent(mAccountId, true); 545 } 546 if (recentList != null && recentList.size() > 0) { 547 final String name = mContext.getString(R.string.mailbox_list_recent_mailboxes); 548 addMailboxRow(recentCursor, 0L, name, 0, 0, 0, ROW_TYPE_HEADER, 0, 0L); 549 for (long mailboxId : recentList) { 550 final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId); 551 if (mailbox == null) continue; 552 final int messageCount = Utility.getFirstRowInt(mContext, 553 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 554 new String[] { MailboxColumns.MESSAGE_COUNT }, null, null, null, 0); 555 final int unreadCount = Utility.getFirstRowInt(mContext, 556 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 557 new String[] { MailboxColumns.UNREAD_COUNT }, null, null, null, 0); 558 addMailboxRow(recentCursor, mailboxId, mailbox.mDisplayName, mailbox.mType, 559 unreadCount, messageCount, ROW_TYPE_MAILBOX, mailbox.mFlags, 560 mailbox.mAccountKey); 561 } 562 } 563 int accountStarredCount = Message.getFavoriteMessageCount(mContext, mAccountId); 564 if (accountStarredCount > 0) { 565 // Only add "Starred", if there is at least one starred message 566 final int totalStarredCount = Message.getFavoriteMessageCount(mContext); 567 addCombinedMailboxRow(starredCursor, Mailbox.QUERY_ALL_FAVORITES, 568 Mailbox.TYPE_MAIL, totalStarredCount, 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 context, Cursor innerCursor) { 619 MatrixCursor cursor = new ClosingMatrixCursor(MATRIX_PROJECTION, innerCursor); 620 // Combined inbox -- show unread count 621 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX, 622 Mailbox.getUnreadCountByMailboxType(context, Mailbox.TYPE_INBOX), true); 623 624 // Favorite (starred) -- show # of favorites 625 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL, 626 Message.getFavoriteMessageCount(context), false); 627 628 // Drafts -- show # of drafts 629 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS, 630 Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_DRAFTS), false); 631 632 // Outbox -- # of outstanding messages 633 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX, 634 Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_OUTBOX), false); 635 636 return cursor; 637 } 638 } 639} 640