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