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