FolderListFragment.java revision 4a4de37596a8172c767749c361c1ebbba082d56f
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.Activity;
21import android.app.ListFragment;
22import android.app.LoaderManager;
23import android.content.CursorLoader;
24import android.content.Loader;
25import android.database.Cursor;
26import android.database.DataSetObserver;
27import android.net.Uri;
28import android.os.Bundle;
29import android.view.LayoutInflater;
30import android.view.View;
31import android.view.ViewGroup;
32import android.widget.ArrayAdapter;
33import android.widget.BaseAdapter;
34import android.widget.ImageView;
35import android.widget.ListAdapter;
36import android.widget.ListView;
37
38import com.android.mail.R;
39import com.android.mail.adapter.DrawerItem;
40import com.android.mail.providers.*;
41import com.android.mail.providers.UIProvider.FolderType;
42import com.android.mail.utils.LogTag;
43import com.android.mail.utils.LogUtils;
44import java.util.ArrayList;
45import java.util.Iterator;
46import java.util.List;
47
48/**
49 * The folder list UI component.
50 */
51public final class FolderListFragment extends ListFragment implements
52        LoaderManager.LoaderCallbacks<Cursor> {
53    private static final String LOG_TAG = LogTag.getLogTag();
54    /** The parent activity */
55    private ControllableActivity mActivity;
56    /** The underlying list view */
57    private ListView mListView;
58    /** URI that points to the list of folders for the current account. */
59    private Uri mFolderListUri;
60    /** True if you want a sectioned FolderList, false otherwise. */
61    private boolean mIsSectioned;
62    /** Is the current device using tablet UI (true if 2-pane, false if 1-pane) */
63    private boolean mIsTabletUI;
64    /** An {@link ArrayList} of {@link FolderType}s to exclude from displaying. */
65    private ArrayList<Integer> mExcludedFolderTypes;
66    /** Object that changes folders on our behalf. */
67    private FolderListSelectionListener mFolderChanger;
68    /** Object that changes accounts on our behalf */
69    private AccountController mAccountChanger;
70
71    /** The currently selected folder (the folder being viewed).  This is never null. */
72    private Uri mSelectedFolderUri = Uri.EMPTY;
73    /**
74     * The current folder from the controller.  This is meant only to check when the unread count
75     * goes out of sync and fixing it. This should NOT be serialized and stored.
76     */
77    private Folder mCurrentFolderForUnreadCheck;
78    /** Parent of the current folder, or null if the current folder is not a child. */
79    private Folder mParentFolder;
80
81    private static final int FOLDER_LOADER_ID = 0;
82    /** Key to store {@link #mParentFolder}. */
83    private static final String ARG_PARENT_FOLDER = "arg-parent-folder";
84    /** Key to store {@link #mFolderListUri}. */
85    private static final String ARG_FOLDER_URI = "arg-folder-list-uri";
86    /** Key to store {@link #mIsSectioned} */
87    private static final String ARG_IS_SECTIONED = "arg-is-sectioned";
88    /** Key to store {@link #mIsTabletUI} */
89    private static final String ARG_IS_TABLET_UI = "arg-is-tablet-ui";
90    /** Key to store {@link #mExcludedFolderTypes} */
91    private static final String ARG_EXCLUDED_FOLDER_TYPES = "arg-excluded-folder-types";
92    /** Should the {@link FolderListFragment} show less labels to begin with? */
93    private static final boolean ARE_ITEMS_COLLAPSED = true;
94
95    private static final String BUNDLE_LIST_STATE = "flf-list-state";
96    private static final String BUNDLE_SELECTED_FOLDER = "flf-selected-folder";
97    private static final String BUNDLE_SELECTED_TYPE = "flf-selected-type";
98
99    private FolderListFragmentCursorAdapter mCursorAdapter;
100    /** View that we show while we are waiting for the folder list to load */
101    private View mEmptyView;
102    /** Observer to wait for changes to the current folder so we can change the selected folder */
103    private FolderObserver mFolderObserver = null;
104    /** Listen for account changes. */
105    private AccountObserver mAccountObserver = null;
106
107    /** Listen to changes to list of all accounts */
108    private AllAccountObserver mAllAccountObserver = null;
109    /**
110     * Type of currently selected folder: {@link DrawerItem#FOLDER_SYSTEM},
111     * {@link DrawerItem#FOLDER_RECENT} or {@link DrawerItem#FOLDER_USER}.
112     */
113    // Setting to INERT_HEADER = leaving uninitialized.
114    private int mSelectedFolderType = DrawerItem.INERT_HEADER;
115    private Cursor mFutureData;
116    private ConversationListCallbacks mConversationListCallback;
117    /** The current account according to the controller */
118    private Account mCurrentAccount;
119
120    /** List of all accounts currently known */
121    private Account[] mAllAccounts;
122
123    /**
124     * Constructor needs to be public to handle orientation changes and activity lifecycle events.
125     */
126    public FolderListFragment() {
127        super();
128    }
129
130    /**
131     * Creates a new instance of {@link ConversationListFragment}, initialized
132     * to display conversation list context.
133     * @param isSectioned True if sections should be shown for folder list
134     * @param isTabletUI True if two-pane layout, false if not
135     */
136    public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri,
137            boolean isSectioned, boolean isTabletUI) {
138        return newInstance(parentFolder, folderUri, isSectioned, null, isTabletUI);
139    }
140
141    /**
142     * Creates a new instance of {@link ConversationListFragment}, initialized
143     * to display conversation list context.
144     * @param isSectioned True if sections should be shown for folder list
145     * @param excludedFolderTypes A list of {@link FolderType}s to exclude from displaying
146     * @param isTabletUI True if two-pane layout, false if not
147     */
148    public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri,
149            boolean isSectioned, final ArrayList<Integer> excludedFolderTypes,
150            boolean isTabletUI) {
151        final FolderListFragment fragment = new FolderListFragment();
152        final Bundle args = new Bundle();
153        if (parentFolder != null) {
154            args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
155        }
156        args.putString(ARG_FOLDER_URI, folderUri.toString());
157        args.putBoolean(ARG_IS_SECTIONED, isSectioned);
158        args.putBoolean(ARG_IS_TABLET_UI, isTabletUI);
159        if (excludedFolderTypes != null) {
160            args.putIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES, excludedFolderTypes);
161        }
162        fragment.setArguments(args);
163        return fragment;
164    }
165
166    @Override
167    public void onActivityCreated(Bundle savedState) {
168        super.onActivityCreated(savedState);
169        // Strictly speaking, we get back an android.app.Activity from getActivity. However, the
170        // only activity creating a ConversationListContext is a MailActivity which is of type
171        // ControllableActivity, so this cast should be safe. If this cast fails, some other
172        // activity is creating ConversationListFragments. This activity must be of type
173        // ControllableActivity.
174        final Activity activity = getActivity();
175        if (! (activity instanceof ControllableActivity)){
176            LogUtils.wtf(LOG_TAG, "FolderListFragment expects only a ControllableActivity to" +
177                    "create it. Cannot proceed.");
178        }
179        mActivity = (ControllableActivity) activity;
180        mConversationListCallback = mActivity.getListHandler();
181        final FolderController controller = mActivity.getFolderController();
182        // Listen to folder changes in the future
183        mFolderObserver = new FolderObserver() {
184            @Override
185            public void onChanged(Folder newFolder) {
186                setSelectedFolder(newFolder);
187            }
188        };
189        if (controller != null) {
190            // Only register for selected folder updates if we have a controller.
191            mCurrentFolderForUnreadCheck = mFolderObserver.initialize(controller);
192        }
193        final AccountController accountController = mActivity.getAccountController();
194        mAccountObserver = new AccountObserver() {
195            @Override
196            public void onChanged(Account newAccount) {
197                setSelectedAccount(newAccount);
198            }
199        };
200        if (accountController != null) {
201            // Current account and its observer.
202            mCurrentAccount = mAccountObserver.initialize(accountController);
203            // List of all accounts and its observer.
204            mAllAccountObserver = new AllAccountObserver(){
205                @Override
206                public void onChanged(Account[] allAccounts) {
207                    mAllAccounts = allAccounts;
208                }
209            };
210            mAllAccounts = mAllAccountObserver.initialize(accountController);
211            mAccountChanger = accountController;
212        }
213
214        mFolderChanger = mActivity.getFolderListSelectionListener();
215        if (mActivity.isFinishing()) {
216            // Activity is finishing, just bail.
217            return;
218        }
219
220        final Folder selectedFolder;
221        if (mParentFolder != null) {
222            mCursorAdapter = new HierarchicalFolderListAdapter(null, mParentFolder);
223            selectedFolder = mActivity.getHierarchyFolder();
224        } else {
225            // Initiate FLA with accounts and folders collapsed in the list
226            // The second param is for whether folders should be collapsed
227            // The third param is for whether accounts should be collapsed
228            mCursorAdapter = new FolderListAdapter(mIsSectioned,
229                    !mIsTabletUI && ARE_ITEMS_COLLAPSED,
230                    !mIsTabletUI && ARE_ITEMS_COLLAPSED);
231            selectedFolder = controller == null ? null : controller.getFolder();
232        }
233        // Is the selected folder fresher than the one we have restored from a bundle?
234        if (selectedFolder != null && !selectedFolder.uri.equals(mSelectedFolderUri)) {
235            setSelectedFolder(selectedFolder);
236        }
237        setListAdapter(mCursorAdapter);
238        // Set the region which gets highlighted since it might not have been set till now.
239        getLoaderManager().initLoader(FOLDER_LOADER_ID, Bundle.EMPTY, this);
240    }
241
242    @Override
243    public View onCreateView(LayoutInflater inflater, ViewGroup container,
244            Bundle savedState) {
245        final Bundle args = getArguments();
246        mFolderListUri = Uri.parse(args.getString(ARG_FOLDER_URI));
247        mParentFolder = (Folder) args.getParcelable(ARG_PARENT_FOLDER);
248        mIsSectioned = args.getBoolean(ARG_IS_SECTIONED);
249        mIsTabletUI = args.getBoolean(ARG_IS_TABLET_UI);
250        mExcludedFolderTypes = args.getIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES);
251        final View rootView = inflater.inflate(R.layout.folder_list, null);
252        mListView = (ListView) rootView.findViewById(android.R.id.list);
253        mListView.setHeaderDividersEnabled(false);
254        mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
255        mListView.setEmptyView(null);
256        if (savedState != null && savedState.containsKey(BUNDLE_LIST_STATE)) {
257            mListView.onRestoreInstanceState(savedState.getParcelable(BUNDLE_LIST_STATE));
258        }
259        mEmptyView = rootView.findViewById(R.id.empty_view);
260        if (savedState != null && savedState.containsKey(BUNDLE_SELECTED_FOLDER)) {
261            mSelectedFolderUri = Uri.parse(savedState.getString(BUNDLE_SELECTED_FOLDER));
262            mSelectedFolderType = savedState.getInt(BUNDLE_SELECTED_TYPE);
263        } else if (mParentFolder != null) {
264            mSelectedFolderUri = mParentFolder.uri;
265            // No selected folder type required for hierarchical lists.
266        }
267
268        return rootView;
269    }
270
271    @Override
272    public void onStart() {
273        super.onStart();
274    }
275
276    @Override
277    public void onStop() {
278        super.onStop();
279    }
280
281    @Override
282    public void onPause() {
283        super.onPause();
284    }
285
286    @Override
287    public void onSaveInstanceState(Bundle outState) {
288        super.onSaveInstanceState(outState);
289        if (mListView != null) {
290            outState.putParcelable(BUNDLE_LIST_STATE, mListView.onSaveInstanceState());
291        }
292        if (mSelectedFolderUri != null) {
293            outState.putString(BUNDLE_SELECTED_FOLDER, mSelectedFolderUri.toString());
294        }
295        outState.putInt(BUNDLE_SELECTED_TYPE, mSelectedFolderType);
296    }
297
298    @Override
299    public void onDestroyView() {
300        if (mCursorAdapter != null) {
301            mCursorAdapter.destroy();
302        }
303        // Clear the adapter.
304        setListAdapter(null);
305        if (mFolderObserver != null) {
306            mFolderObserver.unregisterAndDestroy();
307            mFolderObserver = null;
308        }
309        if (mAccountObserver != null) {
310            mAccountObserver.unregisterAndDestroy();
311            mAccountObserver = null;
312        }
313        if (mAllAccountObserver != null) {
314            mAllAccountObserver.unregisterAndDestroy();
315            mAllAccountObserver = null;
316        }
317        super.onDestroyView();
318    }
319
320    @Override
321    public void onListItemClick(ListView l, View v, int position, long id) {
322        viewFolderOrChangeAccount(position);
323    }
324
325    /**
326     * Display the conversation list from the folder at the position given.
327     * @param position a zero indexed position into the list.
328     */
329    private void viewFolderOrChangeAccount(int position) {
330        final Object item = getListAdapter().getItem(position);
331        final Folder folder;
332        if (item instanceof DrawerItem) {
333            final DrawerItem folderItem = (DrawerItem) item;
334            // Could be a folder, account, or expand block.
335            final int itemType = mCursorAdapter.getItemType(folderItem);
336            if (itemType == DrawerItem.VIEW_ACCOUNT) {
337                // Account, so switch.
338                folder = null;
339                final Account account = mCursorAdapter.getFullAccount(folderItem);
340                mAccountChanger.changeAccount(account);
341            } else if (itemType == DrawerItem.VIEW_FOLDER) {
342                // Folder type, so change folders only.
343                folder = mCursorAdapter.getFullFolder(folderItem);
344                mSelectedFolderType = folderItem.mFolderType;
345            } else {
346                // Block for expanding/contracting labels/accounts
347                folder = null;
348                if(!folderItem.mIsExpandForAccount) {
349                    mCursorAdapter.toggleShowLessFolders();
350                } else {
351                    mCursorAdapter.toggleShowLessAccounts();
352                }
353            }
354        } else if (item instanceof Folder) {
355            folder = (Folder) item;
356        } else {
357            folder = new Folder((Cursor) item);
358        }
359        if (folder != null) {
360            // Since we may be looking at hierarchical views, if we can
361            // determine the parent of the folder we have tapped, set it here.
362            // If we are looking at the folder we are already viewing, don't
363            // update its parent!
364            folder.parent = folder.equals(mParentFolder) ? null : mParentFolder;
365            // Go to the conversation list for this folder.
366            mFolderChanger.onFolderSelected(folder);
367        }
368    }
369
370    @Override
371    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
372        mListView.setEmptyView(null);
373        mEmptyView.setVisibility(View.GONE);
374        return new CursorLoader(mActivity.getActivityContext(), mFolderListUri,
375                UIProvider.FOLDERS_PROJECTION, null, null, null);
376    }
377
378    public void onAnimationEnd() {
379        if (mFutureData != null) {
380            updateCursorAdapter(mFutureData);
381            mFutureData = null;
382        }
383    }
384
385    private void updateCursorAdapter(Cursor data) {
386        mCursorAdapter.setCursor(data);
387        if (data == null || data.getCount() == 0) {
388            mEmptyView.setVisibility(View.VISIBLE);
389            mListView.setEmptyView(mEmptyView);
390        }
391    }
392
393    @Override
394    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
395        if (mConversationListCallback == null || !mConversationListCallback.isAnimating()) {
396            updateCursorAdapter(data);
397        } else {
398            mFutureData = data;
399            mCursorAdapter.setCursor(null);
400        }
401    }
402
403    @Override
404    public void onLoaderReset(Loader<Cursor> loader) {
405        mCursorAdapter.setCursor(null);
406    }
407
408    /**
409     * Interface for all cursor adapters that allow setting a cursor and being destroyed.
410     */
411    private interface FolderListFragmentCursorAdapter extends ListAdapter {
412        /** Update the folder list cursor with the cursor given here. */
413        void setCursor(Cursor cursor);
414        /** Toggles showing more accounts or less accounts. */
415        boolean toggleShowLessAccounts();
416        /** Toggles showing more folders or less. */
417        boolean toggleShowLessFolders();
418        /**
419         * Given an item, find the type of the item, which is {@link
420         * DrawerItem#VIEW_FOLDER}, {@link DrawerItem#VIEW_ACCOUNT} or
421         * {@link DrawerItem#VIEW_MORE}
422         * @return the type of the item.
423         */
424        int getItemType(DrawerItem item);
425        /** Get the folder associated with this item. **/
426        Folder getFullFolder(DrawerItem item);
427        /** Get the account associated with this item. **/
428        Account getFullAccount(DrawerItem item);
429        /** Remove all observers and destroy the object. */
430        void destroy();
431        /** Notifies the adapter that the data has changed. */
432        void notifyDataSetChanged();
433    }
434
435    /**
436     * An adapter for flat folder lists.
437     */
438    private class FolderListAdapter extends BaseAdapter implements FolderListFragmentCursorAdapter {
439
440        private final RecentFolderObserver mRecentFolderObserver = new RecentFolderObserver() {
441            @Override
442            public void onChanged() {
443                recalculateList();
444            }
445        };
446
447        /** After given number of accounts, show "more" until expanded. */
448        private static final int MAX_ACCOUNTS = 2;
449        /** After the given number of labels, show "more" until expanded. */
450        private static final int MAX_FOLDERS = 7;
451
452        private final RecentFolderList mRecentFolders;
453        /** True if the list is sectioned, false otherwise */
454        private final boolean mIsSectioned;
455        /** All the items */
456        private final List<DrawerItem> mItemList = new ArrayList<DrawerItem>();
457        /** Cursor into the folder list. This might be null. */
458        private Cursor mCursor = null;
459        /** Watcher for tracking and receiving unread counts for mail */
460        private FolderWatcher mFolderWatcher = null;
461        private boolean mShowLessFolders;
462        private boolean mShowLessAccounts;
463
464        /**
465         * Creates a {@link FolderListAdapter}.This is a flat folder list of all the folders for the
466         * given account.
467         * @param isSectioned TODO(viki):
468         */
469        public FolderListAdapter(boolean isSectioned, boolean showLess, boolean showLessAccounts) {
470            super();
471            mIsSectioned = isSectioned;
472            final RecentFolderController controller = mActivity.getRecentFolderController();
473            if (controller != null && mIsSectioned) {
474                mRecentFolders = mRecentFolderObserver.initialize(controller);
475            } else {
476                mRecentFolders = null;
477            }
478            mFolderWatcher = new FolderWatcher(mActivity, this);
479            mShowLessFolders = showLess;
480            mShowLessAccounts = showLessAccounts;
481            for (int i=0; i < mAllAccounts.length; i++) {
482                final Uri uri = mAllAccounts[i].settings.defaultInbox;
483                mFolderWatcher.startWatching(uri);
484            }
485        }
486
487        @Override
488        public View getView(int position, View convertView, ViewGroup parent) {
489            final DrawerItem item = (DrawerItem) getItem(position);
490            final View view = item.getView(position, convertView, parent);
491            final int type = item.mType;
492            if (mListView!= null) {
493                final boolean isSelected =
494                        item.isHighlighted(mCurrentFolderForUnreadCheck, mSelectedFolderType);
495                if (type == DrawerItem.VIEW_FOLDER) {
496                    mListView.setItemChecked(position, isSelected);
497                }
498                // If this is the current folder, also check to verify that the unread count
499                // matches what the action bar shows.
500                if (type == DrawerItem.VIEW_FOLDER
501                        && isSelected
502                        && (mCurrentFolderForUnreadCheck != null)
503                        && item.mFolder.unreadCount != mCurrentFolderForUnreadCheck.unreadCount) {
504                    ((FolderItemView) view).overrideUnreadCount(
505                            mCurrentFolderForUnreadCheck.unreadCount);
506                }
507            }
508            return view;
509        }
510
511        @Override
512        public int getViewTypeCount() {
513            // Accounts, headers and folders
514            return DrawerItem.getViewTypes();
515        }
516
517        @Override
518        public int getItemViewType(int position) {
519            return ((DrawerItem) getItem(position)).mType;
520        }
521
522        @Override
523        public int getCount() {
524            return mItemList.size();
525        }
526
527        @Override
528        public boolean isEnabled(int position) {
529            final DrawerItem item = (DrawerItem) getItem(position);
530            return item.isItemEnabled(getCurrentAccountUri());
531
532        }
533
534        private Uri getCurrentAccountUri() {
535            return mCurrentAccount == null ? Uri.EMPTY : mCurrentAccount.uri;
536        }
537
538        @Override
539        public boolean areAllItemsEnabled() {
540            // The headers and current accounts are not enabled.
541            return false;
542        }
543
544        /**
545         * Returns all the recent folders from the list given here. Safe to call with a null list.
546         * @param recentList a list of all recently accessed folders.
547         * @return a valid list of folders, which are all recent folders.
548         */
549        private List<Folder> getRecentFolders(RecentFolderList recentList) {
550            final List<Folder> folderList = new ArrayList<Folder>();
551            if (recentList == null) {
552                return folderList;
553            }
554            // Get all recent folders, after removing system folders.
555            for (final Folder f : recentList.getRecentFolderList(null)) {
556                if (!f.isProviderFolder()) {
557                    folderList.add(f);
558                }
559            }
560            return folderList;
561        }
562
563        /**
564         * Toggle boolean for what folders are shown and which ones are
565         * hidden. Redraws list after toggling to show changes.
566         * @return true if folders are hidden, false if all are shown
567         */
568        @Override
569        public boolean toggleShowLessFolders() {
570            mShowLessFolders = !mShowLessFolders;
571            recalculateList();
572            return mShowLessFolders;
573        }
574
575        /**
576         * Toggle boolean for what accounts are shown and which ones are
577         * hidden. Redraws list after toggling to show changes.
578         * @return true if accounts are hidden, false if all are shown
579         */
580        @Override
581        public boolean toggleShowLessAccounts() {
582            mShowLessAccounts = !mShowLessAccounts;
583            recalculateList();
584            return mShowLessAccounts;
585        }
586
587        /**
588         * Responsible for verifying mCursor, adding collapsed view items
589         * when necessary, and notifying the data set has changed.
590         */
591        private void recalculateList() {
592            if (mCursor == null || mCursor.isClosed() || mCursor.getCount() <= 0
593                    || !mCursor.moveToFirst()) {
594                return;
595            }
596            recalculateListFolders();
597            if(mShowLessFolders) {
598                mItemList.add(new DrawerItem(mActivity, R.string.folder_list_more, false));
599            }
600            // Ask the list to invalidate its views.
601            notifyDataSetChanged();
602        }
603
604        /**
605         * Recalculates the system, recent and user label lists.
606         * This method modifies all the three lists on every single invocation.
607         */
608        private void recalculateListFolders() {
609            mItemList.clear();
610            if (mAllAccounts != null) {
611                // Add the accounts at the top.
612                // TODO(shahrk): The logic here is messy and will be changed
613                //               to properly add/reflect on LRU/MRU account
614                //               changes similar to RecentFoldersList
615                int unreadCount;
616                if (mShowLessAccounts && mAllAccounts.length > MAX_ACCOUNTS) {
617                    mItemList.add(new DrawerItem(
618                            mActivity, R.string.folder_list_show_all_accounts, true));
619                    unreadCount = mFolderWatcher.get(
620                            mCurrentAccount.settings.defaultInbox).unreadCount;
621                    mItemList.add(new DrawerItem(mActivity, mCurrentAccount, unreadCount));
622                } else {
623                    Uri currentAccountUri = getCurrentAccountUri();
624                    for (final Account c : mAllAccounts) {
625                        if (!currentAccountUri.equals(c.uri)) {
626                            unreadCount = mFolderWatcher.get(c.settings.defaultInbox).unreadCount;
627                            mItemList.add(new DrawerItem(mActivity, c, unreadCount));
628                        }
629                    }
630                    unreadCount = mFolderWatcher.get(
631                            mCurrentAccount.settings.defaultInbox).unreadCount;
632                    mItemList.add(new DrawerItem(mActivity, mCurrentAccount, unreadCount));
633                }
634            }
635            if (!mIsSectioned) {
636                // Adapter for a flat list. Everything is a FOLDER_USER, and there are no headers.
637                do {
638                    final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor);
639                    if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) {
640                        mItemList.add(new DrawerItem(mActivity, f, DrawerItem.FOLDER_USER,
641                                mCursor.getPosition()));
642                    }
643                } while (mCursor.moveToNext());
644                // Ask the list to invalidate its views.
645                notifyDataSetChanged();
646                return;
647            }
648
649            // Tracks how many folders have been added through the rest of the function
650            int folderCount = 0;
651            // Otherwise, this is an adapter for a sectioned list.
652            // First add all the system folders.
653            final List<DrawerItem> userFolderList = new ArrayList<DrawerItem>();
654            do {
655                final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor);
656                if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) {
657                    if (f.isProviderFolder()) {
658                        mItemList.add(new DrawerItem(mActivity, f, DrawerItem.FOLDER_SYSTEM,
659                                mCursor.getPosition()));
660                        // Check if show less is enabled and we've passed max folders
661                        folderCount++;
662                        if(mShowLessFolders && folderCount >= MAX_FOLDERS) {
663                            return;
664                        }
665                    } else {
666                        userFolderList.add(new DrawerItem(
667                                mActivity, f, DrawerItem.FOLDER_USER, mCursor.getPosition()));
668                    }
669                }
670            } while (mCursor.moveToNext());
671            // If there are recent folders, add them and a header.
672            final List<Folder> recentFolderList = getRecentFolders(mRecentFolders);
673
674            // Remove any excluded folder types
675            if (mExcludedFolderTypes != null) {
676                final Iterator<Folder> iterator = recentFolderList.iterator();
677                while (iterator.hasNext()) {
678                    if (mExcludedFolderTypes.contains(iterator.next().type)) {
679                        iterator.remove();
680                    }
681                }
682            }
683
684            if (recentFolderList.size() > 0) {
685                mItemList.add(new DrawerItem(mActivity, R.string.recent_folders_heading));
686                for (Folder f : recentFolderList) {
687                    mItemList.add(new DrawerItem(mActivity, f, DrawerItem.FOLDER_RECENT, -1));
688                    // Check if show less is enabled and we've passed max folders
689                    folderCount++;
690                    if(mShowLessFolders && folderCount >= MAX_FOLDERS) {
691                        return;
692                    }
693                }
694            }
695            // If there are user folders, add them and a header.
696            if (userFolderList.size() > 0) {
697                mItemList.add(new DrawerItem(mActivity, R.string.all_folders_heading));
698                for (final DrawerItem i : userFolderList) {
699                    mItemList.add(i);
700                    // Check if show less is enabled and we've passed max folders
701                    folderCount++;
702                    if(mShowLessFolders && folderCount >= MAX_FOLDERS) {
703                        return;
704                    }
705                }
706            }
707        }
708
709        @Override
710        public void setCursor(Cursor cursor) {
711            mCursor = cursor;
712            recalculateList();
713        }
714
715        @Override
716        public Object getItem(int position) {
717            return mItemList.get(position);
718        }
719
720        @Override
721        public long getItemId(int position) {
722            return getItem(position).hashCode();
723        }
724
725        @Override
726        public final void destroy() {
727            mRecentFolderObserver.unregisterAndDestroy();
728        }
729
730        @Override
731        public int getItemType(DrawerItem item) {
732            return item.mType;
733        }
734
735        @Override
736        public Folder getFullFolder(DrawerItem folderItem) {
737            if (folderItem.mFolderType == DrawerItem.FOLDER_RECENT) {
738                return folderItem.mFolder;
739            } else {
740                int pos = folderItem.mPosition;
741                if (mFutureData != null) {
742                    mCursor = mFutureData;
743                    mFutureData = null;
744                }
745                if (pos > -1 && mCursor != null && !mCursor.isClosed()
746                        && mCursor.moveToPosition(folderItem.mPosition)) {
747                    return new Folder(mCursor);
748                } else {
749                    return null;
750                }
751            }
752        }
753
754        @Override
755        public Account getFullAccount(DrawerItem item) {
756            return item.mAccount;
757        }
758    }
759
760    private class HierarchicalFolderListAdapter extends ArrayAdapter<Folder>
761            implements FolderListFragmentCursorAdapter{
762
763        private static final int PARENT = 0;
764        private static final int CHILD = 1;
765        private final Uri mParentUri;
766        private final Folder mParent;
767        private final FolderItemView.DropHandler mDropHandler;
768        private Cursor mCursor;
769
770        public HierarchicalFolderListAdapter(Cursor c, Folder parentFolder) {
771            super(mActivity.getActivityContext(), R.layout.folder_item);
772            mDropHandler = mActivity;
773            mParent = parentFolder;
774            mParentUri = parentFolder.uri;
775            setCursor(c);
776        }
777
778        @Override
779        public int getViewTypeCount() {
780            // Child and Parent
781            return 2;
782        }
783
784        @Override
785        public int getItemViewType(int position) {
786            final Folder f = getItem(position);
787            return f.uri.equals(mParentUri) ? PARENT : CHILD;
788        }
789
790        @Override
791        public View getView(int position, View convertView, ViewGroup parent) {
792            final FolderItemView folderItemView;
793            final Folder folder = getItem(position);
794            boolean isParent = folder.uri.equals(mParentUri);
795            if (convertView != null) {
796                folderItemView = (FolderItemView) convertView;
797            } else {
798                int resId = isParent ? R.layout.folder_item : R.layout.child_folder_item;
799                folderItemView = (FolderItemView) LayoutInflater.from(
800                        mActivity.getActivityContext()).inflate(resId, null);
801            }
802            folderItemView.bind(folder, mDropHandler);
803            if (folder.uri.equals(mSelectedFolderUri)) {
804                getListView().setItemChecked(position, true);
805                // If this is the current folder, also check to verify that the unread count
806                // matches what the action bar shows.
807                final boolean unreadCountDiffers = (mCurrentFolderForUnreadCheck != null)
808                        && folder.unreadCount != mCurrentFolderForUnreadCheck.unreadCount;
809                if (unreadCountDiffers) {
810                    folderItemView.overrideUnreadCount(mCurrentFolderForUnreadCheck.unreadCount);
811                }
812            }
813            Folder.setFolderBlockColor(folder, folderItemView.findViewById(R.id.color_block));
814            Folder.setIcon(folder, (ImageView) folderItemView.findViewById(R.id.folder_icon));
815            return folderItemView;
816        }
817
818        @Override
819        public void setCursor(Cursor cursor) {
820            mCursor = cursor;
821            clear();
822            if (mParent != null) {
823                add(mParent);
824            }
825            if (cursor != null && cursor.getCount() > 0) {
826                cursor.moveToFirst();
827                do {
828                    Folder f = new Folder(cursor);
829                    f.parent = mParent;
830                    add(f);
831                } while (cursor.moveToNext());
832            }
833        }
834
835        @Override
836        public void destroy() {
837            // Do nothing.
838        }
839
840        @Override
841        public int getItemType(DrawerItem item) {
842            // Always returns folders for now.
843            return DrawerItem.VIEW_FOLDER;
844        }
845
846        @Override
847        public Folder getFullFolder(DrawerItem folderItem) {
848            int pos = folderItem.mPosition;
849            if (mCursor == null || mCursor.isClosed()) {
850                // See if we have a cursor hanging out we can use
851                mCursor = mFutureData;
852                mFutureData = null;
853            }
854            if (pos > -1 && mCursor != null && !mCursor.isClosed()
855                    && mCursor.moveToPosition(folderItem.mPosition)) {
856                return new Folder(mCursor);
857            } else {
858                return null;
859            }
860        }
861
862        @Override
863        public Account getFullAccount(DrawerItem item) {
864            return null;
865        }
866
867        @Override
868        public boolean toggleShowLessFolders() {
869            return false;
870        }
871
872        @Override
873        public boolean toggleShowLessAccounts() {
874            return false;
875        }
876    }
877
878    /**
879     * Sets the currently selected folder safely.
880     * @param folder
881     */
882    private void setSelectedFolder(Folder folder) {
883        if (folder == null) {
884            mSelectedFolderUri = Uri.EMPTY;
885            LogUtils.e(LOG_TAG, "FolderListFragment.setSelectedFolder(null) called!");
886            return;
887        }
888        // If the current folder changed, we don't have a selected folder type anymore.
889        if (!folder.uri.equals(mSelectedFolderUri)) {
890            mSelectedFolderType = DrawerItem.INERT_HEADER;
891        }
892        mCurrentFolderForUnreadCheck = folder;
893        mSelectedFolderUri = folder.uri;
894        setSelectedFolderType(folder);
895        if (mCursorAdapter != null) {
896            mCursorAdapter.notifyDataSetChanged();
897        }
898    }
899
900    /**
901     * Sets the selected folder type safely.
902     * @param folder folder to set to.
903     */
904    private void setSelectedFolderType(Folder folder) {
905        // If it is set already, assume it is correct.
906        if (mSelectedFolderType != DrawerItem.INERT_HEADER) {
907            return;
908        }
909        mSelectedFolderType = folder.isProviderFolder() ? DrawerItem.FOLDER_SYSTEM
910                : DrawerItem.FOLDER_USER;
911    }
912
913    /**
914     * Sets the current account to the one provided here.
915     * @param account the current account to set to.
916     */
917    private void setSelectedAccount(Account account){
918        mCurrentAccount = account;
919    }
920
921    public interface FolderListSelectionListener {
922        public void onFolderSelected(Folder folder);
923    }
924
925    /**
926     * Get whether the FolderListFragment is currently showing the hierarchy
927     * under a single parent.
928     */
929    public boolean showingHierarchy() {
930        return mParentFolder != null;
931    }
932}
933