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     // The LabelList has headers which are not
    195     // enabled.
    196     @Override
    197     public boolean areAllItemsEnabled() {
    198         return false;
    199     }
    200 
    201     @Override
    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
    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 
    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. */
    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      */
    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. */
    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 */
    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      */
    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      */
    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      */
    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      */
    283     static void enableUpdates(boolean state) {
    284         sEnableUpdate = state;
    285     }
    286 
    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 
    301     static long getId(Cursor cursor) {
    302         return cursor.getLong(cursor.getColumnIndex(ORIGINAL_ID));
    303     }
    304 
    305     static int getType(Cursor cursor) {
    306         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.TYPE));
    307     }
    308 
    309     static int getMessageCount(Cursor cursor) {
    310         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.MESSAGE_COUNT));
    311     }
    312 
    313     static int getUnreadCount(Cursor cursor) {
    314         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.UNREAD_COUNT));
    315     }
    316 
    317     static long getAccountId(Cursor cursor) {
    318         return cursor.getLong(cursor.getColumnIndex(MailboxColumns.ACCOUNT_KEY));
    319     }
    320 
    321     private static int getRowType(Cursor cursor) {
    322         return cursor.getInt(cursor.getColumnIndex(ROW_TYPE));
    323     }
    324 
    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 
    340         CursorWithExtras(Cursor cursor, int childCount) {
    341             super(cursor);
    342             mChildCount = childCount;
    343         }
    344     }
    345 
    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 
    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      */
    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      */
    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      */
    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 
    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 
    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
    531         public void onContentChanged() {
    532             if (sEnableUpdate) {
    533                 super.onContentChanged();
    534             }
    535         }
    536 
    537         @Override
    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 
    615         private CombinedMailboxLoader(Context context) {
    616             super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null);
    617             mContext = context;
    618         }
    619 
    620         @Override
    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
    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