AbstractActivityController.java revision e0828393e175c9293c86a7490225f324cbec5eef
1/*******************************************************************************
2 *      Copyright (C) 2012 Google Inc.
3 *      Licensed to The Android Open Source Project.
4 *
5 *      Licensed under the Apache License, Version 2.0 (the "License");
6 *      you may not use this file except in compliance with the License.
7 *      You may obtain a copy of the License at
8 *
9 *           http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *      Unless required by applicable law or agreed to in writing, software
12 *      distributed under the License is distributed on an "AS IS" BASIS,
13 *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *      See the License for the specific language governing permissions and
15 *      limitations under the License.
16 *******************************************************************************/
17
18package com.android.mail.ui;
19
20import android.app.ActionBar;
21import android.app.ActionBar.LayoutParams;
22import android.app.Activity;
23import android.app.Dialog;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.CursorLoader;
27import android.content.Intent;
28import android.content.Loader;
29import android.database.Cursor;
30import android.net.Uri;
31import android.os.AsyncTask;
32import android.os.Bundle;
33import android.view.KeyEvent;
34import android.view.LayoutInflater;
35import android.view.Menu;
36import android.view.MenuInflater;
37import android.view.MenuItem;
38import android.view.MotionEvent;
39import android.widget.LinearLayout;
40
41import com.android.mail.R;
42import com.android.mail.ConversationListContext;
43import com.android.mail.compose.ComposeActivity;
44import com.android.mail.providers.Account;
45import com.android.mail.providers.AccountCacheProvider;
46import com.android.mail.providers.Conversation;
47import com.android.mail.providers.Folder;
48import com.android.mail.providers.Settings;
49import com.android.mail.providers.UIProvider;
50import com.android.mail.ui.AsyncRefreshTask;
51import com.android.mail.utils.LogUtils;
52import com.android.mail.utils.Utils;
53
54import com.google.common.collect.Sets;
55
56import java.util.Set;
57
58
59/**
60 * This is an abstract implementation of the Activity Controller. This class
61 * knows how to respond to menu items, state changes, layout changes, etc. It
62 * weaves together the views and listeners, dispatching actions to the
63 * respective underlying classes.
64 * <p>
65 * Even though this class is abstract, it should provide default implementations
66 * for most, if not all the methods in the ActivityController interface. This
67 * makes the task of the subclasses easier: OnePaneActivityController and
68 * TwoPaneActivityController can be concise when the common functionality is in
69 * AbstractActivityController.
70 * </p>
71 * <p>
72 * In the Gmail codebase, this was called BaseActivityController
73 * </p>
74 */
75public abstract class AbstractActivityController implements ActivityController {
76    private static final String SAVED_CONVERSATION = "saved-conversation";
77    private static final String SAVED_CONVERSATION_POSITION = "saved-conv-pos";
78    // Keys for serialization of various information in Bundles.
79    private static final String SAVED_LIST_CONTEXT = "saved-list-context";
80    private static final String SAVED_ACCOUNT = "saved-account";
81
82    /**
83     * Are we on a tablet device or not.
84     */
85    public final boolean IS_TABLET_DEVICE;
86
87    protected Account mAccount;
88    protected Folder mFolder;
89    protected ActionBarView mActionBarView;
90    protected final RestrictedActivity mActivity;
91    protected final Context mContext;
92    protected ConversationListContext mConvListContext;
93    private FetchAccountFolderTask mFetchAccountFolderTask;
94    protected Conversation mCurrentConversation;
95
96    protected ConversationListFragment mConversationListFragment;
97    /**
98     * The current mode of the application. All changes in mode are initiated by
99     * the activity controller. View mode changes are propagated to classes that
100     * attach themselves as listeners of view mode changes.
101     */
102    protected final ViewMode mViewMode;
103    protected ContentResolver mResolver;
104    protected FolderListFragment mFolderListFragment;
105    protected ConversationViewFragment mConversationViewFragment;
106    protected boolean isLoaderInitialized = false;
107    private AsyncRefreshTask mAsyncRefreshTask;
108
109    private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
110    protected Settings mCachedSettings;
111    private FetchSearchFolderTask mFetchSearchFolderTask;
112
113    protected static final String LOG_TAG = new LogUtils().getLogTag();
114    private static final int ACCOUNT_CURSOR_LOADER = 0;
115    private static final int ACCOUNT_SETTINGS_LOADER = 1;
116    private static final int FOLDER_CURSOR_LOADER = 2;
117
118    public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
119        mActivity = activity;
120        mViewMode = viewMode;
121        mContext = activity.getApplicationContext();
122        IS_TABLET_DEVICE = Utils.useTabletUI(mContext);
123    }
124
125    @Override
126    public synchronized void attachConversationList(ConversationListFragment fragment) {
127        // If there is an existing fragment, unregister it
128        if (mConversationListFragment != null) {
129            mViewMode.removeListener(mConversationListFragment);
130        }
131        mConversationListFragment = fragment;
132        // If the current fragment is non-null, add it as a listener.
133        if (fragment != null) {
134            mViewMode.addListener(mConversationListFragment);
135        }
136    }
137
138    @Override
139    public synchronized void attachFolderList(FolderListFragment fragment) {
140        // If there is an existing fragment, unregister it
141        if (mFolderListFragment != null) {
142            mViewMode.removeListener(mFolderListFragment);
143        }
144        mFolderListFragment = fragment;
145        if (fragment != null) {
146            mViewMode.addListener(mFolderListFragment);
147        }
148    }
149
150    @Override
151    public void attachConversationView(ConversationViewFragment conversationViewFragment) {
152        mConversationViewFragment = conversationViewFragment;
153    }
154
155    @Override
156    public void clearSubject() {
157        // TODO(viki): Auto-generated method stub
158    }
159
160    @Override
161    public Account getCurrentAccount() {
162        return mAccount;
163    }
164
165    @Override
166    public ConversationListContext getCurrentListContext() {
167        return mConvListContext;
168    }
169
170    @Override
171    public String getHelpContext() {
172        return "Mail";
173    }
174
175    @Override
176    public int getMode() {
177        return mViewMode.getMode();
178    }
179
180    @Override
181    public String getUnshownSubject(String subject) {
182        // Calculate how much of the subject is shown, and return the remaining.
183        return null;
184    }
185
186    @Override
187    public void handleConversationLoadError() {
188        // TODO(viki): Auto-generated method stub
189    }
190
191    /**
192     * Initialize the action bar. This is not visible to OnePaneController and
193     * TwoPaneController so they cannot override this behavior.
194     */
195    private void initCustomActionBarView() {
196        ActionBar actionBar = mActivity.getActionBar();
197        mActionBarView = (ActionBarView) LayoutInflater.from(mContext).inflate(
198                R.layout.actionbar_view, null);
199
200        if (actionBar != null && mActionBarView != null) {
201            // Why have a different variable for the same thing? We should apply
202            // the same actions
203            // on mActionBarView instead.
204            mActionBarView.initialize(mActivity, this, mViewMode, actionBar);
205            actionBar.setCustomView((LinearLayout) mActionBarView, new ActionBar.LayoutParams(
206                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
207            actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
208                    ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE);
209        }
210    }
211
212    /**
213     * Returns whether the conversation list fragment is visible or not.
214     * Different layouts will have their own notion on the visibility of
215     * fragments, so this method needs to be overriden.
216     *
217     * @return
218     */
219    protected abstract boolean isConversationListVisible();
220
221    @Override
222    public void onAccountChanged(Account account) {
223        if (!account.equals(mAccount)) {
224            mAccount = account;
225            onSettingsChanged(null);
226            restartSettingsLoader();
227            mActionBarView.setAccount(mAccount);
228            mActivity.invalidateOptionsMenu();
229            // Account changed; existing folder is invalid.
230            mFolder = null;
231            fetchAccountFolderInfo();
232        }
233    }
234
235    private void restartSettingsLoader() {
236        if (mAccount.settingsQueryUri != null) {
237            mActivity.getLoaderManager().restartLoader(ACCOUNT_SETTINGS_LOADER, null, this);
238        }
239    }
240
241    public void onSettingsChanged(Settings settings) {
242        mCachedSettings = settings;
243        resetActionBarIcon();
244    }
245
246    @Override
247    public Settings getSettings() {
248        return mCachedSettings;
249    }
250
251    private void fetchAccountFolderInfo() {
252        if (mFetchAccountFolderTask != null) {
253            mFetchAccountFolderTask.cancel(true);
254        }
255        mFetchAccountFolderTask = new FetchAccountFolderTask();
256        mFetchAccountFolderTask.execute();
257    }
258
259    private void fetchSearchFolder(Intent intent) {
260        if (mFetchSearchFolderTask != null) {
261            mFetchSearchFolderTask.cancel(true);
262        }
263        mFetchSearchFolderTask = new FetchSearchFolderTask(intent
264                .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
265        mFetchSearchFolderTask.execute();
266    }
267
268    @Override
269    public void onFolderChanged(Folder folder) {
270        if (folder != null && !folder.equals(mFolder)) {
271            setFolder(folder);
272            mConvListContext = ConversationListContext.forFolder(mContext, mAccount, mFolder);
273            showConversationList(mConvListContext);
274        }
275    }
276
277    private void setFolder(Folder folder) {
278        // Start watching folder for sync status.
279        if (folder != null && !folder.equals(mFolder)) {
280            mActionBarView.setRefreshInProgress(false);
281            mFolder = folder;
282            mActionBarView.setFolder(mFolder);
283            mActivity.getLoaderManager().restartLoader(FOLDER_CURSOR_LOADER, null, this);
284        } else if (folder == null) {
285            LogUtils.wtf(LOG_TAG, "Folder in setFolder is null");
286        }
287    }
288
289    @Override
290    public void onActivityResult(int requestCode, int resultCode, Intent data) {
291        // TODO(viki): Auto-generated method stub
292    }
293
294    @Override
295    public void onConversationListVisibilityChanged(boolean visible) {
296        // TODO(viki): Auto-generated method stub
297    }
298
299    /**
300     * By default, doing nothing is right. A two-pane controller will need to
301     * override this.
302     */
303    @Override
304    public void onConversationVisibilityChanged(boolean visible) {
305        // Do nothing.
306        return;
307    }
308
309    @Override
310    public boolean onCreate(Bundle savedState) {
311        // Initialize the action bar view.
312        initCustomActionBarView();
313        // Allow shortcut keys to function for the ActionBar and menus.
314        mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
315        mResolver = mActivity.getContentResolver();
316
317        // All the individual UI components listen for ViewMode changes. This
318        // simplifies the amount of logic in the AbstractActivityController, but increases the
319        // possibility of timing-related bugs.
320        mViewMode.addListener(this);
321        assert (mActionBarView != null);
322        mViewMode.addListener(mActionBarView);
323
324        restoreState(savedState);
325        return true;
326    }
327
328    @Override
329    public Dialog onCreateDialog(int id, Bundle bundle) {
330        // TODO(viki): Auto-generated method stub
331        return null;
332    }
333
334    @Override
335    public boolean onCreateOptionsMenu(Menu menu) {
336        MenuInflater inflater = mActivity.getMenuInflater();
337        inflater.inflate(mActionBarView.getOptionsMenuId(), menu);
338        mActionBarView.onCreateOptionsMenu(menu);
339        return true;
340    }
341
342    @Override
343    public boolean onKeyDown(int keyCode, KeyEvent event) {
344        // TODO(viki): Auto-generated method stub
345        return false;
346    }
347
348    @Override
349    public boolean onOptionsItemSelected(MenuItem item) {
350        int id = item.getItemId();
351        boolean handled = true;
352        switch (id) {
353            case android.R.id.home:
354                onUpPressed();
355                break;
356            case R.id.compose:
357                ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
358                break;
359            case R.id.show_all_folders:
360                showFolderList();
361                break;
362            case R.id.refresh:
363                requestFolderRefresh();
364                break;
365            case R.id.settings:
366                Utils.showSettings(mActivity.getActivityContext(), mAccount);
367                break;
368            case R.id.help_info_menu_item:
369                // TODO: enable context sensitive help
370                Utils.showHelp(mActivity.getActivityContext(), mAccount.helpIntentUri, null);
371                break;
372            default:
373                handled = false;
374                break;
375        }
376        return handled;
377    }
378
379    private void requestFolderRefresh() {
380        if (mFolder != null) {
381            if (mAsyncRefreshTask != null) {
382                mAsyncRefreshTask.cancel(true);
383            }
384            mAsyncRefreshTask = new AsyncRefreshTask(mContext, mFolder);
385            mAsyncRefreshTask.execute();
386        }
387    }
388
389    @Override
390    public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
391        // TODO(viki): Auto-generated method stub
392
393    }
394
395    @Override
396    public boolean onPrepareOptionsMenu(Menu menu) {
397        mActionBarView.onPrepareOptionsMenu(menu);
398        return true;
399    }
400
401    @Override
402    public void onPause() {
403        isLoaderInitialized = false;
404    }
405
406    @Override
407    public void onResume() {
408        if (mActionBarView != null) {
409            mActionBarView.onResume();
410        }
411    }
412
413    @Override
414    public void onSaveInstanceState(Bundle outState) {
415        if (mAccount != null) {
416            LogUtils.d(LOG_TAG, "Saving the account now");
417            outState.putParcelable(SAVED_ACCOUNT, mAccount);
418        }
419        if (mConvListContext != null) {
420            outState.putBundle(SAVED_LIST_CONTEXT, mConvListContext.toBundle());
421        }
422    }
423
424    @Override
425    public void onSearchRequested(String query) {
426        Intent intent = new Intent();
427        intent.setAction(Intent.ACTION_SEARCH);
428        intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query);
429        intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount);
430        intent.setComponent(mActivity.getComponentName());
431        mActivity.startActivity(intent);
432    }
433
434    @Override
435    public void onStartDragMode() {
436        // TODO(viki): Auto-generated method stub
437    }
438
439    @Override
440    public void onStop() {
441        // TODO(viki): Auto-generated method stub
442    }
443
444    @Override
445    public void onStopDragMode() {
446        // TODO(viki): Auto-generated method stub
447    }
448
449    /**
450     * {@inheritDoc} Subclasses must override this to listen to mode changes
451     * from the ViewMode. Subclasses <b>must</b> call the parent's
452     * onViewModeChanged since the parent will handle common state changes.
453     */
454    @Override
455    public void onViewModeChanged(int newMode) {
456        // Perform any mode specific work here.
457        // reset the action bar icon based on the mode. Why don't the individual
458        // controllers do
459        // this themselves?
460
461        // In conversation list mode, clean up the conversation.
462        if (newMode == ViewMode.CONVERSATION_LIST) {
463            // Clean up the conversation here.
464        }
465
466        // We don't want to invalidate the options menu when switching to
467        // conversation
468        // mode, as it will happen when the conversation finishes loading.
469        if (newMode != ViewMode.CONVERSATION) {
470            mActivity.invalidateOptionsMenu();
471        }
472    }
473
474    @Override
475    public void onWindowFocusChanged(boolean hasFocus) {
476        // TODO(viki): Auto-generated method stub
477    }
478
479    @Override
480    public void reloadSearch(String string) {
481        // TODO(viki): Auto-generated method stub
482    }
483
484    /**
485     * @param savedState
486     */
487    protected void restoreListContext(Bundle savedState) {
488        // TODO(viki): Restore the account, the folder, and the conversation, if any.
489        Bundle listContextBundle = savedState.getBundle(SAVED_LIST_CONTEXT);
490        if (listContextBundle != null) {
491            mConvListContext = ConversationListContext.forBundle(listContextBundle);
492            mFolder = mConvListContext.folder;
493        }
494    }
495
496    /**
497     * Restore the state from the previous bundle. Subclasses should call this
498     * method from the parent class, since it performs important UI
499     * initialization.
500     *
501     * @param savedState
502     */
503    protected void restoreState(Bundle savedState) {
504        final Intent intent = mActivity.getIntent();
505        if (savedState != null) {
506            restoreListContext(savedState);
507            mAccount = savedState.getParcelable(SAVED_ACCOUNT);
508            mActionBarView.setAccount(mAccount);
509            restartSettingsLoader();
510        } else if (intent != null) {
511            if (Intent.ACTION_VIEW.equals(intent.getAction())) {
512                if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
513                    mAccount = ((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
514                    mActionBarView.setAccount(mAccount);
515                    mActivity.getLoaderManager().restartLoader(ACCOUNT_SETTINGS_LOADER, null, this);
516                    mActivity.invalidateOptionsMenu();
517                }
518                if (intent.hasExtra(Utils.EXTRA_FOLDER)) {
519                    // Open the folder.
520                    LogUtils.d(LOG_TAG, "SHOW THE FOLDER at %s",
521                            intent.getParcelableExtra(Utils.EXTRA_FOLDER));
522                    onFolderChanged((Folder) intent.getParcelableExtra(Utils.EXTRA_FOLDER));
523                }
524                if (intent.hasExtra(Utils.EXTRA_CONVERSATION)) {
525                    // Open the conversation.
526                    LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
527                            intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
528                    showConversation((Conversation) intent
529                            .getParcelableExtra(Utils.EXTRA_CONVERSATION));
530                }
531            } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
532                mViewMode.enterSearchResultsListMode();
533                mAccount = ((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT));
534                mActionBarView.setAccount(mAccount);
535                fetchSearchFolder(intent);
536            }
537        }
538        // Create the accounts loader; this loads the account switch spinner.
539        mActivity.getLoaderManager().initLoader(ACCOUNT_CURSOR_LOADER, null, this);
540    }
541
542    @Override
543    public void setSubject(String subject) {
544        // Do something useful with the subject. This requires changing the
545        // conversation view's subject text.
546    }
547
548    @Override
549    public void startActionBarStatusCursorLoader(String account) {
550        // TODO(viki): Auto-generated method stub
551    }
552
553    @Override
554    public void stopActionBarStatusCursorLoader(String account) {
555        // TODO(viki): Auto-generated method stub
556    }
557
558    @Override
559    public void toggleStar(boolean toggleOn, long conversationId, long maxMessageId) {
560        // TODO(viki): Auto-generated method stub
561    }
562
563    @Override
564    public void onConversationSelected(Conversation conversation) {
565        mCurrentConversation = conversation;
566        showConversation(mCurrentConversation);
567        if (mConvListContext != null && mConvListContext.isSearchResult()) {
568            mViewMode.enterSearchResultsConversationMode();
569        } else {
570            mViewMode.enterConversationMode();
571        }
572    }
573
574    /**
575     * {@inheritDoc}
576     */
577    @Override
578    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
579        // Create a loader to listen in on account changes.
580        if (id == ACCOUNT_CURSOR_LOADER) {
581            return new CursorLoader(mContext, AccountCacheProvider.getAccountsUri(),
582                    UIProvider.ACCOUNTS_PROJECTION, null, null, null);
583        } else if (id == FOLDER_CURSOR_LOADER) {
584            return new CursorLoader(mActivity.getActivityContext(), mFolder.uri,
585                    UIProvider.FOLDERS_PROJECTION, null, null, null);
586        } else if (id == ACCOUNT_SETTINGS_LOADER) {
587            if (mAccount.settingsQueryUri != null) {
588                return new CursorLoader(mActivity.getActivityContext(), mAccount.settingsQueryUri,
589                        UIProvider.SETTINGS_PROJECTION, null, null, null);
590            }
591        }
592        return null;
593    }
594
595    private boolean accountsUpdated(Cursor accountCursor) {
596        // Check to see if the current account hasn't been set, or the account cursor is empty
597        if (mAccount == null || !accountCursor.moveToFirst()) {
598            return true;
599        }
600
601        // Check to see if the number of accounts are different, from the number we saw on the last
602        // updated
603        if (mCurrentAccountUris.size() != accountCursor.getCount()) {
604            return true;
605        }
606
607        // Check to see if the account list is different or if the current account is not found in
608        // the cursor.
609        boolean foundCurrentAccount = false;
610        do {
611            final Uri accountUri =
612                    Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
613            if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
614                foundCurrentAccount = true;
615            }
616
617            if (!mCurrentAccountUris.contains(accountUri)) {
618                return true;
619            }
620        } while (accountCursor.moveToNext());
621
622        // As long as we found the current account, the list hasn't been updated
623        return !foundCurrentAccount;
624    }
625
626    /**
627     * Update the accounts on the device. This currently loads the first account
628     * in the list.
629     *
630     * @param loader
631     * @param accounts cursor into the AccountCache
632     * @return true if the update was successful, false otherwise
633     */
634    private boolean updateAccounts(Loader<Cursor> loader, Cursor accounts) {
635        if (accounts == null || !accounts.moveToFirst()) {
636            return false;
637        }
638
639        final Account[] allAccounts = Account.getAllAccounts(accounts);
640
641        // Save the uris for the accounts
642        mCurrentAccountUris.clear();
643        for (Account account : allAccounts) {
644            mCurrentAccountUris.add(account.uri);
645        }
646
647        final Account newAccount;
648        if (mAccount == null || !mCurrentAccountUris.contains(mAccount.uri)) {
649            accounts.moveToFirst();
650            newAccount = new Account(accounts);
651        } else {
652            newAccount = mAccount;
653        }
654        // only bother updating the account/folder if the new account is different than the
655        // existing one
656        final boolean refetchFolderInfo = !newAccount.equals(mAccount);
657        onAccountChanged(newAccount);
658
659        if(refetchFolderInfo) {
660            fetchAccountFolderInfo();
661        }
662
663        mActionBarView.setAccounts(allAccounts);
664        return (allAccounts.length > 0);
665    }
666
667    /**
668     * {@inheritDoc}
669     */
670    @Override
671    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
672        // We want to reinitialize only if we haven't ever been initialized, or
673        // if the current account has vanished.
674        final int id = loader.getId();
675        if (data == null) {
676            LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", id);
677        }
678        if (id == ACCOUNT_CURSOR_LOADER) {
679
680            final boolean accountListUpdated = accountsUpdated(data);
681            if (!isLoaderInitialized || accountListUpdated) {
682                isLoaderInitialized = updateAccounts(loader, data);
683            }
684        } else if (id == FOLDER_CURSOR_LOADER) {
685            // Check status of the cursor.
686            if (data != null) {
687                data.moveToFirst();
688                Folder folder = new Folder(data);
689                if (folder.isSyncInProgress()) {
690                    mActionBarView.onRefreshStarted();
691                } else {
692                    // Stop the spinner here.
693                    mActionBarView.onRefreshStopped(folder.lastSyncResult);
694                }
695                LogUtils.v(LOG_TAG, "FOLDER STATUS = " + folder.syncStatus);
696            }
697        } else if (id == ACCOUNT_SETTINGS_LOADER) {
698            if (data != null) {
699                data.moveToFirst();
700                onSettingsChanged(new Settings(data));
701            }
702        }
703    }
704
705    /**
706     * {@inheritDoc}
707     */
708    @Override
709    public void onLoaderReset(Loader<Cursor> loader) {
710        // Do nothing for now, since we don't have any state. When a load is
711        // finished, the
712        // onLoadFinished will be called and we will be fine.
713    }
714
715    @Override
716    public void onTouchEvent(MotionEvent event) {
717        if (event.getAction() == MotionEvent.ACTION_DOWN) {
718            int mode = mViewMode.getMode();
719            if (mode == ViewMode.CONVERSATION_LIST) {
720                mConversationListFragment.onTouchEvent(event);
721            } else if (mode == ViewMode.CONVERSATION) {
722                mConversationViewFragment.onTouchEvent(event);
723            }
724        }
725    }
726
727    private class FetchAccountFolderTask extends AsyncTask<Void, Void, ConversationListContext> {
728        @Override
729        public ConversationListContext doInBackground(Void... params) {
730            return ConversationListContext.forFolder(mContext, mAccount, mFolder);
731        }
732
733        @Override
734        public void onPostExecute(ConversationListContext result) {
735            mConvListContext = result;
736            setFolder(mConvListContext.folder);
737            if (mFolderListFragment != null) {
738                mFolderListFragment.selectFolder(mConvListContext.folder);
739            }
740            showConversationList(mConvListContext);
741            mFetchAccountFolderTask = null;
742        }
743    }
744
745    private class FetchSearchFolderTask extends AsyncTask<Void, Void, Folder> {
746        String mQuery;
747        public FetchSearchFolderTask(String query) {
748            mQuery = query;
749        }
750        public Folder doInBackground(Void... params) {
751            Folder searchFolder = Folder.forSearchResults(mAccount, mQuery,
752                    mActivity.getActivityContext());
753            return searchFolder;
754        }
755        public void onPostExecute(Folder folder) {
756            setFolder(folder);
757            mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder, mQuery);
758            showConversationList(mConvListContext);
759            mActivity.invalidateOptionsMenu();
760        }
761    }
762}
763