blob: f6f3f9c208b1b5d34c4d5aaa2a8aa56709d5c018 [file] [log] [blame]
Jeff Sharkeye22d02e2013-04-26 16:54:55 -07001/*
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
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070019import android.app.Fragment;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070020import android.app.FragmentManager;
21import android.app.FragmentTransaction;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070022import android.app.LoaderManager.LoaderCallbacks;
23import android.content.Context;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070024import android.content.Loader;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070025import android.net.Uri;
26import android.os.Bundle;
27import android.provider.DocumentsContract;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070028import android.text.format.DateUtils;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070029import android.util.SparseBooleanArray;
30import android.view.ActionMode;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070031import android.view.LayoutInflater;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070032import android.view.Menu;
33import android.view.MenuInflater;
34import android.view.MenuItem;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070035import android.view.View;
36import android.view.ViewGroup;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070037import android.widget.AbsListView;
38import android.widget.AbsListView.MultiChoiceModeListener;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070039import android.widget.AdapterView;
40import android.widget.AdapterView.OnItemClickListener;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070041import android.widget.BaseAdapter;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070042import android.widget.GridView;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070043import android.widget.ImageView;
44import android.widget.ListView;
45import android.widget.TextView;
46
Jeff Sharkey3c28b792013-07-01 17:22:02 -070047import com.android.documentsui.DocumentsActivity.DisplayState;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070048import com.android.documentsui.model.Document;
49import com.android.internal.util.Predicate;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070050import com.google.android.collect.Lists;
51
52import java.util.ArrayList;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070053import java.util.Comparator;
54import java.util.List;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070055
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
Jeff Sharkey3c28b792013-07-01 17:22:02 -070062
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070063 private ListView mListView;
64 private GridView mGridView;
65
Jeff Sharkeyc317af82013-07-01 16:56:54 -070066 private AbsListView mCurrentView;
67
Jeff Sharkeya5defe32013-08-05 17:56:48 -070068 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;
Jeff Sharkey5b535922013-08-02 15:55:26 -070072
73 private int mType = TYPE_NORMAL;
74
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070075 private DocumentsAdapter mAdapter;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070076 private LoaderCallbacks<List<Document>> mCallbacks;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070077
Jeff Sharkey5b535922013-08-02 15:55:26 -070078 private static final String EXTRA_URI = "uri";
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070079
80 private static final int LOADER_DOCUMENTS = 2;
81
Jeff Sharkey5b535922013-08-02 15:55:26 -070082 public static void show(FragmentManager fm, Uri uri) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070083 final Bundle args = new Bundle();
Jeff Sharkey5b535922013-08-02 15:55:26 -070084 args.putParcelable(EXTRA_URI, uri);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070085
86 final DirectoryFragment fragment = new DirectoryFragment();
87 fragment.setArguments(args);
88
89 final FragmentTransaction ft = fm.beginTransaction();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070090 ft.replace(R.id.directory, fragment);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070091 ft.commitAllowingStateLoss();
92 }
93
Jeff Sharkeya5defe32013-08-05 17:56:48 -070094 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
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070099 @Override
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700100 public void onCreate(Bundle savedInstanceState) {
101 super.onCreate(savedInstanceState);
102 setHasOptionsMenu(true);
103 }
104
105 @Override
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700106 public View onCreateView(
107 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
108 final Context context = inflater.getContext();
109
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700110 final View view = inflater.inflate(R.layout.fragment_directory, container, false);
111
112 mListView = (ListView) view.findViewById(R.id.list);
113 mListView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700114 mListView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700115
116 mGridView = (GridView) view.findViewById(R.id.grid);
117 mGridView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700118 mGridView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700119
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700120 mAdapter = new DocumentsAdapter();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700121 updateMode();
122
Jeff Sharkey5b535922013-08-02 15:55:26 -0700123 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 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700134
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700135 mCallbacks = new LoaderCallbacks<List<Document>>() {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700136 @Override
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700137 public Loader<List<Document>> onCreateLoader(int id, Bundle args) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700138 final DisplayState state = getDisplayState(DirectoryFragment.this);
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700139
Jeff Sharkey46165b52013-07-31 20:53:22 -0700140 final Uri contentsUri;
Jeff Sharkey5b535922013-08-02 15:55:26 -0700141 if (mType == TYPE_NORMAL) {
142 contentsUri = DocumentsContract.buildContentsUri(uri);
Jeff Sharkey46165b52013-07-31 20:53:22 -0700143 } else {
Jeff Sharkey5b535922013-08-02 15:55:26 -0700144 contentsUri = uri;
Jeff Sharkey46165b52013-07-31 20:53:22 -0700145 }
146
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700147 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);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700160 }
161
162 @Override
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700163 public void onLoadFinished(Loader<List<Document>> loader, List<Document> data) {
164 mAdapter.swapDocuments(data);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700165 }
166
167 @Override
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700168 public void onLoaderReset(Loader<List<Document>> loader) {
169 mAdapter.swapDocuments(null);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700170 }
171 };
172
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700173 return view;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700174 }
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
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700189 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
190 super.onCreateOptionsMenu(menu, inflater);
191 inflater.inflate(R.menu.directory, menu);
192 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700193
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700194 @Override
195 public void onPrepareOptionsMenu(Menu menu) {
196 super.onPrepareOptionsMenu(menu);
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700197 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);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700200 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700201
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700202 @Override
203 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700204 final DisplayState state = getDisplayState(this);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700205 final int id = item.getItemId();
206 if (id == R.id.menu_grid) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700207 state.mode = DisplayState.MODE_GRID;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700208 updateMode();
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700209 getFragmentManager().invalidateOptionsMenu();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700210 return true;
211 } else if (id == R.id.menu_list) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700212 state.mode = DisplayState.MODE_LIST;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700213 updateMode();
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700214 getFragmentManager().invalidateOptionsMenu();
215 return true;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700216 } else {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700217 return super.onOptionsItemSelected(item);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700218 }
219 }
220
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700221 private void updateMode() {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700222 final DisplayState state = getDisplayState(this);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700223
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700224 mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE);
225 mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700226
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700227 final int choiceMode;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700228 if (state.allowMultiple) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700229 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
230 } else {
231 choiceMode = ListView.CHOICE_MODE_NONE;
232 }
233
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700234 if (state.mode == DisplayState.MODE_GRID) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700235 mListView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700236 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700237 mGridView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700238 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700239 mGridView.setNumColumns(GridView.AUTO_FIT);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700240 mGridView.setChoiceMode(choiceMode);
241 mCurrentView = mGridView;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700242 } else if (state.mode == DisplayState.MODE_LIST) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700243 mGridView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700244 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700245 mListView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700246 mListView.setChoiceMode(choiceMode);
247 mCurrentView = mListView;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700248 } else {
249 throw new IllegalStateException();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700250 }
251 }
252
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700253 public void updateSortOrder() {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700254 getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700255 mListView.smoothScrollToPosition(0);
256 mGridView.smoothScrollToPosition(0);
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700257 }
258
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700259 private OnItemClickListener mItemListener = new OnItemClickListener() {
260 @Override
261 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700262 final Document doc = mAdapter.getItem(position);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700263 ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
264 }
265 };
266
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700267 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) {
Jeff Sharkey5b535922013-08-02 15:55:26 -0700282 final Uri uri = getArguments().getParcelable(EXTRA_URI);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700283 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)) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700289 final Document doc = mAdapter.getItem(checked.keyAt(i));
290 docs.add(doc);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700291 }
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) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700310 // Directories cannot be checked
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700311 final Document doc = mAdapter.getItem(position);
312 if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700313 mCurrentView.setItemChecked(position, false);
314 }
315 }
316
317 mode.setTitle(getResources()
318 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
319 }
320 };
321
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700322 private static DisplayState getDisplayState(Fragment fragment) {
323 return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700324 }
325
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700326 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();
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700335 }
336
337 @Override
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700338 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 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700351 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700352
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700353 final Document doc = getItem(position);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700354
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700355 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);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700358
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700359 if (doc.isThumbnailSupported()) {
360 // TODO: load thumbnails async
361 icon.setImageURI(doc.uri);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700362 } else {
Jeff Sharkey5b535922013-08-02 15:55:26 -0700363 icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700364 context, doc.uri.getAuthority(), doc.mimeType));
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700365 }
366
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700367 title.setText(doc.displayName);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700368 if (summary != null) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700369 summary.setText(DateUtils.getRelativeTimeSpanString(doc.lastModified));
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700370 }
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700371
372 return convertView;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700373 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700374
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700375 @Override
376 public int getCount() {
377 return mDocuments != null ? mDocuments.size() : 0;
378 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700379
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700380 @Override
381 public Document getItem(int position) {
382 return mDocuments.get(position);
383 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700384
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700385 @Override
386 public long getItemId(int position) {
387 return getItem(position).uri.hashCode();
388 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700389 }
390}