MailboxFragmentAdapter.java revision 6fc728b7cd963ef7ed1e6d8749cd50ccd9b24f38
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 android.content.ContentUris;
20import android.content.Context;
21import android.content.Loader;
22import android.database.Cursor;
23import android.database.CursorWrapper;
24import android.database.MatrixCursor;
25import android.database.MatrixCursor.RowBuilder;
26import android.database.MergeCursor;
27import android.util.Log;
28import android.view.LayoutInflater;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.AdapterView;
32import android.widget.CursorAdapter;
33import android.widget.ImageView;
34import android.widget.TextView;
35
36import com.android.email.Email;
37import com.android.email.FolderProperties;
38import com.android.email.R;
39import com.android.email.ResourceHelper;
40import com.android.email.data.ClosingMatrixCursor;
41import com.android.email.data.ThrottlingCursorLoader;
42import com.android.emailcommon.Logging;
43import com.android.emailcommon.provider.Account;
44import com.android.emailcommon.provider.EmailContent;
45import com.android.emailcommon.provider.EmailContent.AccountColumns;
46import com.android.emailcommon.provider.EmailContent.MailboxColumns;
47import com.android.emailcommon.provider.EmailContent.Message;
48import com.android.emailcommon.provider.Mailbox;
49import com.android.emailcommon.utility.Utility;
50import com.google.common.annotations.VisibleForTesting;
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    // 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