FolderListFragment.java revision 9be59911221b236b10c7575e407ac587c9231bc7
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;
37import android.widget.TextView;
38
39import com.android.mail.R;
40import com.android.mail.providers.Folder;
41import com.android.mail.providers.RecentFolderObserver;
42import com.android.mail.providers.UIProvider;
43import com.android.mail.providers.UIProvider.FolderType;
44import com.android.mail.utils.LogTag;
45import com.android.mail.utils.LogUtils;
46import com.android.mail.utils.Utils;
47
48import java.util.ArrayList;
49import java.util.Iterator;
50import java.util.List;
51
52/**
53 * The folder list UI component.
54 */
55public final class FolderListFragment extends ListFragment implements
56        LoaderManager.LoaderCallbacks<Cursor> {
57    private static final String LOG_TAG = LogTag.getLogTag();
58    /** The parent activity */
59    private ControllableActivity mActivity;
60    /** The underlying list view */
61    private ListView mListView;
62    /** URI that points to the list of folders for the current account. */
63    private Uri mFolderListUri;
64    /** True if you want a sectioned FolderList, false otherwise. */
65    private boolean mIsSectioned;
66    /** An {@link ArrayList} of {@link FolderType}s to exclude from displaying. */
67    private ArrayList<Integer> mExcludedFolderTypes;
68    /** Callback into the parent */
69    private FolderListSelectionListener mListener;
70
71    /** The currently selected folder (the folder being viewed).  This is never null. */
72    private Uri mSelectedFolderUri = Uri.EMPTY;
73    /** Parent of the current folder, or null if the current folder is not a child. */
74    private Folder mParentFolder;
75
76    private static final int FOLDER_LOADER_ID = 0;
77    public static final int MODE_DEFAULT = 0;
78    public static final int MODE_PICK = 1;
79    /** Key to store {@link #mParentFolder}. */
80    private static final String ARG_PARENT_FOLDER = "arg-parent-folder";
81    /** Key to store {@link #mFolderListUri}. */
82    private static final String ARG_FOLDER_URI = "arg-folder-list-uri";
83    /** Key to store {@link #mIsSectioned} */
84    private static final String ARG_IS_SECTIONED = "arg-is-sectioned";
85    /** Key to store {@link #mExcludedFolderTypes} */
86    private static final String ARG_EXCLUDED_FOLDER_TYPES = "arg-excluded-folder-types";
87
88    private static final String BUNDLE_LIST_STATE = "flf-list-state";
89    private static final String BUNDLE_SELECTED_FOLDER = "flf-selected-folder";
90    private static final String BUNDLE_SELECTED_TYPE = "flf-selected-type";
91
92    private FolderListFragmentCursorAdapter mCursorAdapter;
93    /** View that we show while we are waiting for the folder list to load */
94    private View mEmptyView;
95    /** Observer to wait for changes to the current folder so we can change the selected folder */
96    private FolderObserver mFolderObserver = null;
97    /**
98     * Type of currently selected folder: {@link FolderListAdapter.Item#FOLDER_SYSTEM},
99     * {@link FolderListAdapter.Item#FOLDER_RECENT} or {@link FolderListAdapter.Item#FOLDER_USER}.
100     */
101    // Setting to NOT_A_FOLDER = leaving uninitialized.
102    private int mSelectedFolderType = FolderListAdapter.Item.NOT_A_FOLDER;
103    private Cursor mFutureData;
104    private ConversationListCallbacks mConversationListCallback;
105
106    /**
107     * Listens to folder changes from the controller and updates state accordingly.
108     */
109    private final class FolderObserver extends DataSetObserver {
110        @Override
111        public void onChanged() {
112            if (mActivity == null) {
113                return;
114            }
115            final FolderController controller = mActivity.getFolderController();
116            if (controller == null) {
117                return;
118            }
119            setSelectedFolder(controller.getFolder());
120        }
121    }
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 TODO(viki):
134     */
135    public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri,
136            boolean isSectioned) {
137        return newInstance(parentFolder, folderUri, isSectioned, null);
138    }
139
140    /**
141     * Creates a new instance of {@link ConversationListFragment}, initialized
142     * to display conversation list context.
143     * @param isSectioned TODO(viki):
144     * @param excludedFolderTypes A list of {@link FolderType}s to exclude from displaying
145     */
146    public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri,
147            boolean isSectioned, final ArrayList<Integer> excludedFolderTypes) {
148        final FolderListFragment fragment = new FolderListFragment();
149        final Bundle args = new Bundle();
150        if (parentFolder != null) {
151            args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
152        }
153        args.putString(ARG_FOLDER_URI, folderUri.toString());
154        args.putBoolean(ARG_IS_SECTIONED, isSectioned);
155        if (excludedFolderTypes != null) {
156            args.putIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES, excludedFolderTypes);
157        }
158        fragment.setArguments(args);
159        return fragment;
160    }
161
162    @Override
163    public void onActivityCreated(Bundle savedState) {
164        super.onActivityCreated(savedState);
165        // Strictly speaking, we get back an android.app.Activity from getActivity. However, the
166        // only activity creating a ConversationListContext is a MailActivity which is of type
167        // ControllableActivity, so this cast should be safe. If this cast fails, some other
168        // activity is creating ConversationListFragments. This activity must be of type
169        // ControllableActivity.
170        final Activity activity = getActivity();
171        if (! (activity instanceof ControllableActivity)){
172            LogUtils.wtf(LOG_TAG, "FolderListFragment expects only a ControllableActivity to" +
173                    "create it. Cannot proceed.");
174        }
175        mActivity = (ControllableActivity) activity;
176        mConversationListCallback = mActivity.getListHandler();
177        final FolderController controller = mActivity.getFolderController();
178        // Listen to folder changes in the future
179        mFolderObserver = new FolderObserver();
180        if (controller != null) {
181            // Only register for selected folder updates if we have a controller.
182            controller.registerFolderObserver(mFolderObserver);
183        }
184
185        mListener = mActivity.getFolderListSelectionListener();
186        if (mActivity.isFinishing()) {
187            // Activity is finishing, just bail.
188            return;
189        }
190
191        final Folder selectedFolder;
192        if (mParentFolder != null) {
193            mCursorAdapter = new HierarchicalFolderListAdapter(null, mParentFolder);
194            selectedFolder = mActivity.getHierarchyFolder();
195        } else {
196            mCursorAdapter = new FolderListAdapter(R.layout.folder_item, mIsSectioned);
197            selectedFolder = controller == null ? null : controller.getFolder();
198        }
199        // Is the selected folder fresher than the one we have restored from a bundle?
200        if (selectedFolder != null && !selectedFolder.uri.equals(mSelectedFolderUri)) {
201            setSelectedFolder(selectedFolder);
202        }
203        setListAdapter(mCursorAdapter);
204        // Set the region which gets highlighted since it might not have been set till now.
205        getLoaderManager().initLoader(FOLDER_LOADER_ID, Bundle.EMPTY, this);
206    }
207
208    @Override
209    public View onCreateView(LayoutInflater inflater, ViewGroup container,
210            Bundle savedState) {
211        final Bundle args = getArguments();
212        mFolderListUri = Uri.parse(args.getString(ARG_FOLDER_URI));
213        mParentFolder = (Folder) args.getParcelable(ARG_PARENT_FOLDER);
214        mIsSectioned = args.getBoolean(ARG_IS_SECTIONED);
215        mExcludedFolderTypes = args.getIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES);
216        final View rootView = inflater.inflate(R.layout.folder_list, null);
217        mListView = (ListView) rootView.findViewById(android.R.id.list);
218        mListView.setHeaderDividersEnabled(false);
219        mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
220        mListView.setEmptyView(null);
221        if (savedState != null && savedState.containsKey(BUNDLE_LIST_STATE)) {
222            mListView.onRestoreInstanceState(savedState.getParcelable(BUNDLE_LIST_STATE));
223        }
224        mEmptyView = rootView.findViewById(R.id.empty_view);
225        if (savedState != null && savedState.containsKey(BUNDLE_SELECTED_FOLDER)) {
226            mSelectedFolderUri = Uri.parse(savedState.getString(BUNDLE_SELECTED_FOLDER));
227            mSelectedFolderType = savedState.getInt(BUNDLE_SELECTED_TYPE);
228        } else if (mParentFolder != null) {
229            mSelectedFolderUri = mParentFolder.uri;
230            // No selected folder type required for hierarchical lists.
231        }
232
233        return rootView;
234    }
235
236    @Override
237    public void onStart() {
238        super.onStart();
239    }
240
241    @Override
242    public void onStop() {
243        super.onStop();
244    }
245
246    @Override
247    public void onPause() {
248        super.onPause();
249    }
250
251    @Override
252    public void onSaveInstanceState(Bundle outState) {
253        super.onSaveInstanceState(outState);
254        if (mListView != null) {
255            outState.putParcelable(BUNDLE_LIST_STATE, mListView.onSaveInstanceState());
256        }
257        if (mSelectedFolderUri != null) {
258            outState.putString(BUNDLE_SELECTED_FOLDER, mSelectedFolderUri.toString());
259        }
260        outState.putInt(BUNDLE_SELECTED_TYPE, mSelectedFolderType);
261    }
262
263    @Override
264    public void onDestroyView() {
265        if (mCursorAdapter != null) {
266            mCursorAdapter.destroy();
267        }
268        // Clear the adapter.
269        setListAdapter(null);
270        if (mFolderObserver != null) {
271            FolderController controller = mActivity.getFolderController();
272            if (controller != null) {
273                controller.unregisterFolderObserver(mFolderObserver);
274                mFolderObserver = null;
275            }
276        }
277        super.onDestroyView();
278    }
279
280    @Override
281    public void onListItemClick(ListView l, View v, int position, long id) {
282        viewFolder(position);
283    }
284
285    /**
286     * Display the conversation list from the folder at the position given.
287     * @param position
288     */
289    private void viewFolder(int position) {
290        final Object item = getListAdapter().getItem(position);
291        final Folder folder;
292        if (item instanceof FolderListAdapter.Item) {
293            final FolderListAdapter.Item folderItem = (FolderListAdapter.Item) item;
294            folder = mCursorAdapter.getFullFolder(folderItem);
295            mSelectedFolderType = folderItem.mFolderType;
296        } else if (item instanceof Folder) {
297            folder = (Folder) item;
298        } else {
299            folder = new Folder((Cursor) item);
300        }
301        if (folder != null) {
302            // Since we may be looking at hierarchical views, if we can
303            // determine the parent of the folder we have tapped, set it here.
304            // If we are looking at the folder we are already viewing, don't
305            // update its parent!
306            folder.parent = folder.equals(mParentFolder) ? null : mParentFolder;
307            // Go to the conversation list for this folder.
308            mListener.onFolderSelected(folder);
309        } else {
310            LogUtils.d(LOG_TAG, "FolderListFragment unable to get a full fledged folder" +
311                    " to hand to the listener for position %d", position);
312        }
313    }
314
315    @Override
316    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
317        mListView.setEmptyView(null);
318        mEmptyView.setVisibility(View.GONE);
319        return new CursorLoader(mActivity.getActivityContext(), mFolderListUri,
320                UIProvider.FOLDERS_PROJECTION, null, null, null);
321    }
322
323    public void onAnimationEnd() {
324        if (mFutureData != null) {
325            updateCursorAdapter(mFutureData);
326            mFutureData = null;
327        }
328    }
329
330    private void updateCursorAdapter(Cursor data) {
331        mCursorAdapter.setCursor(data);
332        if (data == null || data.getCount() == 0) {
333            mEmptyView.setVisibility(View.VISIBLE);
334            mListView.setEmptyView(mEmptyView);
335        }
336    }
337
338    @Override
339    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
340        if (mConversationListCallback == null || !mConversationListCallback.isAnimating()) {
341            updateCursorAdapter(data);
342        } else {
343            mFutureData = data;
344            mCursorAdapter.setCursor(null);
345        }
346    }
347
348    @Override
349    public void onLoaderReset(Loader<Cursor> loader) {
350        mCursorAdapter.setCursor(null);
351    }
352
353    /**
354     * Interface for all cursor adapters that allow setting a cursor and being destroyed.
355     */
356    private interface FolderListFragmentCursorAdapter extends ListAdapter {
357        /** Update the folder list cursor with the cursor given here. */
358        void setCursor(Cursor cursor);
359        /** Get the cursor associated with this adapter **/
360        Folder getFullFolder(FolderListAdapter.Item item);
361        /** Remove all observers and destroy the object. */
362        void destroy();
363        /** Notifies the adapter that the data has changed. */
364        void notifyDataSetChanged();
365    }
366
367    /**
368     * An adapter for flat folder lists.
369     */
370    private class FolderListAdapter extends BaseAdapter implements FolderListFragmentCursorAdapter {
371
372        private final RecentFolderObserver mRecentFolderObserver = new RecentFolderObserver() {
373            @Override
374            public void onChanged() {
375                recalculateList();
376            }
377        };
378
379        private final RecentFolderList mRecentFolders;
380        /** True if the list is sectioned, false otherwise */
381        private final boolean mIsSectioned;
382        private final LayoutInflater mInflater;
383        /** All the items */
384        private final List<Item> mItemList = new ArrayList<Item>();
385        /** Cursor into the folder list. This might be null. */
386        private Cursor mCursor = null;
387
388        /** A union of either a folder or a resource string */
389        private class Item {
390            public int mPosition;
391            public final Folder mFolder;
392            public final int mResource;
393            /** Either {@link #VIEW_FOLDER} or {@link #VIEW_HEADER} */
394            public final int mType;
395            /** A normal folder, also a child, if a parent is specified. */
396            private static final int VIEW_FOLDER = 0;
397            /** A text-label which serves as a header in sectioned lists. */
398            private static final int VIEW_HEADER = 1;
399
400            /**
401             * Either {@link #FOLDER_SYSTEM}, {@link #FOLDER_RECENT} or {@link #FOLDER_USER} when
402             * {@link #mType} is {@link #VIEW_FOLDER}, and {@link #NOT_A_FOLDER} otherwise.
403             */
404            public final int mFolderType;
405            private static final int NOT_A_FOLDER = 0;
406            private static final int FOLDER_SYSTEM = 1;
407            private static final int FOLDER_RECENT = 2;
408            private static final int FOLDER_USER = 3;
409
410            /**
411             * Create a folder item with the given type.
412             * @param folder
413             * @param folderType one of {@link #FOLDER_SYSTEM}, {@link #FOLDER_RECENT} or
414             * {@link #FOLDER_USER}
415             */
416            private Item(Folder folder, int folderType, int cursorPosition) {
417                mFolder = folder;
418                mResource = -1;
419                mType = VIEW_FOLDER;
420                mFolderType = folderType;
421                mPosition = cursorPosition;
422            }
423            /**
424             * Create a header item with a string resource.
425             * @param resource the string resource: R.string.all_folders_heading
426             */
427            private Item(int resource) {
428                mFolder = null;
429                mResource = resource;
430                mType = VIEW_HEADER;
431                mFolderType = NOT_A_FOLDER;
432            }
433
434            private final View getView(int position, View convertView, ViewGroup parent) {
435                if (mType == VIEW_FOLDER) {
436                    return getFolderView(position, convertView, parent);
437                } else {
438                    return getHeaderView(position, convertView, parent);
439                }
440            }
441
442            /**
443             * Returns a text divider between sections.
444             * @param convertView
445             * @param parent
446             * @return a text header at the given position.
447             */
448            private final View getHeaderView(int position, View convertView, ViewGroup parent) {
449                final TextView headerView;
450                if (convertView != null) {
451                    headerView = (TextView) convertView;
452                } else {
453                    headerView = (TextView) mInflater.inflate(
454                            R.layout.folder_list_header, parent, false);
455                }
456                headerView.setText(mResource);
457                return headerView;
458            }
459
460            /**
461             * Return a folder: either a parent folder or a normal (child or flat)
462             * folder.
463             * @param position
464             * @param convertView
465             * @param parent
466             * @return a view showing a folder at the given position.
467             */
468            private final View getFolderView(int position, View convertView, ViewGroup parent) {
469                final FolderItemView folderItemView;
470                if (convertView != null) {
471                    folderItemView = (FolderItemView) convertView;
472                } else {
473                    folderItemView =
474                            (FolderItemView) mInflater.inflate(R.layout.folder_item, null, false);
475                }
476                folderItemView.bind(mFolder, mActivity);
477                if (mListView != null) {
478                    final boolean isSelected = (mFolderType == mSelectedFolderType)
479                            && mFolder.uri.equals(mSelectedFolderUri);
480                    mListView.setItemChecked(position, isSelected);
481                }
482                Folder.setFolderBlockColor(mFolder, folderItemView.findViewById(R.id.color_block));
483                Folder.setIcon(mFolder, (ImageView) folderItemView.findViewById(R.id.folder_box));
484                return folderItemView;
485            }
486        }
487
488        /**
489         * Creates a {@link FolderListAdapter}.This is a flat folder list of all the folders for the
490         * given account.
491         * @param layout
492         * @param isSectioned TODO(viki):
493         */
494        public FolderListAdapter(int layout, boolean isSectioned) {
495            super();
496            mInflater = LayoutInflater.from(mActivity.getActivityContext());
497            mIsSectioned = isSectioned;
498            final RecentFolderController controller = mActivity.getRecentFolderController();
499            if (controller != null && mIsSectioned) {
500                mRecentFolders = mRecentFolderObserver.initialize(controller);
501            } else {
502                mRecentFolders = null;
503            }
504        }
505
506        @Override
507        public View getView(int position, View convertView, ViewGroup parent) {
508            return ((Item) getItem(position)).getView(position, convertView, parent);
509        }
510
511        @Override
512        public int getViewTypeCount() {
513            // Headers and folders
514            return 2;
515        }
516
517        @Override
518        public int getItemViewType(int position) {
519            return ((Item) 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            // We disallow taps on headers
530            return ((Item) getItem(position)).mType != Item.VIEW_HEADER;
531        }
532
533        @Override
534        public boolean areAllItemsEnabled() {
535            // The headers are not enabled.
536            return false;
537        }
538
539        /**
540         * Returns all the recent folders from the list given here. Safe to call with a null list.
541         * @param recentList
542         * @return a valid list of folders, which are all recent folders.
543         */
544        private final List<Folder> getRecentFolders(RecentFolderList recentList) {
545            final List<Folder> folderList = new ArrayList<Folder>();
546            if (recentList == null) {
547                return folderList;
548            }
549            // Get all recent folders, after removing system folders.
550            for (final Folder f : recentList.getRecentFolderList(null)) {
551                if (!f.isProviderFolder()) {
552                    folderList.add(f);
553                }
554            }
555            return folderList;
556        }
557
558        /**
559         * Recalculates the system, recent and user label lists. Notifies that the data has changed.
560         * This method modifies all the three lists on every single invocation.
561         */
562        private void recalculateList() {
563            if (mCursor == null || mCursor.isClosed() || mCursor.getCount() <= 0
564                    || !mCursor.moveToFirst()) {
565                return;
566            }
567            mItemList.clear();
568            if (!mIsSectioned) {
569                // Adapter for a flat list. Everything is a FOLDER_USER, and there are no headers.
570                do {
571                    final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor);
572                    if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) {
573                        mItemList.add(new Item(f, Item.FOLDER_USER, mCursor.getPosition()));
574                    }
575                } while (mCursor.moveToNext());
576                // Ask the list to invalidate its views.
577                notifyDataSetChanged();
578                return;
579            }
580
581            // Otherwise, this is an adapter for a sectioned list.
582            // First add all the system folders.
583            final List<Item> userFolderList = new ArrayList<Item>();
584            do {
585                final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor);
586                if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) {
587                    if (f.isProviderFolder()) {
588                        mItemList.add(new Item(f, Item.FOLDER_SYSTEM, mCursor.getPosition()));
589                    } else {
590                        userFolderList.add(new Item(f, Item.FOLDER_USER, mCursor.getPosition()));
591                    }
592                }
593            } while (mCursor.moveToNext());
594            // If there are recent folders, add them and a header.
595            final List<Folder> recentFolderList = getRecentFolders(mRecentFolders);
596
597            // Remove any excluded folder types
598            if (mExcludedFolderTypes != null) {
599                final Iterator<Folder> iterator = recentFolderList.iterator();
600                while (iterator.hasNext()) {
601                    if (mExcludedFolderTypes.contains(iterator.next().type)) {
602                        iterator.remove();
603                    }
604                }
605            }
606
607            if (recentFolderList.size() > 0) {
608                mItemList.add(new Item(R.string.recent_folders_heading));
609                for (Folder f : recentFolderList) {
610                    mItemList.add(new Item(f, Item.FOLDER_RECENT, -1));
611                }
612            }
613            // If there are user folders, add them and a header.
614            if (userFolderList.size() > 0) {
615                mItemList.add(new Item(R.string.all_folders_heading));
616                for (final Item i : userFolderList) {
617                    mItemList.add(i);
618                }
619            }
620            // Ask the list to invalidate its views.
621            notifyDataSetChanged();
622        }
623
624        @Override
625        public void setCursor(Cursor cursor) {
626            mCursor = cursor;
627            recalculateList();
628        }
629
630        @Override
631        public Object getItem(int position) {
632            return mItemList.get(position);
633        }
634
635        @Override
636        public long getItemId(int position) {
637            return getItem(position).hashCode();
638        }
639
640        @Override
641        public final void destroy() {
642            mRecentFolderObserver.unregisterAndDestroy();
643        }
644
645        @Override
646        public Folder getFullFolder(Item folderItem) {
647            if (folderItem.mFolderType == Item.FOLDER_RECENT) {
648                return folderItem.mFolder;
649            } else {
650                int pos = folderItem.mPosition;
651                if (mFutureData != null) {
652                    mCursor = mFutureData;
653                    mFutureData = null;
654                }
655                if (pos > -1 && mCursor != null && !mCursor.isClosed()
656                        && mCursor.moveToPosition(folderItem.mPosition)) {
657                    return new Folder(mCursor);
658                } else {
659                    return null;
660                }
661            }
662        }
663    }
664
665    private class HierarchicalFolderListAdapter extends ArrayAdapter<Folder>
666            implements FolderListFragmentCursorAdapter{
667
668        private static final int PARENT = 0;
669        private static final int CHILD = 1;
670        private final Uri mParentUri;
671        private final Folder mParent;
672        private final FolderItemView.DropHandler mDropHandler;
673        private Cursor mCursor;
674
675        public HierarchicalFolderListAdapter(Cursor c, Folder parentFolder) {
676            super(mActivity.getActivityContext(), R.layout.folder_item);
677            mDropHandler = mActivity;
678            mParent = parentFolder;
679            mParentUri = parentFolder.uri;
680            setCursor(c);
681        }
682
683        @Override
684        public int getViewTypeCount() {
685            // Child and Parent
686            return 2;
687        }
688
689        @Override
690        public int getItemViewType(int position) {
691            Folder f = getItem(position);
692            return f.uri.equals(mParentUri) ? PARENT : CHILD;
693        }
694
695        @Override
696        public View getView(int position, View convertView, ViewGroup parent) {
697            FolderItemView folderItemView;
698            Folder folder = getItem(position);
699            boolean isParent = folder.uri.equals(mParentUri);
700            if (convertView != null) {
701                folderItemView = (FolderItemView) convertView;
702            } else {
703                int resId = isParent ? R.layout.folder_item : R.layout.child_folder_item;
704                folderItemView = (FolderItemView) LayoutInflater.from(
705                        mActivity.getActivityContext()).inflate(resId, null);
706            }
707            folderItemView.bind(folder, mDropHandler);
708            if (folder.uri.equals(mSelectedFolderUri)) {
709                getListView().setItemChecked(position, true);
710            }
711            Folder.setFolderBlockColor(folder, folderItemView.findViewById(R.id.folder_box));
712            return folderItemView;
713        }
714
715        @Override
716        public void setCursor(Cursor cursor) {
717            mCursor = cursor;
718            clear();
719            if (mParent != null) {
720                add(mParent);
721            }
722            if (cursor != null && cursor.getCount() > 0) {
723                cursor.moveToFirst();
724                do {
725                    Folder f = new Folder(cursor);
726                    f.parent = mParent;
727                    add(f);
728                } while (cursor.moveToNext());
729            }
730        }
731
732        @Override
733        public void destroy() {
734            // Do nothing.
735        }
736
737        @Override
738        public Folder getFullFolder(FolderListAdapter.Item folderItem) {
739            int pos = folderItem.mPosition;
740            if (mCursor == null || mCursor.isClosed()) {
741                // See if we have a cursor hanging out we can use
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    /**
755     * Sets the currently selected folder safely.
756     * @param folder
757     */
758    private void setSelectedFolder(Folder folder) {
759        if (folder == null) {
760            mSelectedFolderUri = Uri.EMPTY;
761            return;
762        }
763        mSelectedFolderUri = folder.uri;
764        setSelectedFolderType(folder);
765        if (mCursorAdapter != null) {
766            mCursorAdapter.notifyDataSetChanged();
767        }
768    }
769
770    /**
771     * Sets the selected folder type safely.
772     * @param folder
773     */
774    private void setSelectedFolderType(Folder folder) {
775        // If it is set already, assume it is correct.
776        if (mSelectedFolderType != FolderListAdapter.Item.NOT_A_FOLDER) {
777            return;
778        }
779        mSelectedFolderType = folder.isProviderFolder() ? FolderListAdapter.Item.FOLDER_SYSTEM
780                : FolderListAdapter.Item.FOLDER_USER;
781    }
782
783    public interface FolderListSelectionListener {
784        public void onFolderSelected(Folder folder);
785    }
786
787    /**
788     * Get whether the FolderListFragment is currently showing the hierarchy
789     * under a single parent.
790     */
791    public boolean showingHierarchy() {
792        return mParentFolder != null;
793    }
794}
795