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