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