blob: bae42d5cbd2e820124335703e94cf2ccb9bbcc4d [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;
24import android.content.CursorLoader;
25import android.content.Loader;
26import android.database.Cursor;
27import android.net.Uri;
28import android.os.Bundle;
29import android.provider.DocumentsContract;
30import android.provider.DocumentsContract.DocumentColumns;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070031import android.text.format.DateUtils;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070032import android.util.SparseBooleanArray;
33import android.view.ActionMode;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070034import android.view.LayoutInflater;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070035import android.view.Menu;
36import android.view.MenuInflater;
37import android.view.MenuItem;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070038import android.view.View;
39import android.view.ViewGroup;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070040import android.widget.AbsListView;
41import android.widget.AbsListView.MultiChoiceModeListener;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070042import android.widget.AdapterView;
43import android.widget.AdapterView.OnItemClickListener;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070044import android.widget.CursorAdapter;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070045import android.widget.GridView;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070046import android.widget.ImageView;
47import android.widget.ListView;
48import android.widget.TextView;
49
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070050import com.android.documentsui.DocumentsActivity.Document;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070051import com.google.android.collect.Lists;
52
53import java.util.ArrayList;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070054
55/**
56 * Display the documents inside a single directory.
57 */
58public class DirectoryFragment extends Fragment {
59
60 // TODO: show storage backend in item views when requested
61 // TODO: implement sorting dialog
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070062
63 private ListView mListView;
64 private GridView mGridView;
65
Jeff Sharkeyc317af82013-07-01 16:56:54 -070066 private AbsListView mCurrentView;
67
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070068 private DocumentsAdapter mAdapter;
69 private LoaderCallbacks<Cursor> mCallbacks;
70
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070071 private int mFlags;
72
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070073 private static final String EXTRA_URI = "uri";
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070074 private static final String EXTRA_MODE = "display_mode";
Jeff Sharkeyc317af82013-07-01 16:56:54 -070075 private static final String EXTRA_ALLOW_MULTIPLE = "allow_multiple";
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070076
77 private static final int MODE_LIST = 1;
78 private static final int MODE_GRID = 2;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070079
80 private static final int LOADER_DOCUMENTS = 2;
81
Jeff Sharkeyc317af82013-07-01 16:56:54 -070082 public static void show(
83 FragmentManager fm, Uri uri, String displayName, boolean allowMultiple) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070084 final Bundle args = new Bundle();
85 args.putParcelable(EXTRA_URI, uri);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070086 args.putInt(EXTRA_MODE, MODE_LIST);
Jeff Sharkeyc317af82013-07-01 16:56:54 -070087 args.putBoolean(EXTRA_ALLOW_MULTIPLE, allowMultiple);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070088
89 final DirectoryFragment fragment = new DirectoryFragment();
90 fragment.setArguments(args);
91
92 final FragmentTransaction ft = fm.beginTransaction();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070093 ft.replace(R.id.directory, fragment);
94 ft.addToBackStack(displayName);
95 ft.setBreadCrumbTitle(displayName);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070096 ft.commitAllowingStateLoss();
97 }
98
99 @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 Sharkeye22d02e2013-04-26 16:54:55 -0700120 mAdapter = new DocumentsAdapter(context);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700121 updateMode();
122
123 // TODO: migrate flags query to loader
124 final Uri uri = getArguments().getParcelable(EXTRA_URI);
125 mFlags = getDocumentFlags(context, uri);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700126
127 mCallbacks = new LoaderCallbacks<Cursor>() {
128 @Override
129 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700130 final Uri contentsUri = DocumentsContract.buildContentsUri(uri);
131 return new CursorLoader(context, contentsUri, null, null, null, null);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700132 }
133
134 @Override
135 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
136 mAdapter.swapCursor(data);
137 }
138
139 @Override
140 public void onLoaderReset(Loader<Cursor> loader) {
141 mAdapter.swapCursor(null);
142 }
143 };
144
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700145 return view;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700146 }
147
148 @Override
149 public void onStart() {
150 super.onStart();
151 getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700152
153 // TODO: clean up tracking of current directory
154 final Uri uri = getArguments().getParcelable(EXTRA_URI);
155 ((DocumentsActivity) getActivity()).onDirectoryChanged(uri, mFlags);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700156 }
157
158 @Override
159 public void onStop() {
160 super.onStop();
161 getLoaderManager().destroyLoader(LOADER_DOCUMENTS);
162 }
163
164 @Override
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700165 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
166 super.onCreateOptionsMenu(menu, inflater);
167 inflater.inflate(R.menu.directory, menu);
168 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700169
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700170 @Override
171 public void onPrepareOptionsMenu(Menu menu) {
172 super.onPrepareOptionsMenu(menu);
173 final int mode = getMode();
174 menu.findItem(R.id.menu_grid).setVisible(mode != MODE_GRID);
175 menu.findItem(R.id.menu_list).setVisible(mode != MODE_LIST);
176 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700177
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700178 @Override
179 public boolean onOptionsItemSelected(MenuItem item) {
180 final int id = item.getItemId();
181 if (id == R.id.menu_grid) {
182 getArguments().putInt(EXTRA_MODE, MODE_GRID);
183 updateMode();
184 return true;
185 } else if (id == R.id.menu_list) {
186 getArguments().putInt(EXTRA_MODE, MODE_LIST);
187 updateMode();
188 return true;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700189 } else {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700190 return super.onOptionsItemSelected(item);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700191 }
192 }
193
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700194 private void updateMode() {
195 final int mode = getMode();
196
197 mListView.setVisibility(mode == MODE_LIST ? View.VISIBLE : View.GONE);
198 mGridView.setVisibility(mode == MODE_GRID ? View.VISIBLE : View.GONE);
199
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700200 final int choiceMode;
201 if (getArguments().getBoolean(EXTRA_ALLOW_MULTIPLE)) {
202 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
203 } else {
204 choiceMode = ListView.CHOICE_MODE_NONE;
205 }
206
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700207 if (mode == MODE_GRID) {
208 mListView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700209 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700210 mGridView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700211 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700212 mGridView.setNumColumns(GridView.AUTO_FIT);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700213 mGridView.setChoiceMode(choiceMode);
214 mCurrentView = mGridView;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700215 } else {
216 mGridView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700217 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700218 mListView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700219 mListView.setChoiceMode(choiceMode);
220 mCurrentView = mListView;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700221 }
222 }
223
224 private OnItemClickListener mItemListener = new OnItemClickListener() {
225 @Override
226 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
227 final Cursor cursor = (Cursor) mAdapter.getItem(position);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700228 final Uri uri = getArguments().getParcelable(EXTRA_URI);
229 final Document doc = Document.fromCursor(uri.getAuthority(), cursor);
230 ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
231 }
232 };
233
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700234 private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
235 @Override
236 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
237 mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
238 return true;
239 }
240
241 @Override
242 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
243 return true;
244 }
245
246 @Override
247 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
248 if (item.getItemId() == R.id.menu_open) {
249 final Uri uri = getArguments().getParcelable(EXTRA_URI);
250 final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
251 final ArrayList<Document> docs = Lists.newArrayList();
252
253 final int size = checked.size();
254 for (int i = 0; i < size; i++) {
255 if (checked.valueAt(i)) {
256 final Cursor cursor = (Cursor) mAdapter.getItem(checked.keyAt(i));
257 docs.add(Document.fromCursor(uri.getAuthority(), cursor));
258 }
259 }
260
261 ((DocumentsActivity) getActivity()).onDocumentsPicked(docs);
262 return true;
263 } else {
264 return false;
265 }
266 }
267
268 @Override
269 public void onDestroyActionMode(ActionMode mode) {
270 // ignored
271 }
272
273 @Override
274 public void onItemCheckedStateChanged(
275 ActionMode mode, int position, long id, boolean checked) {
276 if (checked) {
277 final Cursor cursor = (Cursor) mAdapter.getItem(position);
278 final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
279
280 // Directories cannot be checked
281 if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
282 mCurrentView.setItemChecked(position, false);
283 }
284 }
285
286 mode.setTitle(getResources()
287 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
288 }
289 };
290
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700291 private boolean getSupportsCreate() {
292 return (mFlags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
293 }
294
295 private int getMode() {
296 return getArguments().getInt(EXTRA_MODE, MODE_LIST);
297 }
298
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700299 private class DocumentsAdapter extends CursorAdapter {
300 public DocumentsAdapter(Context context) {
301 super(context, null, false);
302 }
303
304 @Override
305 public View newView(Context context, Cursor cursor, ViewGroup parent) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700306 final LayoutInflater inflater = LayoutInflater.from(context);
307 final int mode = getMode();
308 if (mode == MODE_LIST) {
309 return inflater.inflate(R.layout.item_doc_list, parent, false);
310 } else if (mode == MODE_GRID) {
311 return inflater.inflate(R.layout.item_doc_grid, parent, false);
312 } else {
313 throw new IllegalStateException();
314 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700315 }
316
317 @Override
318 public void bindView(View view, Context context, Cursor cursor) {
319 final TextView title = (TextView) view.findViewById(android.R.id.title);
320 final TextView summary = (TextView) view.findViewById(android.R.id.summary);
321 final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
322
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700323 final String guid = getCursorString(cursor, DocumentColumns.GUID);
324 final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
325 final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700326 final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700327 final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
328
329 if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
330 final Uri uri = getArguments().getParcelable(EXTRA_URI);
331 final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid);
332 icon.setImageURI(childUri);
333 } else {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700334 icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(context, mimeType));
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700335 }
336
337 title.setText(displayName);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700338 if (summary != null) {
339 summary.setText(DateUtils.getRelativeTimeSpanString(lastModified));
340 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700341 }
342 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700343
344 private static int getDocumentFlags(Context context, Uri uri) {
345 final Cursor cursor = context.getContentResolver().query(uri, new String[] {
346 DocumentColumns.FLAGS }, null, null, null);
347 try {
348 if (cursor.moveToFirst()) {
349 return getCursorInt(cursor, DocumentColumns.FLAGS);
350 } else {
351 return 0;
352 }
353 } finally {
354 cursor.close();
355 }
356 }
357
358 public static String getCursorString(Cursor cursor, String columnName) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700359 return cursor.getString(cursor.getColumnIndex(columnName));
360 }
361
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700362 public static long getCursorLong(Cursor cursor, String columnName) {
363 return cursor.getLong(cursor.getColumnIndex(columnName));
364 }
365
366 public static int getCursorInt(Cursor cursor, String columnName) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700367 return cursor.getInt(cursor.getColumnIndex(columnName));
368 }
369}