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