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