MailboxFragmentAdapter.java revision f9a9f5289769c88b437155ae84c05d3ada96e727
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 /** Returns {@code true} if the current row is a header */ 236 private static boolean isHeaderRow(Cursor cursor) { 237 return getRowType(cursor) == ROW_TYPE_HEADER; 238 } 239 240 /** 241 * Returns the ID of the given row. It may be a mailbox or account ID depending upon the 242 * result of {@link #isAccountRow}. 243 */ 244 long getId(int position) { 245 Cursor c = (Cursor) getItem(position); 246 return getId(c); 247 } 248 249 /** 250 * Returns the account ID of the mailbox owner for the given row. If the given row is a 251 * combined mailbox, {@link Account#ACCOUNT_ID_COMBINED_VIEW} is returned. If the given 252 * row is an account, returns the account's ID [the same as {@link #ORIGINAL_ID}]. 253 */ 254 long getAccountId(int position) { 255 Cursor c = (Cursor) getItem(position); 256 return getAccountId(c); 257 } 258 259 /** 260 * Turn on and off list updates; during a drag operation, we do NOT want to the list of 261 * mailboxes to update, as this would be visually jarring 262 * @param state whether or not the MailboxList can be updated 263 */ 264 static void enableUpdates(boolean state) { 265 sEnableUpdate = state; 266 } 267 268 private static String getDisplayName(Context context, Cursor cursor) { 269 final String name = cursor.getString(cursor.getColumnIndex(MailboxColumns.DISPLAY_NAME)); 270 if (isHeaderRow(cursor) || isAccountRow(cursor)) { 271 // Always use actual name 272 return name; 273 } else { 274 // Use this method for two purposes: 275 // - Set combined mailbox names 276 // - Rewrite special mailbox names (e.g. trash) 277 FolderProperties fp = FolderProperties.getInstance(context); 278 return fp.getDisplayName(getType(cursor), getId(cursor), name); 279 } 280 } 281 282 static long getId(Cursor cursor) { 283 return cursor.getLong(cursor.getColumnIndex(ORIGINAL_ID)); 284 } 285 286 static int getType(Cursor cursor) { 287 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.TYPE)); 288 } 289 290 static int getMessageCount(Cursor cursor) { 291 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.MESSAGE_COUNT)); 292 } 293 294 static int getUnreadCount(Cursor cursor) { 295 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.UNREAD_COUNT)); 296 } 297 298 static long getAccountId(Cursor cursor) { 299 return cursor.getLong(cursor.getColumnIndex(MailboxColumns.ACCOUNT_KEY)); 300 } 301 302 private static int getRowType(Cursor cursor) { 303 return cursor.getInt(cursor.getColumnIndex(ROW_TYPE)); 304 } 305 306 private static int getFlags(Cursor cursor) { 307 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.FLAGS)); 308 } 309 310 /** 311 * {@link Cursor} with extra information which is returned by the loader created by 312 * {@link MailboxFragmentAdapter#createMailboxesLoader}. 313 */ 314 static class CursorWithExtras extends CursorWrapper { 315 /** 316 * The number of mailboxes in the cursor if the cursor contains top-level mailboxes. 317 * Otherwise, the number of *child* mailboxes. 318 */ 319 public final int mChildCount; 320 321 CursorWithExtras(Cursor cursor, int childCount) { 322 super(cursor); 323 mChildCount = childCount; 324 } 325 } 326 327 private void bindListHeader(View view, Context context, Cursor cursor) { 328 final TextView nameView = (TextView) view.findViewById(R.id.display_name); 329 nameView.setText(getDisplayName(context, cursor)); 330 } 331 332 private void bindListItem(View view, Context context, Cursor cursor) { 333 final boolean isAccount = isAccountRow(cursor); 334 final int type = getType(cursor); 335 final long id = getId(cursor); 336 final long accountId = getAccountId(cursor); 337 final int flags = getFlags(cursor); 338 final int rowType = getRowType(cursor); 339 final boolean hasVisibleChildren = (flags & Mailbox.FLAG_HAS_CHILDREN) != 0 340 && (flags & Mailbox.FLAG_CHILDREN_VISIBLE) != 0; 341 342 MailboxListItem listItem = (MailboxListItem)view; 343 listItem.mMailboxId = isAccountRow(cursor) ? Mailbox.NO_MAILBOX : id; 344 listItem.mMailboxType = type; 345 listItem.mAccountId = accountId; 346 listItem.mIsValidDropTarget = (id >= 0) 347 && !Utility.arrayContains(Mailbox.INVALID_DROP_TARGETS, type) 348 && (flags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) != 0; 349 listItem.mIsNavigable = hasVisibleChildren; 350 351 listItem.mAdapter = this; 352 // Set the background depending on whether we're in drag mode, the mailbox is a valid 353 // target, etc. 354 mCallback.onBind(listItem); 355 356 // Set mailbox name 357 final TextView nameView = (TextView) view.findViewById(R.id.mailbox_name); 358 nameView.setText(getDisplayName(context, cursor)); 359 // Set count 360 final int count; 361 if (isAccountRow(cursor)) { 362 count = getUnreadCount(cursor); 363 } else { 364 FolderProperties fp = FolderProperties.getInstance(context); 365 count = fp.getMessageCount(type, getUnreadCount(cursor), getMessageCount(cursor)); 366 } 367 final TextView countView = (TextView) view.findViewById(R.id.message_count); 368 369 // Set folder icon 370 final ImageView folderIcon = (ImageView) view.findViewById(R.id.folder_icon); 371 folderIcon.setImageDrawable( 372 FolderProperties.getInstance(context).getIcon(type, id, flags)); 373 374 final ImageView mailboxExpandedIcon = 375 (ImageView) view.findViewById(R.id.folder_expanded_icon); 376 switch (rowType) { 377 case ROW_TYPE_SUBMAILBOX: 378 if (hasVisibleChildren) { 379 mailboxExpandedIcon.setVisibility(View.VISIBLE); 380 mailboxExpandedIcon.setImageResource( 381 R.drawable.ic_mailbox_collapsed_holo_light); 382 } else { 383 mailboxExpandedIcon.setVisibility(View.INVISIBLE); 384 mailboxExpandedIcon.setImageDrawable(null); 385 } 386 folderIcon.setVisibility(View.INVISIBLE); 387 break; 388 case ROW_TYPE_CURMAILBOX: 389 mailboxExpandedIcon.setVisibility(View.GONE); 390 mailboxExpandedIcon.setImageDrawable(null); 391 folderIcon.setVisibility(View.GONE); 392 break; 393 case ROW_TYPE_MAILBOX: 394 default: // Includes ROW_TYPE_ACCOUNT 395 if (hasVisibleChildren) { 396 mailboxExpandedIcon.setVisibility(View.VISIBLE); 397 mailboxExpandedIcon.setImageResource( 398 R.drawable.ic_mailbox_collapsed_holo_light); 399 } else { 400 mailboxExpandedIcon.setVisibility(View.GONE); 401 mailboxExpandedIcon.setImageDrawable(null); 402 } 403 folderIcon.setVisibility(View.VISIBLE); 404 break; 405 } 406 407 // If the unread count is zero, not to show countView. 408 if (count > 0) { 409 countView.setVisibility(View.VISIBLE); 410 countView.setText(Integer.toString(count)); 411 } else { 412 countView.setVisibility(View.GONE); 413 } 414 415 final View chipView = view.findViewById(R.id.color_chip); 416 if (isAccount) { 417 chipView.setVisibility(View.VISIBLE); 418 chipView.setBackgroundColor(mResourceHelper.getAccountColor(id)); 419 } else { 420 chipView.setVisibility(View.GONE); 421 } 422 } 423 424 /** 425 * Returns a cursor loader for the mailboxes of the given account. If <code>parentKey</code> 426 * refers to a valid mailbox ID [e.g. non-zero], restrict the loader to only those mailboxes 427 * contained by this parent mailbox. 428 * 429 * Note the returned loader always returns a {@link CursorWithExtras}. 430 */ 431 static Loader<Cursor> createMailboxesLoader(Context context, long accountId, 432 long parentMailboxId) { 433 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 434 Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#CursorWithExtras accountId=" + accountId 435 + " parentMailboxId=" + parentMailboxId); 436 } 437 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 438 throw new IllegalArgumentException(); 439 } 440 return new MailboxFragmentLoader(context, accountId, parentMailboxId); 441 } 442 443 /** 444 * Returns a cursor loader for the combined view. 445 */ 446 static Loader<Cursor> createCombinedViewLoader(Context context) { 447 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 448 Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#createCombinedViewLoader"); 449 } 450 return new CombinedMailboxLoader(context); 451 } 452 453 /** 454 * Adds a new row into the given cursor. 455 */ 456 private static void addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName, 457 int mailboxType, int unreadCount, int messageCount, int rowType, int flags, 458 long accountId) { 459 long listId = mailboxId; 460 if (mailboxId < 0) { 461 listId = Long.MAX_VALUE + mailboxId; // IDs for the list view must be positive 462 } 463 RowBuilder row = cursor.newRow(); 464 row.add(listId); 465 row.add(mailboxId); 466 row.add(displayName); 467 row.add(mailboxType); 468 row.add(unreadCount); 469 row.add(messageCount); 470 row.add(rowType); 471 row.add(flags); 472 row.add(accountId); 473 } 474 475 private static void addCombinedMailboxRow(MatrixCursor cursor, long id, int mailboxType, 476 int count, boolean showAlways) { 477 if (id >= 0) { 478 throw new IllegalArgumentException(); // Must be QUERY_ALL_*, which are all negative 479 } 480 if (showAlways || (count > 0)) { 481 addMailboxRow( 482 cursor, id, "", mailboxType, count, count, ROW_TYPE_MAILBOX, Mailbox.FLAG_NONE, 483 Account.ACCOUNT_ID_COMBINED_VIEW); 484 } 485 } 486 487 /** 488 * Loads mailboxes that are the children of a given mailbox ID. 489 * 490 * The returned {@link Cursor} is always a {@link CursorWithExtras}. 491 */ 492 private static class MailboxFragmentLoader extends ThrottlingCursorLoader { 493 private final Context mContext; 494 private final long mAccountId; 495 private final long mParentKey; 496 497 MailboxFragmentLoader(Context context, long accountId, long parentKey) { 498 super(context, Mailbox.CONTENT_URI, 499 (parentKey != Mailbox.NO_MAILBOX) 500 ? SUBMAILBOX_PROJECTION 501 : PROJECTION, 502 USER_MAILBOX_SELECTION_WITH_PARENT, 503 new String[] { Long.toString(accountId), Long.toString(parentKey) }, 504 MAILBOX_ORDER_BY); 505 mContext = context; 506 mAccountId = accountId; 507 mParentKey = parentKey; 508 } 509 510 @Override 511 public void onContentChanged() { 512 if (sEnableUpdate) { 513 super.onContentChanged(); 514 } 515 } 516 517 @Override 518 public Cursor loadInBackground() { 519 boolean parentRemoved = false; 520 521 final Cursor userMailboxCursor = super.loadInBackground(); 522 final Cursor returnCursor; 523 524 final int childCount = userMailboxCursor.getCount(); 525 526 if (mParentKey != Mailbox.NO_MAILBOX) { 527 // If we're not showing the top level mailboxes, add the "parent" mailbox. 528 final Cursor parentCursor = getContext().getContentResolver().query( 529 Mailbox.CONTENT_URI, CURMAILBOX_PROJECTION, MAILBOX_SELECTION, 530 new String[] { Long.toString(mAccountId), Long.toString(mParentKey) }, 531 null); 532 returnCursor = new MergeCursor(new Cursor[] { parentCursor, userMailboxCursor }); 533 } else { 534 // TODO Add per-account starred mailbox support 535 final MatrixCursor starredCursor = new MatrixCursor(MATRIX_PROJECTION); 536 final Cursor systemMailboxCursor = mContext.getContentResolver().query( 537 Mailbox.CONTENT_URI, PROJECTION, SYSTEM_MAILBOX_SELECTION, 538 new String[] { Long.toString(mAccountId) }, MAILBOX_ORDER_BY); 539 final MatrixCursor recentCursor = new MatrixCursor(MATRIX_PROJECTION); 540 final MatrixCursor headerCursor = new MatrixCursor(MATRIX_PROJECTION); 541 if (childCount > 0) { 542 final String name = mContext.getString(R.string.mailbox_list_user_mailboxes); 543 addMailboxRow(headerCursor, 0L, name, 0, 0, 0, ROW_TYPE_HEADER, 0, 0L); 544 } 545 ArrayList<Long> recentList = null; 546 boolean useTwoPane = UiUtilities.useTwoPane(mContext); 547 if (useTwoPane) { 548 recentList = RecentMailboxManager.getInstance(mContext) 549 .getMostRecent(mAccountId, true); 550 } 551 if (recentList != null && recentList.size() > 0) { 552 final String name = mContext.getString(R.string.mailbox_list_recent_mailboxes); 553 addMailboxRow(recentCursor, 0L, name, 0, 0, 0, ROW_TYPE_HEADER, 0, 0L); 554 for (long mailboxId : recentList) { 555 final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId); 556 if (mailbox == null) continue; 557 final int messageCount = Utility.getFirstRowInt(mContext, 558 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 559 new String[] { MailboxColumns.MESSAGE_COUNT }, null, null, null, 0); 560 final int unreadCount = Utility.getFirstRowInt(mContext, 561 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 562 new String[] { MailboxColumns.UNREAD_COUNT }, null, null, null, 0); 563 addMailboxRow(recentCursor, mailboxId, mailbox.mDisplayName, mailbox.mType, 564 unreadCount, messageCount, ROW_TYPE_MAILBOX, mailbox.mFlags, 565 mailbox.mAccountKey); 566 } 567 } 568 int accountStarredCount = Message.getFavoriteMessageCount(mContext, mAccountId); 569 if (accountStarredCount > 0) { 570 // Only add "Starred", if there is at least one starred message 571 final int totalStarredCount = Message.getFavoriteMessageCount(mContext); 572 addCombinedMailboxRow(starredCursor, Mailbox.QUERY_ALL_FAVORITES, 573 Mailbox.TYPE_MAIL, totalStarredCount, true); 574 } 575 returnCursor = new MergeCursor(new Cursor[] { 576 starredCursor, systemMailboxCursor, recentCursor, headerCursor, 577 userMailboxCursor, }); 578 } 579 return new CursorWithExtras(returnCursor, childCount); 580 } 581 } 582 583 /** 584 * Loader for mailboxes in "Combined view". 585 */ 586 @VisibleForTesting 587 static class CombinedMailboxLoader extends ThrottlingCursorLoader { 588 private static final String[] ACCOUNT_PROJECTION = new String[] { 589 EmailContent.RECORD_ID, AccountColumns.DISPLAY_NAME, 590 }; 591 private static final int COLUMN_ACCOUND_ID = 0; 592 private static final int COLUMN_ACCOUNT_DISPLAY_NAME = 1; 593 594 private final Context mContext; 595 596 private CombinedMailboxLoader(Context context) { 597 super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null); 598 mContext = context; 599 } 600 601 @Override 602 public Cursor loadInBackground() { 603 final Cursor accounts = super.loadInBackground(); 604 605 // Build combined mailbox rows. 606 final MatrixCursor returnCursor = buildCombinedMailboxes(mContext, accounts); 607 608 // Add account rows. 609 accounts.moveToPosition(-1); 610 while (accounts.moveToNext()) { 611 final long accountId = accounts.getLong(COLUMN_ACCOUND_ID); 612 final String accountName = accounts.getString(COLUMN_ACCOUNT_DISPLAY_NAME); 613 final int unreadCount = Mailbox.getUnreadCountByAccountAndMailboxType( 614 mContext, accountId, Mailbox.TYPE_INBOX); 615 addMailboxRow(returnCursor, accountId, accountName, Mailbox.TYPE_NONE, 616 unreadCount, unreadCount, ROW_TYPE_ACCOUNT, Mailbox.FLAG_NONE, 617 accountId); 618 } 619 return returnCursor; 620 } 621 622 @VisibleForTesting 623 static MatrixCursor buildCombinedMailboxes(Context context, Cursor innerCursor) { 624 MatrixCursor cursor = new ClosingMatrixCursor(MATRIX_PROJECTION, innerCursor); 625 // Combined inbox -- show unread count 626 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX, 627 Mailbox.getUnreadCountByMailboxType(context, Mailbox.TYPE_INBOX), true); 628 629 // Favorite (starred) -- show # of favorites 630 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL, 631 Message.getFavoriteMessageCount(context), false); 632 633 // Drafts -- show # of drafts 634 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS, 635 Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_DRAFTS), false); 636 637 // Outbox -- # of outstanding messages 638 addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX, 639 Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_OUTBOX), false); 640 641 return cursor; 642 } 643 } 644} 645