blob: 531eaf3c7863424556043bb73af5abd55bda2398 [file] [log] [blame]
Jeff Sharkey9e0036e2013-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 Sharkey14827892013-07-01 17:22:02 -070019import android.app.AlertDialog;
20import android.app.Dialog;
21import android.app.DialogFragment;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070022import android.app.Fragment;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070023import android.app.FragmentManager;
24import android.app.FragmentTransaction;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070025import android.app.LoaderManager.LoaderCallbacks;
26import android.content.Context;
27import android.content.CursorLoader;
Jeff Sharkey14827892013-07-01 17:22:02 -070028import android.content.DialogInterface;
29import android.content.DialogInterface.OnClickListener;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070030import android.content.Loader;
31import android.database.Cursor;
32import android.net.Uri;
33import android.os.Bundle;
34import android.provider.DocumentsContract;
35import android.provider.DocumentsContract.DocumentColumns;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070036import android.text.format.DateUtils;
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070037import android.util.SparseBooleanArray;
38import android.view.ActionMode;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070039import android.view.LayoutInflater;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070040import android.view.Menu;
41import android.view.MenuInflater;
42import android.view.MenuItem;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070043import android.view.View;
44import android.view.ViewGroup;
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070045import android.widget.AbsListView;
46import android.widget.AbsListView.MultiChoiceModeListener;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070047import android.widget.AdapterView;
48import android.widget.AdapterView.OnItemClickListener;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070049import android.widget.CursorAdapter;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070050import android.widget.GridView;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070051import android.widget.ImageView;
52import android.widget.ListView;
53import android.widget.TextView;
54
Jeff Sharkey14827892013-07-01 17:22:02 -070055import com.android.documentsui.DocumentsActivity.DisplayState;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070056import com.android.documentsui.DocumentsActivity.Document;
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070057import com.google.android.collect.Lists;
58
59import java.util.ArrayList;
Jeff Sharkey54e55b72013-06-30 20:02:59 -070060
61/**
62 * Display the documents inside a single directory.
63 */
64public class DirectoryFragment extends Fragment {
65
66 // TODO: show storage backend in item views when requested
Jeff Sharkey14827892013-07-01 17:22:02 -070067
68 private static final String TAG_SORT = "sort";
Jeff Sharkey54e55b72013-06-30 20:02:59 -070069
70 private ListView mListView;
71 private GridView mGridView;
72
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -070073 private AbsListView mCurrentView;
74
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070075 private DocumentsAdapter mAdapter;
76 private LoaderCallbacks<Cursor> mCallbacks;
77
Jeff Sharkey54e55b72013-06-30 20:02:59 -070078 private int mFlags;
79
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070080 private static final String EXTRA_URI = "uri";
81
82 private static final int LOADER_DOCUMENTS = 2;
83
Jeff Sharkey14827892013-07-01 17:22:02 -070084 public static void show(FragmentManager fm, Uri uri, String displayName) {
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070085 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();
Jeff Sharkey54e55b72013-06-30 20:02:59 -070092 ft.replace(R.id.directory, fragment);
93 ft.addToBackStack(displayName);
94 ft.setBreadCrumbTitle(displayName);
Jeff Sharkey9e0036e2013-04-26 16:54:55 -070095 ft.commitAllowingStateLoss();
96 }
97
98 @Override
Jeff Sharkey54e55b72013-06-30 20:02:59 -070099 public void onCreate(Bundle savedInstanceState) {
100 super.onCreate(savedInstanceState);
101 setHasOptionsMenu(true);
102 }
103
104 @Override
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700105 public View onCreateView(
106 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
107 final Context context = inflater.getContext();
108
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700109 final View view = inflater.inflate(R.layout.fragment_directory, container, false);
110
111 mListView = (ListView) view.findViewById(R.id.list);
112 mListView.setOnItemClickListener(mItemListener);
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700113 mListView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700114
115 mGridView = (GridView) view.findViewById(R.id.grid);
116 mGridView.setOnItemClickListener(mItemListener);
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700117 mGridView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700118
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700119 mAdapter = new DocumentsAdapter(context);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700120 updateMode();
121
122 // TODO: migrate flags query to loader
123 final Uri uri = getArguments().getParcelable(EXTRA_URI);
124 mFlags = getDocumentFlags(context, uri);
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700125
126 mCallbacks = new LoaderCallbacks<Cursor>() {
127 @Override
128 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Jeff Sharkey14827892013-07-01 17:22:02 -0700129 final DisplayState state = getDisplayState(DirectoryFragment.this);
130 final String sortOrder;
131 if (state.sortBy == DisplayState.SORT_BY_NAME) {
132 sortOrder = DocumentColumns.DISPLAY_NAME + " ASC";
133 } else if (state.sortBy == DisplayState.SORT_BY_DATE) {
134 sortOrder = DocumentColumns.LAST_MODIFIED + " DESC";
135 } else {
136 sortOrder = null;
137 }
138
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700139 final Uri contentsUri = DocumentsContract.buildContentsUri(uri);
Jeff Sharkey14827892013-07-01 17:22:02 -0700140 return new CursorLoader(context, contentsUri, null, null, null, sortOrder);
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700141 }
142
143 @Override
144 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
145 mAdapter.swapCursor(data);
146 }
147
148 @Override
149 public void onLoaderReset(Loader<Cursor> loader) {
150 mAdapter.swapCursor(null);
151 }
152 };
153
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700154 return view;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700155 }
156
157 @Override
158 public void onStart() {
159 super.onStart();
160 getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700161
162 // TODO: clean up tracking of current directory
163 final Uri uri = getArguments().getParcelable(EXTRA_URI);
164 ((DocumentsActivity) getActivity()).onDirectoryChanged(uri, mFlags);
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700165 }
166
167 @Override
168 public void onStop() {
169 super.onStop();
170 getLoaderManager().destroyLoader(LOADER_DOCUMENTS);
171 }
172
173 @Override
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700174 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
175 super.onCreateOptionsMenu(menu, inflater);
176 inflater.inflate(R.menu.directory, menu);
177 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700178
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700179 @Override
180 public void onPrepareOptionsMenu(Menu menu) {
181 super.onPrepareOptionsMenu(menu);
Jeff Sharkey14827892013-07-01 17:22:02 -0700182 final DisplayState state = getDisplayState(this);
183 menu.findItem(R.id.menu_grid).setVisible(state.mode != DisplayState.MODE_GRID);
184 menu.findItem(R.id.menu_list).setVisible(state.mode != DisplayState.MODE_LIST);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700185 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700186
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700187 @Override
188 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey14827892013-07-01 17:22:02 -0700189 final DisplayState state = getDisplayState(this);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700190 final int id = item.getItemId();
191 if (id == R.id.menu_grid) {
Jeff Sharkey14827892013-07-01 17:22:02 -0700192 state.mode = DisplayState.MODE_GRID;
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700193 updateMode();
Jeff Sharkey14827892013-07-01 17:22:02 -0700194 getFragmentManager().invalidateOptionsMenu();
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700195 return true;
196 } else if (id == R.id.menu_list) {
Jeff Sharkey14827892013-07-01 17:22:02 -0700197 state.mode = DisplayState.MODE_LIST;
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700198 updateMode();
Jeff Sharkey14827892013-07-01 17:22:02 -0700199 getFragmentManager().invalidateOptionsMenu();
200 return true;
201 } else if (id == R.id.menu_sort) {
202 SortFragment.show(this);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700203 return true;
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700204 } else {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700205 return super.onOptionsItemSelected(item);
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700206 }
207 }
208
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700209 private void updateMode() {
Jeff Sharkey14827892013-07-01 17:22:02 -0700210 final DisplayState state = getDisplayState(this);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700211
Jeff Sharkey14827892013-07-01 17:22:02 -0700212 mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE);
213 mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700214
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700215 final int choiceMode;
Jeff Sharkey14827892013-07-01 17:22:02 -0700216 if (state.allowMultiple) {
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700217 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
218 } else {
219 choiceMode = ListView.CHOICE_MODE_NONE;
220 }
221
Jeff Sharkey14827892013-07-01 17:22:02 -0700222 if (state.mode == DisplayState.MODE_GRID) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700223 mListView.setAdapter(null);
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700224 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700225 mGridView.setAdapter(mAdapter);
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700226 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700227 mGridView.setNumColumns(GridView.AUTO_FIT);
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700228 mGridView.setChoiceMode(choiceMode);
229 mCurrentView = mGridView;
Jeff Sharkey14827892013-07-01 17:22:02 -0700230 } else if (state.mode == DisplayState.MODE_LIST) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700231 mGridView.setAdapter(null);
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700232 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700233 mListView.setAdapter(mAdapter);
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700234 mListView.setChoiceMode(choiceMode);
235 mCurrentView = mListView;
Jeff Sharkey14827892013-07-01 17:22:02 -0700236 } else {
237 throw new IllegalStateException();
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700238 }
239 }
240
Jeff Sharkey14827892013-07-01 17:22:02 -0700241 private void updateSortBy() {
242 getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
243 }
244
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700245 private OnItemClickListener mItemListener = new OnItemClickListener() {
246 @Override
247 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
248 final Cursor cursor = (Cursor) mAdapter.getItem(position);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700249 final Uri uri = getArguments().getParcelable(EXTRA_URI);
250 final Document doc = Document.fromCursor(uri.getAuthority(), cursor);
251 ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
252 }
253 };
254
Jeff Sharkeybe8b12e2013-07-01 16:56:54 -0700255 private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
256 @Override
257 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
258 mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
259 return true;
260 }
261
262 @Override
263 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
264 return true;
265 }
266
267 @Override
268 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
269 if (item.getItemId() == R.id.menu_open) {
270 final Uri uri = getArguments().getParcelable(EXTRA_URI);
271 final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
272 final ArrayList<Document> docs = Lists.newArrayList();
273
274 final int size = checked.size();
275 for (int i = 0; i < size; i++) {
276 if (checked.valueAt(i)) {
277 final Cursor cursor = (Cursor) mAdapter.getItem(checked.keyAt(i));
278 docs.add(Document.fromCursor(uri.getAuthority(), cursor));
279 }
280 }
281
282 ((DocumentsActivity) getActivity()).onDocumentsPicked(docs);
283 return true;
284 } else {
285 return false;
286 }
287 }
288
289 @Override
290 public void onDestroyActionMode(ActionMode mode) {
291 // ignored
292 }
293
294 @Override
295 public void onItemCheckedStateChanged(
296 ActionMode mode, int position, long id, boolean checked) {
297 if (checked) {
298 final Cursor cursor = (Cursor) mAdapter.getItem(position);
299 final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
300
301 // Directories cannot be checked
302 if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
303 mCurrentView.setItemChecked(position, false);
304 }
305 }
306
307 mode.setTitle(getResources()
308 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
309 }
310 };
311
Jeff Sharkey14827892013-07-01 17:22:02 -0700312 private static DisplayState getDisplayState(Fragment fragment) {
313 return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700314 }
315
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700316 private class DocumentsAdapter extends CursorAdapter {
317 public DocumentsAdapter(Context context) {
318 super(context, null, false);
319 }
320
321 @Override
322 public View newView(Context context, Cursor cursor, ViewGroup parent) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700323 final LayoutInflater inflater = LayoutInflater.from(context);
Jeff Sharkey14827892013-07-01 17:22:02 -0700324 final DisplayState state = getDisplayState(DirectoryFragment.this);
325 if (state.mode == DisplayState.MODE_LIST) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700326 return inflater.inflate(R.layout.item_doc_list, parent, false);
Jeff Sharkey14827892013-07-01 17:22:02 -0700327 } else if (state.mode == DisplayState.MODE_GRID) {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700328 return inflater.inflate(R.layout.item_doc_grid, parent, false);
329 } else {
330 throw new IllegalStateException();
331 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700332 }
333
334 @Override
335 public void bindView(View view, Context context, Cursor cursor) {
336 final TextView title = (TextView) view.findViewById(android.R.id.title);
337 final TextView summary = (TextView) view.findViewById(android.R.id.summary);
338 final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
339
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700340 final String guid = getCursorString(cursor, DocumentColumns.GUID);
341 final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
342 final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700343 final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700344 final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
345
346 if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
347 final Uri uri = getArguments().getParcelable(EXTRA_URI);
348 final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid);
349 icon.setImageURI(childUri);
350 } else {
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700351 icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(context, mimeType));
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700352 }
353
354 title.setText(displayName);
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700355 if (summary != null) {
356 summary.setText(DateUtils.getRelativeTimeSpanString(lastModified));
357 }
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700358 }
359 }
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700360
Jeff Sharkey14827892013-07-01 17:22:02 -0700361 public static class SortFragment extends DialogFragment {
362 public static void show(DirectoryFragment parent) {
363 if (!parent.isAdded()) return;
364
365 final SortFragment dialog = new SortFragment();
366 dialog.setTargetFragment(parent, 0);
367 dialog.show(parent.getFragmentManager(), TAG_SORT);
368 }
369
370 @Override
371 public Dialog onCreateDialog(Bundle savedInstanceState) {
372 final Context context = getActivity();
373 final DisplayState state = getDisplayState(this);
374
375 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
376 builder.setTitle(R.string.menu_sort);
377 builder.setSingleChoiceItems(new CharSequence[] {
378 getText(R.string.sort_name),
379 getText(R.string.sort_date),
380 }, state.sortBy, new OnClickListener() {
381 @Override
382 public void onClick(DialogInterface dialog, int which) {
383 state.sortBy = which;
384 ((DirectoryFragment) getTargetFragment()).updateSortBy();
385 dismiss();
386 }
387 });
388
389 return builder.create();
390 }
391 }
392
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700393 private static int getDocumentFlags(Context context, Uri uri) {
394 final Cursor cursor = context.getContentResolver().query(uri, new String[] {
395 DocumentColumns.FLAGS }, null, null, null);
396 try {
397 if (cursor.moveToFirst()) {
398 return getCursorInt(cursor, DocumentColumns.FLAGS);
399 } else {
400 return 0;
401 }
402 } finally {
403 cursor.close();
404 }
405 }
406
407 public static String getCursorString(Cursor cursor, String columnName) {
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700408 return cursor.getString(cursor.getColumnIndex(columnName));
409 }
410
Jeff Sharkey54e55b72013-06-30 20:02:59 -0700411 public static long getCursorLong(Cursor cursor, String columnName) {
412 return cursor.getLong(cursor.getColumnIndex(columnName));
413 }
414
415 public static int getCursorInt(Cursor cursor, String columnName) {
Jeff Sharkey9e0036e2013-04-26 16:54:55 -0700416 return cursor.getInt(cursor.getColumnIndex(columnName));
417 }
418}