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