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