Home | History | Annotate | Download | only in activity
      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