MailboxFragmentAdapter.java revision da3d04ea31a8452d3e34003b699a02ea02e3632b
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        if (view instanceof MailboxListItem) {
88            bindListItem(view, context, cursor);
89        } else {
90            bindListHeader(view, context, cursor);
91        }
92    }
93    private void bindListHeader(View view, Context context, Cursor cursor) {
94        final TextView nameView = (TextView) view.findViewById(R.id.display_name);
95        nameView.setText(getDisplayName(context, cursor));
96    }
97    private void bindListItem(View view, Context context, Cursor cursor) {
98        final boolean isAccount = isAccountRow(cursor);
99        final int type = cursor.getInt(COLUMN_TYPE);
100        final long id = cursor.getLong(COLUMN_ID);
101        final long accountId = cursor.getLong(COLUMN_ACCOUNT_ID);
102        final int flags = cursor.getInt(COLUMN_FLAGS);
103        final int rowType = cursor.getInt(COLUMN_ROW_TYPE);
104        final boolean hasVisibleChildren = (flags & Mailbox.FLAG_HAS_CHILDREN) != 0
105                && (flags & Mailbox.FLAG_CHILDREN_VISIBLE) != 0;
106
107        MailboxListItem listItem = (MailboxListItem)view;
108        listItem.mMailboxId = isAccountRow(cursor) ? Mailbox.NO_MAILBOX : id;
109        listItem.mMailboxType = type;
110        listItem.mAccountId = accountId;
111        listItem.mIsValidDropTarget = (id >= 0)
112                && !Utility.arrayContains(Mailbox.INVALID_DROP_TARGETS, type)
113                && (flags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) != 0;
114        listItem.mIsNavigable = hasVisibleChildren;
115
116        listItem.mAdapter = this;
117        // Set the background depending on whether we're in drag mode, the mailbox is a valid
118        // target, etc.
119        mCallback.onBind(listItem);
120
121        // Set mailbox name
122        final TextView nameView = (TextView) view.findViewById(R.id.mailbox_name);
123        nameView.setText(getDisplayName(context, cursor));
124        // Set count
125        final int count;
126        switch (getCountTypeForMailboxType(cursor)) {
127            case COUNT_TYPE_UNREAD:
128                count = cursor.getInt(COLUMN_UNREAD_COUNT);
129                break;
130            case COUNT_TYPE_TOTAL:
131                count = cursor.getInt(COLUMN_MESSAGE_COUNT);
132                break;
133            default: // no count
134                count = 0;
135                break;
136        }
137        final TextView countView = (TextView) view.findViewById(R.id.message_count);
138
139        // Set folder icon
140        final ImageView folderIcon = (ImageView) view.findViewById(R.id.folder_icon);
141        folderIcon.setImageDrawable(
142                FolderProperties.getInstance(context).getIcon(type, id, flags));
143
144        final ImageView mailboxExpandedIcon =
145                (ImageView) view.findViewById(R.id.folder_expanded_icon);
146        switch (rowType) {
147            case ROW_TYPE_SUBMAILBOX:
148                if (hasVisibleChildren) {
149                    mailboxExpandedIcon.setVisibility(View.VISIBLE);
150                    mailboxExpandedIcon.setImageResource(
151                            R.drawable.ic_mailbox_collapsed_holo_light);
152                } else {
153                    mailboxExpandedIcon.setVisibility(View.INVISIBLE);
154                    mailboxExpandedIcon.setImageDrawable(null);
155                }
156                folderIcon.setVisibility(View.INVISIBLE);
157                break;
158            case ROW_TYPE_CURMAILBOX:
159                mailboxExpandedIcon.setVisibility(View.GONE);
160                mailboxExpandedIcon.setImageDrawable(null);
161                folderIcon.setVisibility(View.GONE);
162                break;
163            case ROW_TYPE_MAILBOX:
164            default: // Includes ROW_TYPE_ACCOUNT
165                if (hasVisibleChildren) {
166                    mailboxExpandedIcon.setVisibility(View.VISIBLE);
167                    mailboxExpandedIcon.setImageResource(
168                            R.drawable.ic_mailbox_collapsed_holo_light);
169                } else {
170                    mailboxExpandedIcon.setVisibility(View.GONE);
171                    mailboxExpandedIcon.setImageDrawable(null);
172                }
173                folderIcon.setVisibility(View.VISIBLE);
174                break;
175        }
176
177        // If the unread count is zero, not to show countView.
178        if (count > 0) {
179            countView.setVisibility(View.VISIBLE);
180            countView.setText(Integer.toString(count));
181        } else {
182            countView.setVisibility(View.GONE);
183        }
184
185        final View chipView = view.findViewById(R.id.color_chip);
186        if (isAccount) {
187            chipView.setVisibility(View.VISIBLE);
188            chipView.setBackgroundColor(mResourceHelper.getAccountColor(id));
189        } else {
190            chipView.setVisibility(View.GONE);
191        }
192    }
193
194    @Override
195    public View newView(Context context, Cursor cursor, ViewGroup parent) {
196        if (cursor.getInt(cursor.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) {
197            return mInflater.inflate(R.layout.mailbox_list_header, parent, false);
198        }
199        return mInflater.inflate(R.layout.mailbox_list_item, parent, false);
200    }
201
202
203    /**
204     * Returns a cursor loader for the mailboxes of the given account.  If <code>parentKey</code>
205     * refers to a valid mailbox ID [e.g. non-zero], restrict the loader to only those mailboxes
206     * contained by this parent mailbox.
207     *
208     * Note the returned loader always returns a {@link CursorWithExtras}.
209     */
210    public static Loader<Cursor> createMailboxesLoader(Context context, long accountId,
211            long parentMailboxId) {
212        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
213            Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#CursorWithExtras accountId=" + accountId
214                    + " parentMailboxId=" + parentMailboxId);
215        }
216        if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
217            throw new IllegalArgumentException();
218        }
219        return new MailboxFragmentLoader(context, accountId, parentMailboxId);
220    }
221
222    /**
223     * Returns a cursor loader for the combined view.
224     */
225    public static Loader<Cursor> createCombinedViewLoader(Context context) {
226        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
227            Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#createCombinedViewLoader");
228        }
229        return new CombinedMailboxLoader(context);
230    }
231
232    /**
233     * Adds a new row into the given cursor.
234     */
235    private static void addMailboxRow(MatrixCursor cursor, long mailboxId, String displayName,
236            int mailboxType, int unreadCount, int messageCount, int rowType, int flags,
237            long accountId) {
238        long listId = mailboxId;
239        if (mailboxId < 0) {
240            listId = Long.MAX_VALUE + mailboxId; // IDs for the list view must be positive
241        }
242        RowBuilder row = cursor.newRow();
243        row.add(listId);
244        row.add(mailboxId);
245        row.add(displayName);
246        row.add(mailboxType);
247        row.add(unreadCount);
248        row.add(messageCount);
249        row.add(rowType);
250        row.add(flags);
251        row.add(accountId);
252    }
253
254    private static void addCombinedMailboxRow(MatrixCursor cursor, long id, int mailboxType,
255            int count, boolean showAlways) {
256        if (id >= 0) {
257            throw new IllegalArgumentException(); // Must be QUERY_ALL_*, which are all negative
258        }
259        if (showAlways || (count > 0)) {
260            addMailboxRow(
261                    cursor, id, "", mailboxType, count, count, ROW_TYPE_MAILBOX, Mailbox.FLAG_NONE,
262                    Account.ACCOUNT_ID_COMBINED_VIEW);
263        }
264    }
265
266    /**
267     * Loads mailboxes that are the children of a given mailbox ID.
268     *
269     * The returned {@link Cursor} is always a {@link CursorWithExtras}.
270     */
271    private static class MailboxFragmentLoader extends ThrottlingCursorLoader {
272        private final Context mContext;
273        private final long mAccountId;
274        private final long mParentKey;
275
276        MailboxFragmentLoader(Context context, long accountId, long parentKey) {
277            super(context, Mailbox.CONTENT_URI,
278                    (parentKey != Mailbox.NO_MAILBOX)
279                            ? SUBMAILBOX_PROJECTION
280                            : PROJECTION,
281                    MAILBOX_SELECTION_WITH_PARENT,
282                    new String[] { Long.toString(accountId), Long.toString(parentKey) },
283                    MAILBOX_ORDER_BY);
284            mContext = context;
285            mAccountId = accountId;
286            mParentKey = parentKey;
287        }
288
289        @Override
290        public void onContentChanged() {
291            if (sEnableUpdate) {
292                super.onContentChanged();
293            }
294        }
295
296        @Override
297        public Cursor loadInBackground() {
298            boolean parentRemoved = false;
299
300            final Cursor childMailboxCursor = super.loadInBackground();
301            final Cursor returnCursor;
302
303            final int childCount = childMailboxCursor.getCount();
304
305            if (mParentKey != Mailbox.NO_MAILBOX) {
306                // If we're not showing the top level mailboxes, add the "parent" mailbox.
307                final Cursor parentCursor = getContext().getContentResolver().query(
308                        Mailbox.CONTENT_URI, CURMAILBOX_PROJECTION, MAILBOX_SELECTION,
309                        new String[] { Long.toString(mAccountId), Long.toString(mParentKey) },
310                        null);
311                returnCursor = new MergeCursor(new Cursor[] { parentCursor, childMailboxCursor });
312            } else {
313                // TODO Add per-account starred mailbox support
314                final MatrixCursor starredCursor = new MatrixCursor(MATRIX_PROJECTION);
315                int accountStarredCount = Message.getFavoriteMessageCount(mContext, mAccountId);
316                if (accountStarredCount > 0) {
317                    // Only add "Starred", if there is at least one starred message
318                    final int totalStarredCount = Message.getFavoriteMessageCount(mContext);
319                    addCombinedMailboxRow(starredCursor, Mailbox.QUERY_ALL_FAVORITES,
320                            Mailbox.TYPE_MAIL, totalStarredCount, true);
321                }
322                returnCursor = new MergeCursor(new Cursor[] { starredCursor, childMailboxCursor });
323            }
324            return new CursorWithExtras(Utility.CloseTraceCursorWrapper.get(returnCursor),
325                    childCount);
326        }
327    }
328
329    /**
330     * Loader for mailboxes in "Combined view".
331     */
332    /*package*/ static class CombinedMailboxLoader extends ThrottlingCursorLoader {
333        private static final String[] ACCOUNT_PROJECTION = new String[] {
334                    EmailContent.RECORD_ID, AccountColumns.DISPLAY_NAME,
335                };
336        private static final int COLUMN_ACCOUND_ID = 0;
337        private static final int COLUMN_ACCOUNT_DISPLAY_NAME = 1;
338
339        private final Context mContext;
340
341        public CombinedMailboxLoader(Context context) {
342            super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, null);
343            mContext = context;
344        }
345
346        @Override
347        public Cursor loadInBackground() {
348            final Cursor accounts = super.loadInBackground();
349
350            // Build combined mailbox rows.
351            final MatrixCursor returnCursor = buildCombinedMailboxes(mContext, accounts);
352
353            // Add account rows.
354            accounts.moveToPosition(-1);
355            while (accounts.moveToNext()) {
356                final long accountId = accounts.getLong(COLUMN_ACCOUND_ID);
357                final String accountName = accounts.getString(COLUMN_ACCOUNT_DISPLAY_NAME);
358                final int unreadCount = Mailbox.getUnreadCountByAccountAndMailboxType(
359                        mContext, accountId, Mailbox.TYPE_INBOX);
360                addMailboxRow(returnCursor, accountId, accountName, Mailbox.TYPE_NONE,
361                        unreadCount, unreadCount, ROW_TYPE_ACCOUNT, Mailbox.FLAG_NONE,
362                        accountId);
363            }
364            return Utility.CloseTraceCursorWrapper.get(returnCursor);
365        }
366
367        /*package*/ static MatrixCursor buildCombinedMailboxes(Context context,
368                Cursor innerCursor) {
369            MatrixCursor cursor = new ClosingMatrixCursor(PROJECTION, innerCursor);
370            // Combined inbox -- show unread count
371            addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX,
372                    Mailbox.getUnreadCountByMailboxType(context, Mailbox.TYPE_INBOX), true);
373
374            // Favorite (starred) -- show # of favorites
375            addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL,
376                    Message.getFavoriteMessageCount(context), false);
377
378            // Drafts -- show # of drafts
379            addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS,
380                    Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_DRAFTS), false);
381
382            // Outbox -- # of outstanding messages
383            addCombinedMailboxRow(cursor, Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX,
384                    Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_OUTBOX), false);
385
386            return cursor;
387        }
388    }
389}
390