MailboxFragmentAdapter.java revision 844bf745044b4564f42a68f8b7d40105c4def294
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.activity;
18
19import com.android.email.Email;
20import com.android.email.FolderProperties;
21import com.android.email.R;
22import com.android.email.data.ClosingMatrixCursor;
23import com.android.email.data.ThrottlingCursorLoader;
24import com.android.emailcommon.Logging;
25import com.android.emailcommon.provider.EmailContent;
26import com.android.emailcommon.provider.EmailContent.Account;
27import com.android.emailcommon.provider.EmailContent.AccountColumns;
28import com.android.emailcommon.provider.EmailContent.MailboxColumns;
29import com.android.emailcommon.provider.EmailContent.Message;
30import com.android.emailcommon.provider.Mailbox;
31import com.android.emailcommon.utility.Utility;
32
33import android.content.Context;
34import android.content.Loader;
35import android.database.Cursor;
36import android.database.CursorWrapper;
37import android.database.MatrixCursor;
38import android.database.MatrixCursor.RowBuilder;
39import android.database.MergeCursor;
40import android.util.Log;
41import android.view.View;
42import android.view.ViewGroup;
43import android.widget.ImageView;
44import android.widget.TextView;
45
46/**
47 * Mailbox cursor adapter for the mailbox list fragment.
48 *
49 * A mailbox cursor may contain one of several different types of data. Currently, this
50 * adapter supports the following views:
51 * 1. The standard inbox, mailbox view
52 * 2. The combined mailbox view
53 * 3. Nested folder navigation
54 *
55 * TODO At a minimum, we should break out the loaders. They have no relation to the view code
56 * and only serve to confuse the user.
57 * TODO Determine if we actually need a separate adapter / view / loader for nested folder
58 * navigation. It's a little convoluted at the moment, but, still manageable.
59 */
60/*package*/ class MailboxFragmentAdapter extends MailboxesAdapter {
61    private static final String MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" +
62            " AND " + MailboxColumns.ID + "=?";
63
64    /**
65     * {@link Cursor} with extra information which is returned by the loader created by
66     * {@link MailboxFragmentAdapter#createMailboxesLoader}.
67     */
68    public static class CursorWithExtras extends CursorWrapper {
69        /**
70         * The number of mailboxes in the cursor if the cursor contains top-level mailboxes.
71         * Otherwise, the number of *child* mailboxes.
72         */
73        public final int mChildCount;
74
75        CursorWithExtras(Cursor cursor, int childCount) {
76            super(cursor);
77            mChildCount = childCount;
78        }
79    }
80
81    public MailboxFragmentAdapter(Context context, Callback callback) {
82        super(context, callback);
83    }
84
85    @Override
86    public void bindView(View view, Context context, Cursor cursor) {
87        final boolean isAccount = isAccountRow(cursor);
88        final int type = cursor.getInt(COLUMN_TYPE);
89        final long id = cursor.getLong(COLUMN_ID);
90        final long accountId = cursor.getLong(COLUMN_ACCOUNT_ID);
91        final int flags = cursor.getInt(COLUMN_FLAGS);
92        final int rowType = cursor.getInt(COLUMN_ROW_TYPE);
93        final boolean hasVisibleChildren = (flags & Mailbox.FLAG_HAS_CHILDREN) != 0
94                && (flags & Mailbox.FLAG_CHILDREN_VISIBLE) != 0;
95
96        MailboxListItem listItem = (MailboxListItem)view;
97        listItem.mMailboxId = isAccountRow(cursor) ? Mailbox.NO_MAILBOX : id;
98        listItem.mMailboxType = type;
99        listItem.mAccountId = accountId;
100        listItem.mIsValidDropTarget = (id >= 0)
101                && !Utility.arrayContains(Mailbox.INVALID_DROP_TARGETS, type)
102                && (flags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) != 0;
103        listItem.mIsNavigable = hasVisibleChildren;
104
105        listItem.mAdapter = this;
106        // Set the background depending on whether we're in drag mode, the mailbox is a valid
107        // target, etc.
108        mCallback.onBind(listItem);
109
110        // Set mailbox name
111        final TextView nameView = (TextView) view.findViewById(R.id.mailbox_name);
112        nameView.setText(getDisplayName(context, cursor));
113        // Set count
114        final int count;
115        switch (getCountTypeForMailboxType(cursor)) {
116            case COUNT_TYPE_UNREAD:
117                count = cursor.getInt(COLUMN_UNREAD_COUNT);
118                break;
119            case COUNT_TYPE_TOTAL:
120                count = cursor.getInt(COLUMN_MESSAGE_COUNT);
121                break;
122            default: // no count
123                count = 0;
124                break;
125        }
126        final TextView countView = (TextView) view.findViewById(R.id.message_count);
127
128        // Set folder icon
129        final ImageView folderIcon = (ImageView) view.findViewById(R.id.folder_icon);
130        folderIcon.setImageDrawable(
131                FolderProperties.getInstance(context).getIcon(type, id, flags));
132
133        final ImageView mailboxExpandedIcon =
134                (ImageView) view.findViewById(R.id.folder_expanded_icon);
135        switch (rowType) {
136            case ROW_TYPE_SUBMAILBOX:
137                if (hasVisibleChildren) {
138                    mailboxExpandedIcon.setVisibility(View.VISIBLE);
139                    mailboxExpandedIcon.setImageResource(
140                            R.drawable.ic_mailbox_collapsed_holo_light);
141                } else {
142                    mailboxExpandedIcon.setVisibility(View.INVISIBLE);
143                    mailboxExpandedIcon.setImageDrawable(null);
144                }
145                folderIcon.setVisibility(View.INVISIBLE);
146                break;
147            case ROW_TYPE_CURMAILBOX:
148                mailboxExpandedIcon.setVisibility(View.GONE);
149                mailboxExpandedIcon.setImageDrawable(null);
150                folderIcon.setVisibility(View.GONE);
151                break;
152            case ROW_TYPE_MAILBOX:
153            default: // Includes ROW_TYPE_ACCOUNT
154                if (hasVisibleChildren) {
155                    mailboxExpandedIcon.setVisibility(View.VISIBLE);
156                    mailboxExpandedIcon.setImageResource(
157                            R.drawable.ic_mailbox_collapsed_holo_light);
158                } else {
159                    mailboxExpandedIcon.setVisibility(View.GONE);
160                    mailboxExpandedIcon.setImageDrawable(null);
161                }
162                folderIcon.setVisibility(View.VISIBLE);
163                break;
164        }
165
166        // If the unread count is zero, not to show countView.
167        if (count > 0) {
168            countView.setVisibility(View.VISIBLE);
169            countView.setText(Integer.toString(count));
170        } else {
171            countView.setVisibility(View.GONE);
172        }
173
174        final View chipView = view.findViewById(R.id.color_chip);
175        if (isAccount) {
176            chipView.setVisibility(View.VISIBLE);
177            chipView.setBackgroundColor(mResourceHelper.getAccountColor(id));
178        } else {
179            chipView.setVisibility(View.GONE);
180        }
181    }
182
183    @Override
184    public View newView(Context context, Cursor cursor, ViewGroup parent) {
185        return mInflater.inflate(R.layout.mailbox_list_item, parent, false);
186    }
187
188    /**
189     * Returns a cursor loader for the mailboxes of the given account.  If <code>parentKey</code>
190     * refers to a valid mailbox ID [e.g. non-zero], restrict the loader to only those mailboxes
191     * contained by this parent mailbox.
192     *
193     * Note the returned loader always returns a {@link CursorWithExtras}.
194     */
195    public static Loader<Cursor> createMailboxesLoader(Context context, long accountId,
196            long parentMailboxId) {
197        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
198            Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#CursorWithExtras accountId=" + accountId
199                    + " parentMailboxId=" + parentMailboxId);
200        }
201        if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
202            throw new IllegalArgumentException();
203        }
204        return new MailboxFragmentLoader(context, accountId, parentMailboxId);
205    }
206
207    /**
208     * Returns a cursor loader for the combined view.
209     */
210    public static Loader<Cursor> createCombinedViewLoader(Context context) {
211        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
212            Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#createCombinedViewLoader");
213        }
214        return new CombinedMailboxLoader(context);
215    }
216
217    /**
218     * Adds a new row into the given cursor.
219     */
220    private static void addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName,
221            int mailboxType, int unreadCount, int messageCount, int rowType, int flags,
222            long accountId) {
223        long listId = mailboxId;
224        if (mailboxId < 0) {
225            listId = Long.MAX_VALUE + mailboxId; // IDs for the list view must be positive
226        }
227        RowBuilder row = cursor.newRow();
228        row.add(listId);
229        row.add(mailboxId);
230        row.add(displayName);
231        row.add(mailboxType);
232        row.add(unreadCount);
233        row.add(messageCount);
234        row.add(rowType);
235        row.add(flags);
236        row.add(accountId);
237    }
238
239    private static void addCombinedMailboxRow(MatrixCursor cursor, long id, int mailboxType,
240            int count, boolean showAlways) {
241        if (id >= 0) {
242            throw new IllegalArgumentException(); // Must be QUERY_ALL_*, which are all negative
243        }
244        if (showAlways || (count > 0)) {
245            addMailboxRow(
246                    cursor, id, "", mailboxType, count, count, ROW_TYPE_MAILBOX, Mailbox.FLAG_NONE,
247                    Account.ACCOUNT_ID_COMBINED_VIEW);
248        }
249    }
250
251    /**
252     * Loads mailboxes that are the children of a given mailbox ID.
253     *
254     * The returned {@link Cursor} is always a {@link CursorWithExtras}.
255     */
256    private static class MailboxFragmentLoader extends ThrottlingCursorLoader {
257        private final Context mContext;
258        private final long mAccountId;
259        private final long mParentKey;
260
261        MailboxFragmentLoader(Context context, long accountId, long parentKey) {
262            super(context, Mailbox.CONTENT_URI,
263                    (parentKey != Mailbox.NO_MAILBOX)
264                            ? MailboxesAdapter.SUBMAILBOX_PROJECTION
265                            : MailboxesAdapter.PROJECTION,
266                    MAILBOX_SELECTION_WITH_PARENT,
267                    new String[] { Long.toString(accountId), Long.toString(parentKey) },
268                    MAILBOX_ORDER_BY);
269            mContext = context;
270            mAccountId = accountId;
271            mParentKey = parentKey;
272        }
273
274        @Override
275        public void onContentChanged() {
276            if (sEnableUpdate) {
277                super.onContentChanged();
278            }
279        }
280
281        @Override
282        public Cursor loadInBackground() {
283            boolean parentRemoved = false;
284
285            final Cursor childMailboxCursor = super.loadInBackground();
286            final Cursor returnCursor;
287
288            final int childCount = childMailboxCursor.getCount();
289
290            if (mParentKey != Mailbox.NO_MAILBOX) {
291                // If we're not showing the top level mailboxes, add the "parent" mailbox.
292                final Cursor parentCursor = getContext().getContentResolver().query(
293                        Mailbox.CONTENT_URI, CURMAILBOX_PROJECTION, MAILBOX_SELECTION,
294                        new String[] { Long.toString(mAccountId), Long.toString(mParentKey) },
295                        null);
296
297                returnCursor = new MergeCursor(new Cursor[] { parentCursor, childMailboxCursor });
298            } else {
299                // Add "Starred", only if the account has at least one starred message.
300                // TODO It's currently "combined starred", but the plan is to make it per-account
301                // starred.
302                final int accountStarredCount
303                        = Message.getFavoriteMessageCount(mContext, mAccountId);
304                if (accountStarredCount > 0) {
305                    final MatrixCursor starredCursor = new MatrixCursor(getProjection());
306                    final int totalStarredCount = Message.getFavoriteMessageCount(mContext);
307                    addCombinedMailboxRow(starredCursor, Mailbox.QUERY_ALL_FAVORITES,
308                            Mailbox.TYPE_MAIL, totalStarredCount, true);
309                    returnCursor
310                            = new MergeCursor(new Cursor[] { starredCursor, childMailboxCursor });
311                } else {
312                    returnCursor = childMailboxCursor; // no starred message
313                }
314            }
315
316            return new CursorWithExtras(Utility.CloseTraceCursorWrapper.get(returnCursor),
317                    childCount);
318        }
319    }
320
321    /**
322     * Loader for mailboxes in "Combined view".
323     */
324    /*package*/ static class CombinedMailboxLoader extends ThrottlingCursorLoader {
325        private static final String[] ACCOUNT_PROJECTION = new String[] {
326                    EmailContent.RECORD_ID, AccountColumns.DISPLAY_NAME,
327                };
328        private static final int COLUMN_ACCOUND_ID = 0;
329        private static final int COLUMN_ACCOUNT_DISPLAY_NAME = 1;
330
331        private final Context mContext;
332
333        public CombinedMailboxLoader(Context context) {
334            super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null);
335            mContext = context;
336        }
337
338        @Override
339        public Cursor loadInBackground() {
340            final Cursor accounts = super.loadInBackground();
341
342            // Build combined mailbox rows.
343            final MatrixCursor returnCursor = buildCombinedMailboxes(mContext, accounts);
344
345            // Add account rows.
346            accounts.moveToPosition(-1);
347            while (accounts.moveToNext()) {
348                final long accountId = accounts.getLong(COLUMN_ACCOUND_ID);
349                final String accountName = accounts.getString(COLUMN_ACCOUNT_DISPLAY_NAME);
350                final int unreadCount = Mailbox.getUnreadCountByAccountAndMailboxType(
351                        mContext, accountId, Mailbox.TYPE_INBOX);
352                addMailboxRow(returnCursor, accountId, accountName, Mailbox.TYPE_NONE,
353                        unreadCount, unreadCount, ROW_TYPE_ACCOUNT, Mailbox.FLAG_NONE,
354                        accountId);
355            }
356            return Utility.CloseTraceCursorWrapper.get(returnCursor);
357        }
358
359        /*package*/ static MatrixCursor buildCombinedMailboxes(Context context,
360                Cursor innerCursor) {
361            MatrixCursor cursor = new ClosingMatrixCursor(PROJECTION, innerCursor);
362            // Combined inbox -- show unread count
363            addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX,
364                    Mailbox.getUnreadCountByMailboxType(context, Mailbox.TYPE_INBOX), true);
365
366            // Favorite (starred) -- show # of favorites
367            addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL,
368                    Message.getFavoriteMessageCount(context), false);
369
370            // Drafts -- show # of drafts
371            addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS,
372                    Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_DRAFTS), false);
373
374            // Outbox -- # of outstanding messages
375            addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX,
376                    Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_OUTBOX), false);
377
378            return cursor;
379        }
380    }
381}
382