AccountSelectorAdapter.java revision 85bd26e21069b6ee3188be034025c207f4686684
1/*
2 * Copyright (C) 2010 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.google.common.annotations.VisibleForTesting;
20
21import com.android.email.R;
22import com.android.email.data.ClosingMatrixCursor;
23import com.android.email.data.ThrottlingCursorLoader;
24import com.android.emailcommon.provider.EmailContent;
25import com.android.emailcommon.provider.EmailContent.Account;
26import com.android.emailcommon.provider.EmailContent.MailboxColumns;
27import com.android.emailcommon.provider.Mailbox;
28import com.android.emailcommon.utility.Utility;
29
30import java.util.ArrayList;
31
32import android.content.ContentUris;
33import android.content.Context;
34import android.content.Loader;
35import android.database.Cursor;
36import android.database.MatrixCursor;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.view.ViewGroup;
40import android.widget.AdapterView;
41import android.widget.CursorAdapter;
42import android.widget.TextView;
43
44/**
45 * Account selector spinner.
46 *
47 * TODO Test it!
48 */
49public class AccountSelectorAdapter extends CursorAdapter {
50    /** meta data column for an account's unread count */
51    private static final String UNREAD_COUNT = "unreadCount";
52    /** meta data column for the row type; used for display purposes */
53    private static final String ROW_TYPE = "rowType";
54    /** meta data position of the currently selected account in the drop-down list */
55    private static final String ACCOUNT_POSITION = "accountPosition";
56    private static final int ROW_TYPE_HEADER = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
57    @SuppressWarnings("unused")
58    private static final int ROW_TYPE_MAILBOX = 0;
59    private static final int ROW_TYPE_ACCOUNT = 1;
60    private static final int ITEM_VIEW_TYPE_ACCOUNT = 0;
61    static final int UNKNOWN_POSITION = -1;
62    /** Projection for account database query */
63    private static final String[] ACCOUNT_PROJECTION = new String[] {
64        Account.ID,
65        Account.DISPLAY_NAME,
66        Account.EMAIL_ADDRESS,
67    };
68    /**
69     * Projection used for the selector display; we add meta data that doesn't exist in the
70     * account database, so, this should be a super-set of {@link #ACCOUNT_PROJECTION}.
71     */
72    private static final String[] ADAPTER_PROJECTION = new String[] {
73        ROW_TYPE,
74        Account.ID,
75        Account.DISPLAY_NAME,
76        Account.EMAIL_ADDRESS,
77        UNREAD_COUNT,
78        ACCOUNT_POSITION,
79    };
80
81    /** Sort order.  Show the default account first. */
82    private static final String ORDER_BY =
83            EmailContent.Account.IS_DEFAULT + " desc, " + EmailContent.Account.RECORD_ID;
84
85    private final LayoutInflater mInflater;
86    @SuppressWarnings("hiding")
87    private final Context mContext;
88
89    /**
90     * Returns a loader that can populate the account spinner.
91     * @param context a context
92     * @param accountId the ID of the currently viewed account
93     */
94    public static Loader<Cursor> createLoader(Context context, long accountId) {
95        return new AccountsLoader(context, accountId);
96    }
97
98    public AccountSelectorAdapter(Context context) {
99        super(context, null, 0 /* no auto-requery */);
100        mContext = context;
101        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
102    }
103
104    /**
105     * Invoked when the action bar needs the view of the text in the bar itself. The default
106     * is to show just the display name of the cursor at the given position.
107     */
108    @Override
109    public View getView(int position, View convertView, ViewGroup parent) {
110        if (!isAccountItem(position)) {
111            // asked to show a recent mailbox; instead, show the account associated w/ the mailbox
112            int newPosition = getAccountPosition(position);
113            if (newPosition != UNKNOWN_POSITION) {
114                position = newPosition;
115            }
116        }
117        return super.getView(position, convertView, parent);
118    }
119
120    @Override
121    public View getDropDownView(int position, View convertView, ViewGroup parent) {
122        Cursor c = getCursor();
123        c.moveToPosition(position);
124
125        View view;
126        if (c.getInt(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) {
127            view = mInflater.inflate(R.layout.account_selector_dropdown_header, parent, false);
128            final TextView displayNameView = (TextView) view.findViewById(R.id.display_name);
129            final String displayName = getAccountDisplayName(c);
130            displayNameView.setText(displayName);
131        } else {
132            view = mInflater.inflate(R.layout.account_selector_dropdown, parent, false);
133            final TextView displayNameView = (TextView) view.findViewById(R.id.display_name);
134            final TextView emailAddressView = (TextView) view.findViewById(R.id.email_address);
135            final TextView unreadCountView = (TextView) view.findViewById(R.id.unread_count);
136
137            final String displayName = getAccountDisplayName(position);
138            final String emailAddress = getAccountEmailAddress(position);
139
140            displayNameView.setText(displayName);
141
142            // Show the email address only when it's different from the display name.
143            if (displayName.equals(emailAddress)) {
144                emailAddressView.setVisibility(View.GONE);
145            } else {
146                emailAddressView.setVisibility(View.VISIBLE);
147                emailAddressView.setText(emailAddress);
148            }
149
150            unreadCountView.setText(UiUtilities.getMessageCountForUi(mContext,
151                    getAccountUnreadCount(position), false));
152        }
153        return view;
154    }
155
156    @Override
157    public void bindView(View view, Context context, Cursor cursor) {
158        TextView textView = (TextView) view.findViewById(R.id.display_name);
159        textView.setText(getAccountDisplayName(cursor));
160    }
161
162    @Override
163    public View newView(Context context, Cursor cursor, ViewGroup parent) {
164        return mInflater.inflate(R.layout.account_selector, parent, false);
165    }
166
167    @Override
168    public int getViewTypeCount() {
169        return 2;
170    }
171
172    @Override
173    public int getItemViewType(int position) {
174        Cursor c = getCursor();
175        c.moveToPosition(position);
176        return c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER
177                ? AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER
178                : ITEM_VIEW_TYPE_ACCOUNT;
179    }
180
181    @Override
182    public boolean isEnabled(int position) {
183        return (getItemViewType(position) != AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER);
184    }
185
186    public boolean isAccountItem(int position) {
187        Cursor c = getCursor();
188        c.moveToPosition(position);
189        return (c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_ACCOUNT);
190    }
191
192    private String getAccountDisplayName(int position) {
193        final Cursor c = getCursor();
194        return c.moveToPosition(position) ? getAccountDisplayName(c) : null;
195    }
196
197    private String getAccountEmailAddress(int position) {
198        final Cursor c = getCursor();
199        return c.moveToPosition(position) ? getAccountEmailAddress(c) : null;
200    }
201
202    private int getAccountUnreadCount(int position) {
203        final Cursor c = getCursor();
204        return c.moveToPosition(position) ? getAccountUnreadCount(c) : 0;
205    }
206
207    int getAccountPosition(int position) {
208        final Cursor c = getCursor();
209        return c.moveToPosition(position) ? getAccountPosition(c) : UNKNOWN_POSITION;
210    }
211
212    /** Returns the account ID extracted from the given cursor. */
213    private static long getAccountId(Cursor c) {
214        return c.getLong(c.getColumnIndex(Account.ID));
215    }
216
217    /** Returns the account name extracted from the given cursor. */
218    static String getAccountDisplayName(Cursor cursor) {
219        return cursor.getString(cursor.getColumnIndex(Account.DISPLAY_NAME));
220    }
221
222    /** Returns the email address extracted from the given cursor. */
223    private static String getAccountEmailAddress(Cursor cursor) {
224        return cursor.getString(cursor.getColumnIndex(Account.EMAIL_ADDRESS));
225    }
226
227    /** Returns the unread count extracted from the given cursor. */
228    private static int getAccountUnreadCount(Cursor cursor) {
229        return cursor.getInt(cursor.getColumnIndex(UNREAD_COUNT));
230    }
231
232    /** Returns the account position extracted from the given cursor. */
233    private static int getAccountPosition(Cursor cursor) {
234        return cursor.getInt(cursor.getColumnIndex(ACCOUNT_POSITION));
235    }
236
237    /**
238     * Load the account list.  The resulting cursor contains
239     * - Account info
240     * - # of unread messages in inbox
241     * - The "Combined view" row if there's more than one account.
242     */
243    @VisibleForTesting
244    static class AccountsLoader extends ThrottlingCursorLoader {
245        private final Context mContext;
246        private final long mAccountId;
247        public AccountsLoader(Context context, long accountId) {
248            // Super class loads a regular account cursor, but we replace it in loadInBackground().
249            super(context, EmailContent.Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null,
250                    ORDER_BY);
251            mContext = context;
252            mAccountId = accountId;
253        }
254
255        @Override
256        public Cursor loadInBackground() {
257            final Cursor accountsCursor = super.loadInBackground();
258            // Use ClosingMatrixCursor so that accountsCursor gets closed too when it's closed.
259            final CursorWithExtras resultCursor
260                    = new CursorWithExtras(ADAPTER_PROJECTION, accountsCursor);
261            final int accountPosition = addAccountsToCursor(resultCursor, accountsCursor);
262            addRecentsToCursor(resultCursor, accountPosition);
263            return Utility.CloseTraceCursorWrapper.get(resultCursor);
264        }
265
266        /** Adds the account list [with extra meta data] to the given matrix cursor */
267        private int addAccountsToCursor(MatrixCursor matrixCursor, Cursor accountCursor) {
268            int accountPosition = UNKNOWN_POSITION;
269            accountCursor.moveToPosition(-1);
270            // Add a header for the accounts
271            String header =
272                    mContext.getString(R.string.mailbox_list_account_selector_account_header);
273            addRow(matrixCursor, ROW_TYPE_HEADER, 0L, header, null, 0, UNKNOWN_POSITION);
274            int totalUnread = 0;
275            int currentPosition = 1;
276            while (accountCursor.moveToNext()) {
277                // Add account, with its unread count.
278                final long accountId = accountCursor.getLong(0);
279                final int unread = Mailbox.getUnreadCountByAccountAndMailboxType(
280                        mContext, accountId, Mailbox.TYPE_INBOX);
281                final String name = getAccountDisplayName(accountCursor);
282                final String emailAddress = getAccountEmailAddress(accountCursor);
283                addRow(matrixCursor, ROW_TYPE_ACCOUNT, accountId, name, emailAddress, unread,
284                    UNKNOWN_POSITION);
285                totalUnread += unread;
286                if (accountId == mAccountId) {
287                    accountPosition = currentPosition;
288                }
289                currentPosition++;
290            }
291            // Add "combined view" if more than one account exists
292            final int countAccounts = accountCursor.getCount();
293            if (countAccounts > 1) {
294                final String name = mContext.getResources().getString(
295                        R.string.mailbox_list_account_selector_combined_view);
296                final String accountCount = mContext.getResources().getQuantityString(
297                        R.plurals.number_of_accounts, countAccounts, countAccounts);
298                addRow(matrixCursor, ROW_TYPE_ACCOUNT, Account.ACCOUNT_ID_COMBINED_VIEW,
299                        name, accountCount, totalUnread,UNKNOWN_POSITION);
300            }
301            return accountPosition;
302        }
303
304        /**
305         * Adds the recent mailbox list to the given cursor.
306         * @param matrixCursor the cursor to add the list to
307         * @param accountPosition the cursor position of the currently selected account
308         */
309        private void addRecentsToCursor(CursorWithExtras matrixCursor, int accountPosition) {
310            if (mAccountId <= 0L || mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
311                // Currently selected account isn't usable for our purposes
312                return;
313            }
314            String emailAddress = null;
315            if (accountPosition != UNKNOWN_POSITION) {
316                matrixCursor.moveToPosition(accountPosition);
317                emailAddress =
318                        matrixCursor.getString(matrixCursor.getColumnIndex(Account.EMAIL_ADDRESS));
319            }
320            RecentMailboxManager mailboxManager = RecentMailboxManager.getInstance(mContext);
321            ArrayList<Long> recentMailboxes = null;
322            boolean useTwoPane = UiUtilities.useTwoPane(mContext);
323            if (!useTwoPane) {
324                // Do not display recent mailboxes in the account spinner for the two pane view
325                recentMailboxes = mailboxManager.getMostRecent(mAccountId, useTwoPane);
326            }
327            if (recentMailboxes != null && recentMailboxes.size() > 0) {
328                matrixCursor.mRecentCount = recentMailboxes.size();
329                String mailboxHeader = mContext.getString(
330                    R.string.mailbox_list_account_selector_mailbox_header_fmt, emailAddress);
331                addRow(matrixCursor, ROW_TYPE_HEADER, 0L, mailboxHeader, null, 0, UNKNOWN_POSITION);
332                for (long mailboxId : recentMailboxes) {
333                    final int unread = Utility.getFirstRowInt(mContext,
334                            ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
335                            new String[] { MailboxColumns.UNREAD_COUNT }, null, null, null, 0);
336                    final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
337                    addRow(matrixCursor, ROW_TYPE_MAILBOX, mailboxId, mailbox.mDisplayName, null,
338                            unread, accountPosition);
339                }
340            }
341            if (!useTwoPane) {
342                String name = mContext.getString(
343                        R.string.mailbox_list_account_selector_show_all_folders);
344                addRow(matrixCursor, ROW_TYPE_MAILBOX, Mailbox.NO_MAILBOX, name, null, 0,
345                        accountPosition);
346            }
347        }
348
349        /** Adds a row to the given cursor */
350        private void addRow(MatrixCursor cursor, int rowType, long id, String name,
351                String emailAddress, int unreadCount, int listPosition) {
352            cursor.newRow()
353                .add(rowType)
354                .add(id)
355                .add(name)
356                .add(emailAddress)
357                .add(unreadCount)
358                .add(listPosition);
359        }
360    }
361
362    /** Cursor with some extra meta data. */
363    static class CursorWithExtras extends ClosingMatrixCursor {
364        /** Number of account elements */
365        final int mAccountCount;
366        /** Number of recent mailbox elements */
367        int mRecentCount;
368
369        private CursorWithExtras(String[] columnNames, Cursor innerCursor) {
370            super(columnNames, innerCursor);
371            mAccountCount = (innerCursor == null) ? 0 : innerCursor.getCount();
372        }
373
374        /**
375         * Returns the cursor position of the item with the given ID. Or {@link #UNKNOWN_POSITION}
376         * if the given ID does not exist.
377         */
378        int getPosition(long id) {
379            moveToPosition(-1);
380            while(moveToNext()) {
381                if (id == getAccountId(this)) {
382                    return getPosition();
383                }
384            }
385            return UNKNOWN_POSITION;
386        }
387    }
388}
389