• 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.app.Activity;
20 import android.app.FragmentTransaction;
21 import android.app.ListFragment;
22 import android.app.LoaderManager.LoaderCallbacks;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.CursorLoader;
26 import android.content.Loader;
27 import android.content.res.Resources;
28 import android.database.Cursor;
29 import android.database.MatrixCursor;
30 import android.database.MatrixCursor.RowBuilder;
31 import android.database.MergeCursor;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.view.View;
35 import android.widget.AdapterView;
36 import android.widget.AdapterView.OnItemClickListener;
37 import android.widget.ListView;
38 import android.widget.SimpleCursorAdapter;
39 
40 import com.android.email.R;
41 import com.android.emailcommon.provider.Account;
42 import com.android.emailcommon.provider.EmailContent.AccountColumns;
43 import com.android.emailcommon.provider.EmailContent.MailboxColumns;
44 import com.android.emailcommon.provider.HostAuth;
45 import com.android.emailcommon.provider.Mailbox;
46 
47 /**
48  * Fragment containing a list of accounts to show during shortcut creation.
49  * <p>
50  * NOTE: In order to receive callbacks, the activity containing this fragment must implement
51  * the {@link PickerCallback} interface.
52  */
53 public abstract class ShortcutPickerFragment extends ListFragment
54         implements OnItemClickListener, LoaderCallbacks<Cursor> {
55     /** Callback methods. Enclosing activities must implement to receive fragment notifications. */
56     public static interface PickerCallback {
57         /** Builds a mailbox filter for the given account. See MailboxShortcutPickerFragment. */
buildFilter(Account account)58         public Integer buildFilter(Account account);
59         /** Invoked when an account and mailbox have been selected. */
onSelected(Account account, long mailboxId)60         public void onSelected(Account account, long mailboxId);
61         /** Required data is missing; either the account and/or mailbox */
onMissingData(boolean missingAccount, boolean missingMailbox)62         public void onMissingData(boolean missingAccount, boolean missingMailbox);
63     }
64 
65     /** A no-op callback */
66     private final PickerCallback EMPTY_CALLBACK = new PickerCallback() {
67         @Override public Integer buildFilter(Account account) { return null; }
68         @Override public void onSelected(Account account, long mailboxId){ getActivity().finish(); }
69         @Override public void onMissingData(boolean missingAccount, boolean missingMailbox) { }
70     };
71     private final static int LOADER_ID = 0;
72     private final static int[] TO_VIEWS = new int[] {
73         android.R.id.text1,
74     };
75 
76     PickerCallback mCallback = EMPTY_CALLBACK;
77     /** Cursor adapter that provides either the account or mailbox list */
78     private SimpleCursorAdapter mAdapter;
79 
80     @Override
onAttach(Activity activity)81     public void onAttach(Activity activity) {
82         super.onAttach(activity);
83 
84         if (activity instanceof PickerCallback) {
85             mCallback = (PickerCallback) activity;
86         }
87         final String[] fromColumns = getFromColumns();
88         mAdapter = new SimpleCursorAdapter(activity,
89             android.R.layout.simple_expandable_list_item_1, null, fromColumns, TO_VIEWS, 0);
90         setListAdapter(mAdapter);
91 
92         getLoaderManager().initLoader(LOADER_ID, null, this);
93     }
94 
95     @Override
onActivityCreated(Bundle savedInstanceState)96     public void onActivityCreated(Bundle savedInstanceState) {
97         super.onActivityCreated(savedInstanceState);
98 
99         ListView listView = getListView();
100         listView.setOnItemClickListener(this);
101         listView.setItemsCanFocus(false);
102     }
103 
104     @Override
onLoadFinished(Loader<Cursor> loader, Cursor data)105     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
106         mAdapter.swapCursor(data);
107     }
108 
109     @Override
onLoaderReset(Loader<Cursor> loader)110     public void onLoaderReset(Loader<Cursor> loader) {
111         mAdapter.swapCursor(null);
112     }
113 
114     /** Returns the cursor columns to map into list */
getFromColumns()115     abstract String[] getFromColumns();
116 
117     // TODO if we add meta-accounts to the database, remove this class entirely
118     private static final class AccountPickerLoader extends CursorLoader {
AccountPickerLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)119         public AccountPickerLoader(Context context, Uri uri, String[] projection, String selection,
120                 String[] selectionArgs, String sortOrder) {
121             super(context, uri, projection, selection, selectionArgs, sortOrder);
122         }
123 
124         @Override
loadInBackground()125         public Cursor loadInBackground() {
126             Cursor parentCursor = super.loadInBackground();
127             int cursorCount = parentCursor.getCount();
128             final Cursor returnCursor;
129 
130             if (cursorCount > 1) {
131                 // Only add "All accounts" if there is more than 1 account defined
132                 MatrixCursor allAccountCursor = new MatrixCursor(getProjection());
133                 addCombinedAccountRow(allAccountCursor, cursorCount);
134                 returnCursor = new MergeCursor(new Cursor[] { allAccountCursor, parentCursor });
135             } else {
136                 returnCursor = parentCursor;
137             }
138             return returnCursor;
139         }
140 
141         /** Adds a row for "All Accounts" into the given cursor */
addCombinedAccountRow(MatrixCursor cursor, int accountCount)142         private void addCombinedAccountRow(MatrixCursor cursor, int accountCount) {
143             Context context = getContext();
144             Account account = new Account();
145             account.mId = Account.ACCOUNT_ID_COMBINED_VIEW;
146             Resources res = context.getResources();
147             String countString = res.getQuantityString(R.plurals.picker_combined_view_account_count,
148                     accountCount, accountCount);
149             account.mDisplayName = res.getString(R.string.picker_combined_view_fmt, countString);
150             ContentValues values = account.toContentValues();
151             RowBuilder row = cursor.newRow();
152             for (String rowName : cursor.getColumnNames()) {
153                 // special case some of the rows ...
154                 if (AccountColumns.ID.equals(rowName)) {
155                     row.add(Account.ACCOUNT_ID_COMBINED_VIEW);
156                     continue;
157                 } else if (AccountColumns.IS_DEFAULT.equals(rowName)) {
158                     row.add(0);
159                     continue;
160                 }
161                 row.add(values.get(rowName));
162             }
163         }
164     }
165 
166     /** Account picker */
167     public static class AccountShortcutPickerFragment extends ShortcutPickerFragment {
168         private final static String[] ACCOUNT_FROM_COLUMNS = new String[] {
169             AccountColumns.DISPLAY_NAME,
170         };
171 
172         @Override
onActivityCreated(Bundle savedInstanceState)173         public void onActivityCreated(Bundle savedInstanceState) {
174             super.onActivityCreated(savedInstanceState);
175             getActivity().setTitle(R.string.account_shortcut_picker_title);
176         }
177 
178         @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)179         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
180             Cursor cursor = (Cursor) parent.getItemAtPosition(position);
181             selectAccountCursor(cursor, true);
182         }
183 
184         @Override
onCreateLoader(int id, Bundle args)185         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
186             Context context = getActivity();
187             return new AccountPickerLoader(
188                 context, Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, null);
189         }
190 
191         @Override
onLoadFinished(Loader<Cursor> loader, Cursor data)192         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
193             // if there is only one account, auto-select it
194             // No accounts; close the dialog
195             if (data.getCount() == 0) {
196                 mCallback.onMissingData(true, false);
197                 return;
198             }
199             if (data.getCount() == 1 && data.moveToFirst()) {
200                 selectAccountCursor(data, false);
201                 return;
202             }
203             super.onLoadFinished(loader, data);
204         }
205 
206         @Override
getFromColumns()207         String[] getFromColumns() {
208             return ACCOUNT_FROM_COLUMNS;
209         }
210 
211         /** Selects the account specified by the given cursor */
selectAccountCursor(Cursor cursor, boolean allowBack)212         private void selectAccountCursor(Cursor cursor, boolean allowBack) {
213             Account account = new Account();
214             account.restore(cursor);
215             ShortcutPickerFragment fragment = MailboxShortcutPickerFragment.newInstance(
216                     getActivity(), account, mCallback.buildFilter(account));
217             FragmentTransaction transaction = getFragmentManager().beginTransaction();
218             transaction.replace(R.id.shortcut_list, fragment);
219             if (allowBack) {
220                 transaction.addToBackStack(null);
221             }
222             transaction.commitAllowingStateLoss();
223         }
224     }
225 
226     // TODO if we add meta-mailboxes to the database, remove this class entirely
227     private static final class MailboxPickerLoader extends CursorLoader {
228         private final long mAccountId;
229         private final boolean mAllowUnread;
MailboxPickerLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, long accountId, boolean allowUnread)230         public MailboxPickerLoader(Context context, Uri uri, String[] projection, String selection,
231                 String[] selectionArgs, String sortOrder, long accountId, boolean allowUnread) {
232             super(context, uri, projection, selection, selectionArgs, sortOrder);
233             mAccountId = accountId;
234             mAllowUnread = allowUnread;
235         }
236 
237         @Override
loadInBackground()238         public Cursor loadInBackground() {
239             MatrixCursor unreadCursor =
240                     new MatrixCursor(MailboxShortcutPickerFragment.MATRIX_PROJECTION);
241             Context context = getContext();
242             if (mAllowUnread) {
243                 // For the special mailboxes, their ID is < 0. The UI list does not deal with
244                 // negative values very well, so, add MAX_VALUE to ensure they're positive, but,
245                 // don't clash with legitimate mailboxes.
246                 String mailboxName = context.getString(R.string.picker_mailbox_name_all_unread);
247                 unreadCursor.addRow(
248                         new Object[] {
249                             Integer.MAX_VALUE + Mailbox.QUERY_ALL_UNREAD,
250                             Mailbox.QUERY_ALL_UNREAD,
251                             mailboxName,
252                         });
253             }
254 
255             if (mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
256                 // Do something special for the "combined" view
257                 MatrixCursor combinedMailboxesCursor =
258                         new MatrixCursor(MailboxShortcutPickerFragment.MATRIX_PROJECTION);
259                 // For the special mailboxes, their ID is < 0. The UI list does not deal with
260                 // negative values very well, so, add MAX_VALUE to ensure they're positive, but,
261                 // don't clash with legitimate mailboxes.
262                 String mailboxName = context.getString(R.string.picker_mailbox_name_all_inbox);
263                 combinedMailboxesCursor.addRow(
264                         new Object[] {
265                             Integer.MAX_VALUE + Mailbox.QUERY_ALL_INBOXES,
266                             Mailbox.QUERY_ALL_INBOXES,
267                             mailboxName
268                         });
269                 return new MergeCursor(new Cursor[] { combinedMailboxesCursor, unreadCursor });
270             }
271 
272             // Loading for a regular account; perform a normal load
273             return new MergeCursor(new Cursor[] { super.loadInBackground(), unreadCursor });
274         }
275     }
276 
277     /** Mailbox picker */
278     public static class MailboxShortcutPickerFragment extends ShortcutPickerFragment {
279         /** Allow all mailboxes in the mailbox list */
280         public static int FILTER_ALLOW_ALL    = 0;
281         /** Only allow an account's INBOX */
282         public static int FILTER_INBOX_ONLY   = 1 << 0;
283         /** Allow an "unread" mailbox; this is not affected by {@link #FILTER_INBOX_ONLY} */
284         public static int FILTER_ALLOW_UNREAD = 1 << 1;
285         /** Fragment argument to set filter values */
286         static final String ARG_FILTER  = "MailboxShortcutPickerFragment.filter";
287         static final String ARG_ACCOUNT = "MailboxShortcutPickerFragment.account";
288 
289         private final static String REAL_ID = "realId";
290         private final static String[] MAILBOX_FROM_COLUMNS = new String[] {
291             MailboxColumns.DISPLAY_NAME,
292         };
293         /** Loader projection used for IMAP & POP3 accounts */
294         private final static String[] IMAP_PROJECTION = new String [] {
295             MailboxColumns.ID, MailboxColumns.ID + " as " + REAL_ID,
296             MailboxColumns.SERVER_ID + " as " + MailboxColumns.DISPLAY_NAME
297         };
298         /** Loader projection used for EAS accounts */
299         private final static String[] EAS_PROJECTION = new String [] {
300             MailboxColumns.ID, MailboxColumns.ID + " as " + REAL_ID,
301             MailboxColumns.DISPLAY_NAME
302         };
303         /** Loader projection used for a matrix cursor */
304         private final static String[] MATRIX_PROJECTION = new String [] {
305             MailboxColumns.ID, REAL_ID, MailboxColumns.DISPLAY_NAME
306         };
307         // TODO #ALL_MAILBOX_SELECTION is identical to MailboxesAdapter#ALL_MAILBOX_SELECTION;
308         // create a common selection. Move this to the Mailbox class?
309         /** Selection for all visible mailboxes for an account */
310         private final static String ALL_MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" +
311                 " AND " + Mailbox.USER_VISIBLE_MAILBOX_SELECTION;
312         /** Selection for just the INBOX of an account */
313         private final static String INBOX_ONLY_SELECTION = ALL_MAILBOX_SELECTION +
314                     " AND " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX;
315         /** The currently selected account */
316         private Account mAccount;
317         /** The filter values; default to allow all mailboxes */
318         private Integer mFilter;
319 
320         /**
321          * Builds a mailbox shortcut picker for the given account.
322          */
newInstance( Context context, Account account, Integer filter)323         public static MailboxShortcutPickerFragment newInstance(
324                 Context context, Account account, Integer filter) {
325 
326             MailboxShortcutPickerFragment fragment = new MailboxShortcutPickerFragment();
327             Bundle args = new Bundle();
328             args.putParcelable(ARG_ACCOUNT, account);
329             args.putInt(ARG_FILTER, filter);
330             fragment.setArguments(args);
331             return fragment;
332         }
333 
334         /** Returns the mailbox filter */
getFilter()335         int getFilter() {
336             if (mFilter == null) {
337                 mFilter = getArguments().getInt(ARG_FILTER, FILTER_ALLOW_ALL);
338             }
339             return mFilter;
340         }
341 
342         @Override
onAttach(Activity activity)343         public void onAttach(Activity activity) {
344             // Need to setup the account first thing
345             mAccount = getArguments().getParcelable(ARG_ACCOUNT);
346             super.onAttach(activity);
347         }
348 
349         @Override
onActivityCreated(Bundle savedInstanceState)350         public void onActivityCreated(Bundle savedInstanceState) {
351             super.onActivityCreated(savedInstanceState);
352             getActivity().setTitle(R.string.mailbox_shortcut_picker_title);
353         }
354 
355         @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)356         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
357             Cursor cursor = (Cursor) parent.getItemAtPosition(position);
358             long mailboxId = cursor.getLong(cursor.getColumnIndex(REAL_ID));
359             mCallback.onSelected(mAccount, mailboxId);
360         }
361 
362         @Override
onCreateLoader(int id, Bundle args)363         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
364             Context context = getActivity();
365             // TODO Create a fully-qualified path name for Exchange accounts [code should also work
366             //      for MoveMessageToDialog.java]
367             HostAuth recvAuth = mAccount.getOrCreateHostAuthRecv(context);
368             final String[] projection;
369             final String orderBy;
370             final String selection;
371             if (recvAuth.isEasConnection()) {
372                 projection = EAS_PROJECTION;
373                 orderBy = MailboxColumns.DISPLAY_NAME;
374             } else {
375                 projection = IMAP_PROJECTION;
376                 orderBy = MailboxColumns.SERVER_ID;
377             }
378             if ((getFilter() & FILTER_INBOX_ONLY) == 0) {
379                 selection = ALL_MAILBOX_SELECTION;
380             } else {
381                 selection = INBOX_ONLY_SELECTION;
382             }
383             return new MailboxPickerLoader(
384                 context, Mailbox.CONTENT_URI, projection, selection,
385                 new String[] { Long.toString(mAccount.mId) }, orderBy, mAccount.mId,
386                 (getFilter() & FILTER_ALLOW_UNREAD) != 0);
387         }
388 
389         @Override
onLoadFinished(Loader<Cursor> loader, Cursor data)390         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
391             // No accounts; close the dialog
392             if (data.getCount() == 0) {
393                 mCallback.onMissingData(false, true);
394                 return;
395             }
396             // if there is only one mailbox, auto-select it
397             if (data.getCount() == 1 && data.moveToFirst()) {
398                 long mailboxId = data.getLong(data.getColumnIndex(REAL_ID));
399                 mCallback.onSelected(mAccount, mailboxId);
400                 return;
401             }
402             super.onLoadFinished(loader, data);
403         }
404 
405         @Override
getFromColumns()406         String[] getFromColumns() {
407             return MAILBOX_FROM_COLUMNS;
408         }
409     }
410 }
411