DirectoryFragment.java revision 54ca29a5b94c2edf461c5433825d4ae17469fd7c
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
194        final Context context = getActivity();
195        getDisplayState(this).showSize = SettingsActivity.getDisplayFileSize(context);
196
197        getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks);
198    }
199
200    @Override
201    public void onStop() {
202        super.onStop();
203        getLoaderManager().destroyLoader(mLoaderId);
204    }
205
206    public void updateDisplayState() {
207        final DisplayState state = getDisplayState(this);
208
209        // TODO: avoid kicking loader when nothing changed
210        getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks);
211        mListView.smoothScrollToPosition(0);
212        mGridView.smoothScrollToPosition(0);
213
214        mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE);
215        mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE);
216
217        final int choiceMode;
218        if (state.allowMultiple) {
219            choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
220        } else {
221            choiceMode = ListView.CHOICE_MODE_NONE;
222        }
223
224        if (state.mode == DisplayState.MODE_GRID) {
225            mListView.setAdapter(null);
226            mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
227            mGridView.setAdapter(mAdapter);
228            mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
229            mGridView.setNumColumns(GridView.AUTO_FIT);
230            mGridView.setChoiceMode(choiceMode);
231            mCurrentView = mGridView;
232        } else if (state.mode == DisplayState.MODE_LIST) {
233            mGridView.setAdapter(null);
234            mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
235            mListView.setAdapter(mAdapter);
236            mListView.setChoiceMode(choiceMode);
237            mCurrentView = mListView;
238        } else {
239            throw new IllegalStateException();
240        }
241    }
242
243    private OnItemClickListener mItemListener = new OnItemClickListener() {
244        @Override
245        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
246            final Document doc = mAdapter.getItem(position);
247            ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
248        }
249    };
250
251    private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
252        @Override
253        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
254            mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
255            return true;
256        }
257
258        @Override
259        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
260            return true;
261        }
262
263        @Override
264        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
265            if (item.getItemId() == R.id.menu_open) {
266                final Uri uri = getArguments().getParcelable(EXTRA_URI);
267                final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
268                final ArrayList<Document> docs = Lists.newArrayList();
269
270                final int size = checked.size();
271                for (int i = 0; i < size; i++) {
272                    if (checked.valueAt(i)) {
273                        final Document doc = mAdapter.getItem(checked.keyAt(i));
274                        docs.add(doc);
275                    }
276                }
277
278                ((DocumentsActivity) getActivity()).onDocumentsPicked(docs);
279                return true;
280            } else {
281                return false;
282            }
283        }
284
285        @Override
286        public void onDestroyActionMode(ActionMode mode) {
287            // ignored
288        }
289
290        @Override
291        public void onItemCheckedStateChanged(
292                ActionMode mode, int position, long id, boolean checked) {
293            if (checked) {
294                // Directories cannot be checked
295                final Document doc = mAdapter.getItem(position);
296                if (doc.isDirectory()) {
297                    mCurrentView.setItemChecked(position, false);
298                }
299            }
300
301            mode.setTitle(getResources()
302                    .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
303        }
304    };
305
306    private static DisplayState getDisplayState(Fragment fragment) {
307        return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
308    }
309
310    private class DocumentsAdapter extends BaseAdapter {
311        private List<Document> mDocuments;
312
313        public DocumentsAdapter() {
314        }
315
316        public void swapDocuments(List<Document> documents) {
317            mDocuments = documents;
318
319            if (documents != null && documents.isEmpty()) {
320                mEmptyView.setVisibility(View.VISIBLE);
321            } else {
322                mEmptyView.setVisibility(View.GONE);
323            }
324
325            notifyDataSetChanged();
326        }
327
328        @Override
329        public View getView(int position, View convertView, ViewGroup parent) {
330            final Context context = parent.getContext();
331            final DisplayState state = getDisplayState(DirectoryFragment.this);
332
333            if (convertView == null) {
334                final LayoutInflater inflater = LayoutInflater.from(context);
335                if (state.mode == DisplayState.MODE_LIST) {
336                    convertView = inflater.inflate(R.layout.item_doc_list, parent, false);
337                } else if (state.mode == DisplayState.MODE_GRID) {
338                    convertView = inflater.inflate(R.layout.item_doc_grid, parent, false);
339                } else {
340                    throw new IllegalStateException();
341                }
342            }
343
344            final Document doc = getItem(position);
345
346            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
347            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
348            final View summaryGrid = convertView.findViewById(R.id.summary_grid);
349            final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1);
350            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
351            final TextView date = (TextView) convertView.findViewById(R.id.date);
352            final TextView size = (TextView) convertView.findViewById(R.id.size);
353
354            if (doc.isThumbnailSupported()) {
355                // TODO: load thumbnails async
356                icon.setImageURI(doc.uri);
357            } else {
358                icon.setImageDrawable(RootsCache.resolveDocumentIcon(
359                        context, doc.uri.getAuthority(), doc.mimeType));
360            }
361
362            title.setText(doc.displayName);
363
364            if (mType == TYPE_NORMAL || mType == TYPE_SEARCH) {
365                icon1.setVisibility(View.GONE);
366                if (doc.summary != null) {
367                    summary.setText(doc.summary);
368                    summary.setVisibility(View.VISIBLE);
369                } else {
370                    summary.setVisibility(View.INVISIBLE);
371                }
372            } else if (mType == TYPE_RECENT_OPEN) {
373                final Root root = RootsCache.findRoot(context, doc);
374                icon1.setVisibility(View.VISIBLE);
375                icon1.setImageDrawable(root.icon);
376                summary.setText(root.getDirectoryString());
377                summary.setVisibility(View.VISIBLE);
378            }
379
380            if (summaryGrid != null) {
381                summaryGrid.setVisibility(
382                        (summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE);
383            }
384
385            // TODO: omit year from format
386            date.setText(DateUtils.formatSameDayTime(
387                    doc.lastModified, System.currentTimeMillis(), DateFormat.SHORT,
388                    DateFormat.SHORT));
389
390            if (state.showSize) {
391                size.setVisibility(View.VISIBLE);
392                if (doc.isDirectory()) {
393                    size.setText(null);
394                } else {
395                    size.setText(Formatter.formatFileSize(context, doc.size));
396                }
397            } else {
398                size.setVisibility(View.GONE);
399            }
400
401            return convertView;
402        }
403
404        @Override
405        public int getCount() {
406            return mDocuments != null ? mDocuments.size() : 0;
407        }
408
409        @Override
410        public Document getItem(int position) {
411            return mDocuments.get(position);
412        }
413
414        @Override
415        public long getItemId(int position) {
416            return getItem(position).uri.hashCode();
417        }
418
419        @Override
420        public boolean areAllItemsEnabled() {
421            return false;
422        }
423
424        @Override
425        public boolean isEnabled(int position) {
426            final Document doc = getItem(position);
427            return mFilter.apply(doc);
428        }
429    }
430}
431