• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
onBind(MailboxListItem listItem)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();
onBind(MailboxListItem listItem)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 + " COLLATE LOCALIZED ASC";
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 
MailboxFragmentAdapter(Context context, Callback callback)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
getViewTypeCount()180     public int getViewTypeCount() {
181         return 2;
182     }
183 
184     @Override
getItemViewType(int position)185     public int getItemViewType(int position) {
186         return isHeader(position) ? ITEM_VIEW_TYPE_HEADER : ITEM_VIEW_TYPE_NORMAL;
187     }
188 
189     @Override
isEnabled(int position)190     public boolean isEnabled(int position) {
191         return !isHeader(position);
192     }
193 
194     // The LabelList has headers which are not
195     // enabled.
196     @Override
areAllItemsEnabled()197     public boolean areAllItemsEnabled() {
198         return false;
199     }
200 
201     @Override
bindView(View view, Context context, Cursor cursor)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
newView(Context context, Cursor cursor, ViewGroup parent)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 
isHeader(int position)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. */
isAccountRow(int position)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      */
isMailboxRow(int position)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. */
isAccountRow(Cursor cursor)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 */
isHeaderRow(Cursor cursor)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      */
isMailboxRow(Cursor cursor)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      */
getId(int position)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      */
getAccountId(int position)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      */
enableUpdates(boolean state)283     static void enableUpdates(boolean state) {
284         sEnableUpdate = state;
285     }
286 
getDisplayName(Context context, Cursor cursor)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 
getId(Cursor cursor)301     static long getId(Cursor cursor) {
302         return cursor.getLong(cursor.getColumnIndex(ORIGINAL_ID));
303     }
304 
getType(Cursor cursor)305     static int getType(Cursor cursor) {
306         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.TYPE));
307     }
308 
getMessageCount(Cursor cursor)309     static int getMessageCount(Cursor cursor) {
310         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.MESSAGE_COUNT));
311     }
312 
getUnreadCount(Cursor cursor)313     static int getUnreadCount(Cursor cursor) {
314         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.UNREAD_COUNT));
315     }
316 
getAccountId(Cursor cursor)317     static long getAccountId(Cursor cursor) {
318         return cursor.getLong(cursor.getColumnIndex(MailboxColumns.ACCOUNT_KEY));
319     }
320 
getRowType(Cursor cursor)321     private static int getRowType(Cursor cursor) {
322         return cursor.getInt(cursor.getColumnIndex(ROW_TYPE));
323     }
324 
getFlags(Cursor cursor)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 
CursorWithExtras(Cursor cursor, int childCount)340         CursorWithExtras(Cursor cursor, int childCount) {
341             super(cursor);
342             mChildCount = childCount;
343         }
344     }
345 
bindListHeader(View view, Context context, Cursor cursor)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 
bindListItem(View view, Context context, Cursor cursor)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      */
createMailboxesLoader(Context context, long accountId, long parentMailboxId)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      */
createCombinedViewLoader(Context context)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      */
addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName, int mailboxType, int unreadCount, int messageCount, int rowType, int flags, long accountId)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 
addCombinedMailboxRow(Context context, MatrixCursor cursor, long id, int mailboxType, boolean showAlways)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 
MailboxFragmentLoader(Context context, long accountId, long parentKey)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
onContentChanged()531         public void onContentChanged() {
532             if (sEnableUpdate) {
533                 super.onContentChanged();
534             }
535         }
536 
537         @Override
loadInBackground()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 
CombinedMailboxLoader(Context context)615         private CombinedMailboxLoader(Context context) {
616             super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null);
617             mContext = context;
618         }
619 
620         @Override
loadInBackground()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
buildCombinedMailboxes(Context c, Cursor innerCursor)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