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 */ onBind(MailboxListItem listItem)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(); onBind(MailboxListItem listItem)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 + " COLLATE LOCALIZED ASC"; 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 MailboxFragmentAdapter(Context context, Callback callback)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 getViewTypeCount()180 public int getViewTypeCount() { 181 return 2; 182 } 183 184 @Override getItemViewType(int position)185 public int getItemViewType(int position) { 186 return isHeader(position) ? ITEM_VIEW_TYPE_HEADER : ITEM_VIEW_TYPE_NORMAL; 187 } 188 189 @Override isEnabled(int position)190 public boolean isEnabled(int position) { 191 return !isHeader(position); 192 } 193 194 // The LabelList has headers which are not 195 // enabled. 196 @Override areAllItemsEnabled()197 public boolean areAllItemsEnabled() { 198 return false; 199 } 200 201 @Override bindView(View view, Context context, Cursor cursor)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 newView(Context context, Cursor cursor, ViewGroup parent)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 isHeader(int position)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. */ isAccountRow(int position)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 */ isMailboxRow(int position)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. */ isAccountRow(Cursor cursor)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 */ isHeaderRow(Cursor cursor)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 */ isMailboxRow(Cursor cursor)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 */ getId(int position)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 */ getAccountId(int position)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 */ enableUpdates(boolean state)283 static void enableUpdates(boolean state) { 284 sEnableUpdate = state; 285 } 286 getDisplayName(Context context, Cursor cursor)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 getId(Cursor cursor)301 static long getId(Cursor cursor) { 302 return cursor.getLong(cursor.getColumnIndex(ORIGINAL_ID)); 303 } 304 getType(Cursor cursor)305 static int getType(Cursor cursor) { 306 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.TYPE)); 307 } 308 getMessageCount(Cursor cursor)309 static int getMessageCount(Cursor cursor) { 310 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.MESSAGE_COUNT)); 311 } 312 getUnreadCount(Cursor cursor)313 static int getUnreadCount(Cursor cursor) { 314 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.UNREAD_COUNT)); 315 } 316 getAccountId(Cursor cursor)317 static long getAccountId(Cursor cursor) { 318 return cursor.getLong(cursor.getColumnIndex(MailboxColumns.ACCOUNT_KEY)); 319 } 320 getRowType(Cursor cursor)321 private static int getRowType(Cursor cursor) { 322 return cursor.getInt(cursor.getColumnIndex(ROW_TYPE)); 323 } 324 getFlags(Cursor cursor)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 CursorWithExtras(Cursor cursor, int childCount)340 CursorWithExtras(Cursor cursor, int childCount) { 341 super(cursor); 342 mChildCount = childCount; 343 } 344 } 345 bindListHeader(View view, Context context, Cursor cursor)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 bindListItem(View view, Context context, Cursor cursor)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 */ createMailboxesLoader(Context context, long accountId, long parentMailboxId)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 */ createCombinedViewLoader(Context context)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 */ addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName, int mailboxType, int unreadCount, int messageCount, int rowType, int flags, long accountId)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 addCombinedMailboxRow(Context context, MatrixCursor cursor, long id, int mailboxType, boolean showAlways)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 MailboxFragmentLoader(Context context, long accountId, long parentKey)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 onContentChanged()531 public void onContentChanged() { 532 if (sEnableUpdate) { 533 super.onContentChanged(); 534 } 535 } 536 537 @Override loadInBackground()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 CombinedMailboxLoader(Context context)615 private CombinedMailboxLoader(Context context) { 616 super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null); 617 mContext = context; 618 } 619 620 @Override loadInBackground()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 buildCombinedMailboxes(Context c, Cursor innerCursor)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