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; 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 @Override bindView(View view, Context context, Cursor cursor)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 newView(Context context, Cursor cursor, ViewGroup parent)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 isHeader(int position)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. */ isAccountRow(int position)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 */ isMailboxRow(int position)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. */ isAccountRow(Cursor cursor)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 */ isHeaderRow(Cursor cursor)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 */ isMailboxRow(Cursor cursor)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 */ getId(int position)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 */ getAccountId(int position)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 */ enableUpdates(boolean state)276 static void enableUpdates(boolean state) { 277 sEnableUpdate = state; 278 } 279 getDisplayName(Context context, Cursor cursor)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 getId(Cursor cursor)294 static long getId(Cursor cursor) { 295 return cursor.getLong(cursor.getColumnIndex(ORIGINAL_ID)); 296 } 297 getType(Cursor cursor)298 static int getType(Cursor cursor) { 299 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.TYPE)); 300 } 301 getMessageCount(Cursor cursor)302 static int getMessageCount(Cursor cursor) { 303 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.MESSAGE_COUNT)); 304 } 305 getUnreadCount(Cursor cursor)306 static int getUnreadCount(Cursor cursor) { 307 return cursor.getInt(cursor.getColumnIndex(MailboxColumns.UNREAD_COUNT)); 308 } 309 getAccountId(Cursor cursor)310 static long getAccountId(Cursor cursor) { 311 return cursor.getLong(cursor.getColumnIndex(MailboxColumns.ACCOUNT_KEY)); 312 } 313 getRowType(Cursor cursor)314 private static int getRowType(Cursor cursor) { 315 return cursor.getInt(cursor.getColumnIndex(ROW_TYPE)); 316 } 317 getFlags(Cursor cursor)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 CursorWithExtras(Cursor cursor, int childCount)333 CursorWithExtras(Cursor cursor, int childCount) { 334 super(cursor); 335 mChildCount = childCount; 336 } 337 } 338 bindListHeader(View view, Context context, Cursor cursor)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 bindListItem(View view, Context context, Cursor cursor)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 */ createMailboxesLoader(Context context, long accountId, long parentMailboxId)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 */ createCombinedViewLoader(Context context)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 */ addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName, int mailboxType, int unreadCount, int messageCount, int rowType, int flags, long accountId)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 addCombinedMailboxRow(Context context, MatrixCursor cursor, long id, int mailboxType, boolean showAlways)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 MailboxFragmentLoader(Context context, long accountId, long parentKey)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 onContentChanged()524 public void onContentChanged() { 525 if (sEnableUpdate) { 526 super.onContentChanged(); 527 } 528 } 529 530 @Override loadInBackground()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 CombinedMailboxLoader(Context context)608 private CombinedMailboxLoader(Context context) { 609 super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null); 610 mContext = context; 611 } 612 613 @Override loadInBackground()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 buildCombinedMailboxes(Context c, Cursor innerCursor)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