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