DirectoryFragment.java revision dc2963aecaf38bf53d6de82957412a486049c207
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui;
18
19import android.app.Fragment;
20import android.app.FragmentManager;
21import android.app.FragmentTransaction;
22import android.app.LoaderManager.LoaderCallbacks;
23import android.content.Context;
24import android.content.CursorLoader;
25import android.content.Loader;
26import android.database.Cursor;
27import android.net.Uri;
28import android.os.Bundle;
29import android.provider.DocumentsContract;
30import android.provider.DocumentsContract.DocumentColumns;
31import android.text.format.DateUtils;
32import android.util.SparseBooleanArray;
33import android.view.ActionMode;
34import android.view.LayoutInflater;
35import android.view.Menu;
36import android.view.MenuInflater;
37import android.view.MenuItem;
38import android.view.View;
39import android.view.ViewGroup;
40import android.widget.AbsListView;
41import android.widget.AbsListView.MultiChoiceModeListener;
42import android.widget.AdapterView;
43import android.widget.AdapterView.OnItemClickListener;
44import android.widget.CursorAdapter;
45import android.widget.GridView;
46import android.widget.ImageView;
47import android.widget.ListView;
48import android.widget.TextView;
49
50import com.android.documentsui.DocumentsActivity.DisplayState;
51import com.android.documentsui.DocumentsActivity.Document;
52import com.google.android.collect.Lists;
53
54import java.util.ArrayList;
55
56/**
57 * Display the documents inside a single directory.
58 */
59public class DirectoryFragment extends Fragment {
60
61    // TODO: show storage backend in item views when requested
62    // TODO: apply sort order locally
63    // TODO: apply MIME filtering locally
64
65    private ListView mListView;
66    private GridView mGridView;
67
68    private AbsListView mCurrentView;
69
70    private static final int TYPE_NORMAL = 1;
71    private static final int TYPE_SEARCH = 2;
72    private static final int TYPE_RECENT_OPEN = 3;
73    private static final int TYPE_RECENT_CREATE = 4;
74
75    private int mType = TYPE_NORMAL;
76
77    private DocumentsAdapter mAdapter;
78    private LoaderCallbacks<Cursor> mCallbacks;
79
80    private static final String EXTRA_URI = "uri";
81
82    private static final int LOADER_DOCUMENTS = 2;
83
84    public static void show(FragmentManager fm, Uri uri) {
85        final Bundle args = new Bundle();
86        args.putParcelable(EXTRA_URI, uri);
87
88        final DirectoryFragment fragment = new DirectoryFragment();
89        fragment.setArguments(args);
90
91        final FragmentTransaction ft = fm.beginTransaction();
92        ft.replace(R.id.directory, fragment);
93        ft.commitAllowingStateLoss();
94    }
95
96    @Override
97    public void onCreate(Bundle savedInstanceState) {
98        super.onCreate(savedInstanceState);
99        setHasOptionsMenu(true);
100    }
101
102    @Override
103    public View onCreateView(
104            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
105        final Context context = inflater.getContext();
106
107        final View view = inflater.inflate(R.layout.fragment_directory, container, false);
108
109        mListView = (ListView) view.findViewById(R.id.list);
110        mListView.setOnItemClickListener(mItemListener);
111        mListView.setMultiChoiceModeListener(mMultiListener);
112
113        mGridView = (GridView) view.findViewById(R.id.grid);
114        mGridView.setOnItemClickListener(mItemListener);
115        mGridView.setMultiChoiceModeListener(mMultiListener);
116
117        mAdapter = new DocumentsAdapter(context);
118        updateMode();
119
120        final Uri uri = getArguments().getParcelable(EXTRA_URI);
121
122        if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
123            mType = TYPE_SEARCH;
124        } else if (RecentsProvider.buildRecentOpen().equals(uri)) {
125            mType = TYPE_RECENT_OPEN;
126        } else if (RecentsProvider.buildRecentCreate().equals(uri)) {
127            mType = TYPE_RECENT_CREATE;
128        } else {
129            mType = TYPE_NORMAL;
130        }
131
132        mCallbacks = new LoaderCallbacks<Cursor>() {
133            @Override
134            public Loader<Cursor> onCreateLoader(int id, Bundle args) {
135                final DisplayState state = getDisplayState(DirectoryFragment.this);
136                final String sortOrder;
137                if (state.sortBy == DisplayState.SORT_BY_NAME) {
138                    sortOrder = DocumentColumns.DISPLAY_NAME + " ASC";
139                } else if (state.sortBy == DisplayState.SORT_BY_DATE) {
140                    sortOrder = DocumentColumns.LAST_MODIFIED + " DESC";
141                } else {
142                    sortOrder = null;
143                }
144
145                final Uri contentsUri;
146                if (mType == TYPE_NORMAL) {
147                    contentsUri = DocumentsContract.buildContentsUri(uri);
148                } else {
149                    contentsUri = uri;
150                }
151
152                return new CursorLoader(context, contentsUri, null, null, null, sortOrder);
153            }
154
155            @Override
156            public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
157                mAdapter.swapCursor(data);
158            }
159
160            @Override
161            public void onLoaderReset(Loader<Cursor> loader) {
162                mAdapter.swapCursor(null);
163            }
164        };
165
166        return view;
167    }
168
169    @Override
170    public void onStart() {
171        super.onStart();
172        getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
173    }
174
175    @Override
176    public void onStop() {
177        super.onStop();
178        getLoaderManager().destroyLoader(LOADER_DOCUMENTS);
179    }
180
181    @Override
182    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
183        super.onCreateOptionsMenu(menu, inflater);
184        inflater.inflate(R.menu.directory, menu);
185    }
186
187    @Override
188    public void onPrepareOptionsMenu(Menu menu) {
189        super.onPrepareOptionsMenu(menu);
190        final DisplayState state = getDisplayState(this);
191        menu.findItem(R.id.menu_grid).setVisible(state.mode != DisplayState.MODE_GRID);
192        menu.findItem(R.id.menu_list).setVisible(state.mode != DisplayState.MODE_LIST);
193    }
194
195    @Override
196    public boolean onOptionsItemSelected(MenuItem item) {
197        final DisplayState state = getDisplayState(this);
198        final int id = item.getItemId();
199        if (id == R.id.menu_grid) {
200            state.mode = DisplayState.MODE_GRID;
201            updateMode();
202            getFragmentManager().invalidateOptionsMenu();
203            return true;
204        } else if (id == R.id.menu_list) {
205            state.mode = DisplayState.MODE_LIST;
206            updateMode();
207            getFragmentManager().invalidateOptionsMenu();
208            return true;
209        } else {
210            return super.onOptionsItemSelected(item);
211        }
212    }
213
214    private void updateMode() {
215        final DisplayState state = getDisplayState(this);
216
217        mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE);
218        mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE);
219
220        final int choiceMode;
221        if (state.allowMultiple) {
222            choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
223        } else {
224            choiceMode = ListView.CHOICE_MODE_NONE;
225        }
226
227        if (state.mode == DisplayState.MODE_GRID) {
228            mListView.setAdapter(null);
229            mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
230            mGridView.setAdapter(mAdapter);
231            mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
232            mGridView.setNumColumns(GridView.AUTO_FIT);
233            mGridView.setChoiceMode(choiceMode);
234            mCurrentView = mGridView;
235        } else if (state.mode == DisplayState.MODE_LIST) {
236            mGridView.setAdapter(null);
237            mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
238            mListView.setAdapter(mAdapter);
239            mListView.setChoiceMode(choiceMode);
240            mCurrentView = mListView;
241        } else {
242            throw new IllegalStateException();
243        }
244    }
245
246    public void updateSortBy() {
247        getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
248    }
249
250    private OnItemClickListener mItemListener = new OnItemClickListener() {
251        @Override
252        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
253            final Cursor cursor = (Cursor) mAdapter.getItem(position);
254            final Uri uri = getArguments().getParcelable(EXTRA_URI);
255            final Document doc = Document.fromCursor(uri, cursor);
256            ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
257        }
258    };
259
260    private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
261        @Override
262        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
263            mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
264            return true;
265        }
266
267        @Override
268        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
269            return true;
270        }
271
272        @Override
273        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
274            if (item.getItemId() == R.id.menu_open) {
275                final Uri uri = getArguments().getParcelable(EXTRA_URI);
276                final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
277                final ArrayList<Document> docs = Lists.newArrayList();
278
279                final int size = checked.size();
280                for (int i = 0; i < size; i++) {
281                    if (checked.valueAt(i)) {
282                        final Cursor cursor = (Cursor) mAdapter.getItem(checked.keyAt(i));
283                        docs.add(Document.fromCursor(uri, cursor));
284                    }
285                }
286
287                ((DocumentsActivity) getActivity()).onDocumentsPicked(docs);
288                return true;
289            } else {
290                return false;
291            }
292        }
293
294        @Override
295        public void onDestroyActionMode(ActionMode mode) {
296            // ignored
297        }
298
299        @Override
300        public void onItemCheckedStateChanged(
301                ActionMode mode, int position, long id, boolean checked) {
302            if (checked) {
303                final Cursor cursor = (Cursor) mAdapter.getItem(position);
304                final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
305
306                // Directories cannot be checked
307                if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
308                    mCurrentView.setItemChecked(position, false);
309                }
310            }
311
312            mode.setTitle(getResources()
313                    .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
314        }
315    };
316
317    private static DisplayState getDisplayState(Fragment fragment) {
318        return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
319    }
320
321    private class DocumentsAdapter extends CursorAdapter {
322        public DocumentsAdapter(Context context) {
323            super(context, null, false);
324        }
325
326        @Override
327        public View newView(Context context, Cursor cursor, ViewGroup parent) {
328            final LayoutInflater inflater = LayoutInflater.from(context);
329            final DisplayState state = getDisplayState(DirectoryFragment.this);
330            if (state.mode == DisplayState.MODE_LIST) {
331                return inflater.inflate(R.layout.item_doc_list, parent, false);
332            } else if (state.mode == DisplayState.MODE_GRID) {
333                return inflater.inflate(R.layout.item_doc_grid, parent, false);
334            } else {
335                throw new IllegalStateException();
336            }
337        }
338
339        @Override
340        public void bindView(View view, Context context, Cursor cursor) {
341            final TextView title = (TextView) view.findViewById(android.R.id.title);
342            final TextView summary = (TextView) view.findViewById(android.R.id.summary);
343            final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
344
345            final String docId = getCursorString(cursor, DocumentColumns.DOC_ID);
346            final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
347            final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
348            final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
349            final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
350
351            final Uri uri = getArguments().getParcelable(EXTRA_URI);
352            if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
353                final Uri childUri = DocumentsContract.buildDocumentUri(uri, docId);
354                icon.setImageURI(childUri);
355            } else {
356                icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(
357                        context, uri.getAuthority(), mimeType));
358            }
359
360            title.setText(displayName);
361            if (summary != null) {
362                summary.setText(DateUtils.getRelativeTimeSpanString(lastModified));
363            }
364        }
365    }
366
367    public static String getCursorString(Cursor cursor, String columnName) {
368        return cursor.getString(cursor.getColumnIndex(columnName));
369    }
370
371    public static long getCursorLong(Cursor cursor, String columnName) {
372        return cursor.getLong(cursor.getColumnIndex(columnName));
373    }
374
375    public static int getCursorInt(Cursor cursor, String columnName) {
376        return cursor.getInt(cursor.getColumnIndex(columnName));
377    }
378}
379