• 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;
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     @Override
bindView(View view, Context context, Cursor cursor)195     public void bindView(View view, Context context, Cursor cursor) {
196         if (view instanceof MailboxListItem) {
197             bindListItem(view, context, cursor);
198         } else {
199             bindListHeader(view, context, cursor);
200         }
201     }
202 
203     @Override
newView(Context context, Cursor cursor, ViewGroup parent)204     public View newView(Context context, Cursor cursor, ViewGroup parent) {
205         if (cursor.getInt(cursor.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) {
206             return mInflater.inflate(R.layout.mailbox_list_header, parent, false);
207         }
208         return mInflater.inflate(R.layout.mailbox_list_item, parent, false);
209     }
210 
isHeader(int position)211     private boolean isHeader(int position) {
212         Cursor c = getCursor();
213         if ((c == null) || c.isClosed()) {
214             return false;
215         }
216         c.moveToPosition(position);
217         int rowType = c.getInt(c.getColumnIndex(ROW_TYPE));
218         return rowType == ROW_TYPE_HEADER;
219     }
220 
221     /** Returns {@code true} if the specified row is of an account in the combined view. */
isAccountRow(int position)222     boolean isAccountRow(int position) {
223         return isAccountRow((Cursor) getItem(position));
224     }
225 
226     /**
227      * Returns {@code true} if the specified row is a mailbox.
228      * ({@link #ROW_TYPE_MAILBOX}, {@link #ROW_TYPE_CURMAILBOX} and {@link #ROW_TYPE_SUBMAILBOX})
229      */
isMailboxRow(int position)230     boolean isMailboxRow(int position) {
231         return isMailboxRow((Cursor) getItem(position));
232     }
233 
234     /** Returns {@code true} if the current row is of an account in the combined view. */
isAccountRow(Cursor cursor)235     private static boolean isAccountRow(Cursor cursor) {
236         return getRowType(cursor) == ROW_TYPE_ACCOUNT;
237     }
238 
239     /** Returns {@code true} if the current row is a header */
isHeaderRow(Cursor cursor)240     private static boolean isHeaderRow(Cursor cursor) {
241         return getRowType(cursor) == ROW_TYPE_HEADER;
242     }
243 
244     /**
245      * Returns {@code true} if the current row is a mailbox.
246      * ({@link #ROW_TYPE_MAILBOX}, {@link #ROW_TYPE_CURMAILBOX} and {@link #ROW_TYPE_SUBMAILBOX})
247      */
isMailboxRow(Cursor cursor)248     private static boolean isMailboxRow(Cursor cursor) {
249         return !(isAccountRow(cursor) || isHeaderRow(cursor));
250     }
251 
252     /**
253      * Returns the ID of the given row. It may be a mailbox or account ID depending upon the
254      * result of {@link #isAccountRow}.
255      */
getId(int position)256     long getId(int position) {
257         Cursor c = (Cursor) getItem(position);
258         return getId(c);
259     }
260 
261     /**
262      * Returns the account ID of the mailbox owner for the given row. If the given row is a
263      * combined mailbox, {@link Account#ACCOUNT_ID_COMBINED_VIEW} is returned. If the given
264      * row is an account, returns the account's ID [the same as {@link #ORIGINAL_ID}].
265      */
getAccountId(int position)266     long getAccountId(int position) {
267         Cursor c = (Cursor) getItem(position);
268         return getAccountId(c);
269     }
270 
271     /**
272      * Turn on and off list updates; during a drag operation, we do NOT want to the list of
273      * mailboxes to update, as this would be visually jarring
274      * @param state whether or not the MailboxList can be updated
275      */
enableUpdates(boolean state)276     static void enableUpdates(boolean state) {
277         sEnableUpdate = state;
278     }
279 
getDisplayName(Context context, Cursor cursor)280     private static String getDisplayName(Context context, Cursor cursor) {
281         final String name = cursor.getString(cursor.getColumnIndex(MailboxColumns.DISPLAY_NAME));
282         if (isHeaderRow(cursor) || isAccountRow(cursor)) {
283             // Always use actual name
284             return name;
285         } else {
286             // Use this method for two purposes:
287             // - Set combined mailbox names
288             // - Rewrite special mailbox names (e.g. trash)
289             FolderProperties fp = FolderProperties.getInstance(context);
290             return fp.getDisplayName(getType(cursor), getId(cursor), name);
291         }
292     }
293 
getId(Cursor cursor)294     static long getId(Cursor cursor) {
295         return cursor.getLong(cursor.getColumnIndex(ORIGINAL_ID));
296     }
297 
getType(Cursor cursor)298     static int getType(Cursor cursor) {
299         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.TYPE));
300     }
301 
getMessageCount(Cursor cursor)302     static int getMessageCount(Cursor cursor) {
303         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.MESSAGE_COUNT));
304     }
305 
getUnreadCount(Cursor cursor)306     static int getUnreadCount(Cursor cursor) {
307         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.UNREAD_COUNT));
308     }
309 
getAccountId(Cursor cursor)310     static long getAccountId(Cursor cursor) {
311         return cursor.getLong(cursor.getColumnIndex(MailboxColumns.ACCOUNT_KEY));
312     }
313 
getRowType(Cursor cursor)314     private static int getRowType(Cursor cursor) {
315         return cursor.getInt(cursor.getColumnIndex(ROW_TYPE));
316     }
317 
getFlags(Cursor cursor)318     private static int getFlags(Cursor cursor) {
319         return cursor.getInt(cursor.getColumnIndex(MailboxColumns.FLAGS));
320     }
321 
322     /**
323      * {@link Cursor} with extra information which is returned by the loader created by
324      * {@link MailboxFragmentAdapter#createMailboxesLoader}.
325      */
326     static class CursorWithExtras extends CursorWrapper {
327         /**
328          * The number of mailboxes in the cursor if the cursor contains top-level mailboxes.
329          * Otherwise, the number of *child* mailboxes.
330          */
331         public final int mChildCount;
332 
CursorWithExtras(Cursor cursor, int childCount)333         CursorWithExtras(Cursor cursor, int childCount) {
334             super(cursor);
335             mChildCount = childCount;
336         }
337     }
338 
bindListHeader(View view, Context context, Cursor cursor)339     private void bindListHeader(View view, Context context, Cursor cursor) {
340         final TextView nameView = (TextView) view.findViewById(R.id.display_name);
341         nameView.setText(getDisplayName(context, cursor));
342     }
343 
bindListItem(View view, Context context, Cursor cursor)344     private void bindListItem(View view, Context context, Cursor cursor) {
345         final boolean isAccount = isAccountRow(cursor);
346         final int type = getType(cursor);
347         final long id = getId(cursor);
348         final long accountId = getAccountId(cursor);
349         final int flags = getFlags(cursor);
350         final int rowType = getRowType(cursor);
351         final boolean hasVisibleChildren = (flags & Mailbox.FLAG_HAS_CHILDREN) != 0
352                 && (flags & Mailbox.FLAG_CHILDREN_VISIBLE) != 0;
353 
354         MailboxListItem listItem = (MailboxListItem)view;
355         listItem.mMailboxId = isAccountRow(cursor) ? Mailbox.NO_MAILBOX : id;
356         listItem.mMailboxType = type;
357         listItem.mAccountId = accountId;
358         listItem.mIsValidDropTarget = (id >= 0)
359                 && !Utility.arrayContains(Mailbox.INVALID_DROP_TARGETS, type)
360                 && (flags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) != 0;
361         listItem.mIsNavigable = hasVisibleChildren;
362 
363         listItem.mAdapter = this;
364         // Set the background depending on whether we're in drag mode, the mailbox is a valid
365         // target, etc.
366         mCallback.onBind(listItem);
367 
368         // Set mailbox name
369         final TextView nameView = (TextView) view.findViewById(R.id.mailbox_name);
370         nameView.setText(getDisplayName(context, cursor));
371         // Set count
372         final int count;
373         if (isAccountRow(cursor)) {
374             count = getUnreadCount(cursor);
375         } else {
376             FolderProperties fp = FolderProperties.getInstance(context);
377             count = fp.getMessageCount(type, getUnreadCount(cursor), getMessageCount(cursor));
378         }
379         final TextView countView = (TextView) view.findViewById(R.id.message_count);
380 
381         // Set folder icon
382         final ImageView folderIcon = (ImageView) view.findViewById(R.id.folder_icon);
383         folderIcon.setImageDrawable(
384                 FolderProperties.getInstance(context).getIcon(type, id, flags));
385 
386         final ImageView mailboxExpandedIcon =
387                 (ImageView) view.findViewById(R.id.folder_expanded_icon);
388         switch (rowType) {
389             case ROW_TYPE_SUBMAILBOX:
390                 if (hasVisibleChildren) {
391                     mailboxExpandedIcon.setVisibility(View.VISIBLE);
392                     mailboxExpandedIcon.setImageResource(
393                             R.drawable.ic_mailbox_collapsed_holo_light);
394                 } else {
395                     mailboxExpandedIcon.setVisibility(View.INVISIBLE);
396                     mailboxExpandedIcon.setImageDrawable(null);
397                 }
398                 folderIcon.setVisibility(View.INVISIBLE);
399                 break;
400             case ROW_TYPE_CURMAILBOX:
401                 mailboxExpandedIcon.setVisibility(View.GONE);
402                 mailboxExpandedIcon.setImageDrawable(null);
403                 folderIcon.setVisibility(View.GONE);
404                 break;
405             case ROW_TYPE_MAILBOX:
406             default: // Includes ROW_TYPE_ACCOUNT
407                 if (hasVisibleChildren) {
408                     mailboxExpandedIcon.setVisibility(View.VISIBLE);
409                     mailboxExpandedIcon.setImageResource(
410                             R.drawable.ic_mailbox_collapsed_holo_light);
411                 } else {
412                     mailboxExpandedIcon.setVisibility(View.GONE);
413                     mailboxExpandedIcon.setImageDrawable(null);
414                 }
415                 folderIcon.setVisibility(View.VISIBLE);
416                 break;
417         }
418 
419         // If the unread count is zero, not to show countView.
420         if (count > 0) {
421             countView.setVisibility(View.VISIBLE);
422             countView.setText(Integer.toString(count));
423         } else {
424             countView.setVisibility(View.GONE);
425         }
426 
427         final View chipView = view.findViewById(R.id.color_chip);
428         if (isAccount) {
429             chipView.setVisibility(View.VISIBLE);
430             chipView.setBackgroundColor(mResourceHelper.getAccountColor(id));
431         } else {
432             chipView.setVisibility(View.GONE);
433         }
434     }
435 
436     /**
437      * Returns a cursor loader for the mailboxes of the given account.  If <code>parentKey</code>
438      * refers to a valid mailbox ID [e.g. non-zero], restrict the loader to only those mailboxes
439      * contained by this parent mailbox.
440      *
441      * Note the returned loader always returns a {@link CursorWithExtras}.
442      */
createMailboxesLoader(Context context, long accountId, long parentMailboxId)443     static Loader<Cursor> createMailboxesLoader(Context context, long accountId,
444             long parentMailboxId) {
445         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
446             Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#CursorWithExtras accountId=" + accountId
447                     + " parentMailboxId=" + parentMailboxId);
448         }
449         if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
450             throw new IllegalArgumentException();
451         }
452         return new MailboxFragmentLoader(context, accountId, parentMailboxId);
453     }
454 
455     /**
456      * Returns a cursor loader for the combined view.
457      */
createCombinedViewLoader(Context context)458     static Loader<Cursor> createCombinedViewLoader(Context context) {
459         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
460             Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#createCombinedViewLoader");
461         }
462         return new CombinedMailboxLoader(context);
463     }
464 
465     /**
466      * Adds a new row into the given cursor.
467      */
addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName, int mailboxType, int unreadCount, int messageCount, int rowType, int flags, long accountId)468     private static void addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName,
469             int mailboxType, int unreadCount, int messageCount, int rowType, int flags,
470             long accountId) {
471         long listId = mailboxId;
472         if (mailboxId < 0) {
473             listId = Long.MAX_VALUE + mailboxId; // IDs for the list view must be positive
474         }
475         RowBuilder row = cursor.newRow();
476         row.add(listId);
477         row.add(mailboxId);
478         row.add(displayName);
479         row.add(mailboxType);
480         row.add(unreadCount);
481         row.add(messageCount);
482         row.add(rowType);
483         row.add(flags);
484         row.add(accountId);
485     }
486 
addCombinedMailboxRow(Context context, MatrixCursor cursor, long id, int mailboxType, boolean showAlways)487     private static void addCombinedMailboxRow(Context context, MatrixCursor cursor, long id,
488             int mailboxType, boolean showAlways) {
489         if (id >= 0) {
490             throw new IllegalArgumentException(); // Must be QUERY_ALL_*, which are all negative
491         }
492         int count = FolderProperties.getMessageCountForCombinedMailbox(context, id);
493         if (showAlways || (count > 0)) {
494             addMailboxRow(
495                     cursor, id, "", mailboxType, count, count, ROW_TYPE_MAILBOX, Mailbox.FLAG_NONE,
496                     Account.ACCOUNT_ID_COMBINED_VIEW);
497         }
498     }
499 
500     /**
501      * Loads mailboxes that are the children of a given mailbox ID.
502      *
503      * The returned {@link Cursor} is always a {@link CursorWithExtras}.
504      */
505     private static class MailboxFragmentLoader extends ThrottlingCursorLoader {
506         private final Context mContext;
507         private final long mAccountId;
508         private final long mParentKey;
509 
MailboxFragmentLoader(Context context, long accountId, long parentKey)510         MailboxFragmentLoader(Context context, long accountId, long parentKey) {
511             super(context, Mailbox.CONTENT_URI,
512                     (parentKey != Mailbox.NO_MAILBOX)
513                             ? SUBMAILBOX_PROJECTION
514                             : PROJECTION,
515                     USER_MAILBOX_SELECTION_WITH_PARENT,
516                     new String[] { Long.toString(accountId), Long.toString(parentKey) },
517                     MAILBOX_ORDER_BY);
518             mContext = context;
519             mAccountId = accountId;
520             mParentKey = parentKey;
521         }
522 
523         @Override
onContentChanged()524         public void onContentChanged() {
525             if (sEnableUpdate) {
526                 super.onContentChanged();
527             }
528         }
529 
530         @Override
loadInBackground()531         public Cursor loadInBackground() {
532             boolean parentRemoved = false;
533 
534             final Cursor userMailboxCursor = super.loadInBackground();
535             final Cursor returnCursor;
536 
537             final int childCount = userMailboxCursor.getCount();
538 
539             if (mParentKey != Mailbox.NO_MAILBOX) {
540                 // If we're not showing the top level mailboxes, add the "parent" mailbox.
541                 final Cursor parentCursor = getContext().getContentResolver().query(
542                         Mailbox.CONTENT_URI, CURMAILBOX_PROJECTION, MAILBOX_SELECTION,
543                         new String[] { Long.toString(mAccountId), Long.toString(mParentKey) },
544                         null);
545                 returnCursor = new MergeCursor(new Cursor[] { parentCursor, userMailboxCursor });
546             } else {
547                 // TODO Add per-account starred mailbox support
548                 final MatrixCursor starredCursor = new MatrixCursor(MATRIX_PROJECTION);
549                 final Cursor systemMailboxCursor = mContext.getContentResolver().query(
550                         Mailbox.CONTENT_URI, PROJECTION, SYSTEM_MAILBOX_SELECTION,
551                         new String[] { Long.toString(mAccountId) }, MAILBOX_ORDER_BY);
552                 final MatrixCursor recentCursor = new MatrixCursor(MATRIX_PROJECTION);
553                 final MatrixCursor headerCursor = new MatrixCursor(MATRIX_PROJECTION);
554                 if (childCount > 0) {
555                     final String name = mContext.getString(R.string.mailbox_list_user_mailboxes);
556                     addMailboxRow(headerCursor, 0L, name, 0, 0, 0, ROW_TYPE_HEADER, 0, 0L);
557                 }
558                 ArrayList<Long> recentList = null;
559                 boolean useTwoPane = UiUtilities.useTwoPane(mContext);
560                 if (useTwoPane) {
561                     recentList = RecentMailboxManager.getInstance(mContext)
562                             .getMostRecent(mAccountId, true);
563                 }
564                 if (recentList != null && recentList.size() > 0) {
565                     final String name = mContext.getString(R.string.mailbox_list_recent_mailboxes);
566                     addMailboxRow(recentCursor, 0L, name, 0, 0, 0, ROW_TYPE_HEADER, 0, 0L);
567                     for (long mailboxId : recentList) {
568                         final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
569                         if (mailbox == null) continue;
570                         final int messageCount = Utility.getFirstRowInt(mContext,
571                             ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
572                             new String[] { MailboxColumns.MESSAGE_COUNT }, null, null, null, 0);
573                         final int unreadCount = Utility.getFirstRowInt(mContext,
574                             ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
575                             new String[] { MailboxColumns.UNREAD_COUNT }, null, null, null, 0);
576                         addMailboxRow(recentCursor, mailboxId, mailbox.mDisplayName, mailbox.mType,
577                             unreadCount, messageCount, ROW_TYPE_MAILBOX, mailbox.mFlags,
578                             mailbox.mAccountKey);
579                     }
580                 }
581                 int accountStarredCount = Message.getFavoriteMessageCount(mContext, mAccountId);
582                 if (accountStarredCount > 0) {
583                     // Only add "Starred", if there is at least one starred message
584                     addCombinedMailboxRow(mContext, starredCursor, Mailbox.QUERY_ALL_FAVORITES,
585                             Mailbox.TYPE_MAIL, true);
586                 }
587                 returnCursor = new MergeCursor(new Cursor[] {
588                         starredCursor, systemMailboxCursor, recentCursor, headerCursor,
589                         userMailboxCursor, });
590             }
591             return new CursorWithExtras(returnCursor, childCount);
592         }
593     }
594 
595     /**
596      * Loader for mailboxes in "Combined view".
597      */
598     @VisibleForTesting
599     static class CombinedMailboxLoader extends ThrottlingCursorLoader {
600         private static final String[] ACCOUNT_PROJECTION = new String[] {
601             EmailContent.RECORD_ID, AccountColumns.DISPLAY_NAME,
602         };
603         private static final int COLUMN_ACCOUND_ID = 0;
604         private static final int COLUMN_ACCOUNT_DISPLAY_NAME = 1;
605 
606         private final Context mContext;
607 
CombinedMailboxLoader(Context context)608         private CombinedMailboxLoader(Context context) {
609             super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null);
610             mContext = context;
611         }
612 
613         @Override
loadInBackground()614         public Cursor loadInBackground() {
615             final Cursor accounts = super.loadInBackground();
616 
617             // Build combined mailbox rows.
618             final MatrixCursor returnCursor = buildCombinedMailboxes(mContext, accounts);
619 
620             // Add account rows.
621             accounts.moveToPosition(-1);
622             while (accounts.moveToNext()) {
623                 final long accountId = accounts.getLong(COLUMN_ACCOUND_ID);
624                 final String accountName = accounts.getString(COLUMN_ACCOUNT_DISPLAY_NAME);
625                 final int unreadCount = Mailbox.getUnreadCountByAccountAndMailboxType(
626                         mContext, accountId, Mailbox.TYPE_INBOX);
627                 addMailboxRow(returnCursor, accountId, accountName, Mailbox.TYPE_NONE,
628                         unreadCount, unreadCount, ROW_TYPE_ACCOUNT, Mailbox.FLAG_NONE,
629                         accountId);
630             }
631             return returnCursor;
632         }
633 
634         @VisibleForTesting
buildCombinedMailboxes(Context c, Cursor innerCursor)635         static MatrixCursor buildCombinedMailboxes(Context c, Cursor innerCursor) {
636             MatrixCursor cursor = new ClosingMatrixCursor(MATRIX_PROJECTION, innerCursor);
637             // Combined inbox -- show unread count
638             addCombinedMailboxRow(c, cursor, Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX, true);
639 
640             // Favorite (starred) -- show # of favorites
641             addCombinedMailboxRow(c, cursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL, false);
642 
643             // Drafts -- show # of drafts
644             addCombinedMailboxRow(c, cursor, Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS, false);
645 
646             // Outbox -- # of outstanding messages
647             addCombinedMailboxRow(c, cursor, Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX, false);
648 
649             return cursor;
650         }
651     }
652 }
653