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