DirectoryFragment.java revision ef7184a1aa0be5d496a5cb495a0f9e11f342af44
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.Loader;
25import android.net.Uri;
26import android.os.Bundle;
27import android.provider.DocumentsContract;
28import android.text.format.DateUtils;
29import android.util.SparseBooleanArray;
30import android.view.ActionMode;
31import android.view.LayoutInflater;
32import android.view.Menu;
33import android.view.MenuInflater;
34import android.view.MenuItem;
35import android.view.View;
36import android.view.ViewGroup;
37import android.widget.AbsListView;
38import android.widget.AbsListView.MultiChoiceModeListener;
39import android.widget.AdapterView;
40import android.widget.AdapterView.OnItemClickListener;
41import android.widget.BaseAdapter;
42import android.widget.GridView;
43import android.widget.ImageView;
44import android.widget.ListView;
45import android.widget.TextView;
46
47import com.android.documentsui.DocumentsActivity.DisplayState;
48import com.android.documentsui.model.Document;
49import com.android.internal.util.Predicate;
50import com.google.android.collect.Lists;
51
52import java.util.ArrayList;
53import java.util.Comparator;
54import java.util.List;
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
63    private ListView mListView;
64    private GridView mGridView;
65
66    private AbsListView mCurrentView;
67
68    public static final int TYPE_NORMAL = 1;
69    public static final int TYPE_SEARCH = 2;
70    public static final int TYPE_RECENT_OPEN = 3;
71    public static final int TYPE_RECENT_CREATE = 4;
72
73    private int mType = TYPE_NORMAL;
74
75    private DocumentsAdapter mAdapter;
76    private LoaderCallbacks<List<Document>> mCallbacks;
77
78    private static final String EXTRA_URI = "uri";
79
80    private static final int LOADER_DOCUMENTS = 2;
81
82    public static void show(FragmentManager fm, Uri uri) {
83        final Bundle args = new Bundle();
84        args.putParcelable(EXTRA_URI, uri);
85
86        final DirectoryFragment fragment = new DirectoryFragment();
87        fragment.setArguments(args);
88
89        final FragmentTransaction ft = fm.beginTransaction();
90        ft.replace(R.id.directory, fragment);
91        ft.commitAllowingStateLoss();
92    }
93
94    public static DirectoryFragment get(FragmentManager fm) {
95        // TODO: deal with multiple directories shown at once
96        return (DirectoryFragment) fm.findFragmentById(R.id.directory);
97    }
98
99    @Override
100    public void onCreate(Bundle savedInstanceState) {
101        super.onCreate(savedInstanceState);
102        setHasOptionsMenu(true);
103    }
104
105    @Override
106    public View onCreateView(
107            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
108        final Context context = inflater.getContext();
109
110        final View view = inflater.inflate(R.layout.fragment_directory, container, false);
111
112        mListView = (ListView) view.findViewById(R.id.list);
113        mListView.setOnItemClickListener(mItemListener);
114        mListView.setMultiChoiceModeListener(mMultiListener);
115
116        mGridView = (GridView) view.findViewById(R.id.grid);
117        mGridView.setOnItemClickListener(mItemListener);
118        mGridView.setMultiChoiceModeListener(mMultiListener);
119
120        mAdapter = new DocumentsAdapter();
121        updateMode();
122
123        final Uri uri = getArguments().getParcelable(EXTRA_URI);
124
125        if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
126            mType = TYPE_SEARCH;
127        } else if (RecentsProvider.buildRecentOpen().equals(uri)) {
128            mType = TYPE_RECENT_OPEN;
129        } else if (RecentsProvider.buildRecentCreate().equals(uri)) {
130            mType = TYPE_RECENT_CREATE;
131        } else {
132            mType = TYPE_NORMAL;
133        }
134
135        mCallbacks = new LoaderCallbacks<List<Document>>() {
136            @Override
137            public Loader<List<Document>> onCreateLoader(int id, Bundle args) {
138                final DisplayState state = getDisplayState(DirectoryFragment.this);
139
140                final Uri contentsUri;
141                if (mType == TYPE_NORMAL) {
142                    contentsUri = DocumentsContract.buildContentsUri(uri);
143                } else {
144                    contentsUri = uri;
145                }
146
147                final Predicate<Document> filter = new MimePredicate(state.acceptMimes);
148
149                final Comparator<Document> sortOrder;
150                if (state.sortOrder == DisplayState.SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN
151                        || mType == TYPE_RECENT_CREATE) {
152                    sortOrder = new Document.DateComparator();
153                } else if (state.sortOrder == DisplayState.SORT_ORDER_NAME) {
154                    sortOrder = new Document.NameComparator();
155                } else {
156                    throw new IllegalArgumentException("Unknown sort order " + state.sortOrder);
157                }
158
159                return new DirectoryLoader(context, contentsUri, mType, filter, sortOrder);
160            }
161
162            @Override
163            public void onLoadFinished(Loader<List<Document>> loader, List<Document> data) {
164                mAdapter.swapDocuments(data);
165            }
166
167            @Override
168            public void onLoaderReset(Loader<List<Document>> loader) {
169                mAdapter.swapDocuments(null);
170            }
171        };
172
173        return view;
174    }
175
176    @Override
177    public void onStart() {
178        super.onStart();
179        getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
180    }
181
182    @Override
183    public void onStop() {
184        super.onStop();
185        getLoaderManager().destroyLoader(LOADER_DOCUMENTS);
186    }
187
188    @Override
189    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
190        super.onCreateOptionsMenu(menu, inflater);
191        inflater.inflate(R.menu.directory, menu);
192    }
193
194    @Override
195    public void onPrepareOptionsMenu(Menu menu) {
196        super.onPrepareOptionsMenu(menu);
197        final DisplayState state = getDisplayState(this);
198        menu.findItem(R.id.menu_grid).setVisible(state.mode != DisplayState.MODE_GRID);
199        menu.findItem(R.id.menu_list).setVisible(state.mode != DisplayState.MODE_LIST);
200    }
201
202    @Override
203    public boolean onOptionsItemSelected(MenuItem item) {
204        final DisplayState state = getDisplayState(this);
205        final int id = item.getItemId();
206        if (id == R.id.menu_grid) {
207            state.mode = DisplayState.MODE_GRID;
208            updateMode();
209            getFragmentManager().invalidateOptionsMenu();
210            return true;
211        } else if (id == R.id.menu_list) {
212            state.mode = DisplayState.MODE_LIST;
213            updateMode();
214            getFragmentManager().invalidateOptionsMenu();
215            return true;
216        } else {
217            return super.onOptionsItemSelected(item);
218        }
219    }
220
221    private void updateMode() {
222        final DisplayState state = getDisplayState(this);
223
224        mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE);
225        mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE);
226
227        final int choiceMode;
228        if (state.allowMultiple) {
229            choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
230        } else {
231            choiceMode = ListView.CHOICE_MODE_NONE;
232        }
233
234        if (state.mode == DisplayState.MODE_GRID) {
235            mListView.setAdapter(null);
236            mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
237            mGridView.setAdapter(mAdapter);
238            mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
239            mGridView.setNumColumns(GridView.AUTO_FIT);
240            mGridView.setChoiceMode(choiceMode);
241            mCurrentView = mGridView;
242        } else if (state.mode == DisplayState.MODE_LIST) {
243            mGridView.setAdapter(null);
244            mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
245            mListView.setAdapter(mAdapter);
246            mListView.setChoiceMode(choiceMode);
247            mCurrentView = mListView;
248        } else {
249            throw new IllegalStateException();
250        }
251    }
252
253    public void updateSortOrder() {
254        getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
255        mListView.smoothScrollToPosition(0);
256        mGridView.smoothScrollToPosition(0);
257    }
258
259    private OnItemClickListener mItemListener = new OnItemClickListener() {
260        @Override
261        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
262            final Document doc = mAdapter.getItem(position);
263            ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
264        }
265    };
266
267    private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
268        @Override
269        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
270            mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
271            return true;
272        }
273
274        @Override
275        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
276            return true;
277        }
278
279        @Override
280        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
281            if (item.getItemId() == R.id.menu_open) {
282                final Uri uri = getArguments().getParcelable(EXTRA_URI);
283                final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
284                final ArrayList<Document> docs = Lists.newArrayList();
285
286                final int size = checked.size();
287                for (int i = 0; i < size; i++) {
288                    if (checked.valueAt(i)) {
289                        final Document doc = mAdapter.getItem(checked.keyAt(i));
290                        docs.add(doc);
291                    }
292                }
293
294                ((DocumentsActivity) getActivity()).onDocumentsPicked(docs);
295                return true;
296            } else {
297                return false;
298            }
299        }
300
301        @Override
302        public void onDestroyActionMode(ActionMode mode) {
303            // ignored
304        }
305
306        @Override
307        public void onItemCheckedStateChanged(
308                ActionMode mode, int position, long id, boolean checked) {
309            if (checked) {
310                // Directories cannot be checked
311                final Document doc = mAdapter.getItem(position);
312                if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
313                    mCurrentView.setItemChecked(position, false);
314                }
315            }
316
317            mode.setTitle(getResources()
318                    .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
319        }
320    };
321
322    private static DisplayState getDisplayState(Fragment fragment) {
323        return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
324    }
325
326    private class DocumentsAdapter extends BaseAdapter {
327        private List<Document> mDocuments;
328
329        public DocumentsAdapter() {
330        }
331
332        public void swapDocuments(List<Document> documents) {
333            mDocuments = documents;
334            notifyDataSetChanged();
335        }
336
337        @Override
338        public View getView(int position, View convertView, ViewGroup parent) {
339            final Context context = parent.getContext();
340
341            if (convertView == null) {
342                final LayoutInflater inflater = LayoutInflater.from(context);
343                final DisplayState state = getDisplayState(DirectoryFragment.this);
344                if (state.mode == DisplayState.MODE_LIST) {
345                    convertView = inflater.inflate(R.layout.item_doc_list, parent, false);
346                } else if (state.mode == DisplayState.MODE_GRID) {
347                    convertView = inflater.inflate(R.layout.item_doc_grid, parent, false);
348                } else {
349                    throw new IllegalStateException();
350                }
351            }
352
353            final Document doc = getItem(position);
354
355            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
356            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
357            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
358
359            if (doc.isThumbnailSupported()) {
360                // TODO: load thumbnails async
361                icon.setImageURI(doc.uri);
362            } else {
363                icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(
364                        context, doc.uri.getAuthority(), doc.mimeType));
365            }
366
367            title.setText(doc.displayName);
368            if (summary != null) {
369                summary.setText(DateUtils.getRelativeTimeSpanString(doc.lastModified));
370            }
371
372            return convertView;
373        }
374
375        @Override
376        public int getCount() {
377            return mDocuments != null ? mDocuments.size() : 0;
378        }
379
380        @Override
381        public Document getItem(int position) {
382            return mDocuments.get(position);
383        }
384
385        @Override
386        public long getItemId(int position) {
387            return getItem(position).uri.hashCode();
388        }
389    }
390}
391