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