blob: 8b3dd99179c3559ed8c10b4ec0e6b77e9bcc6f48 [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 Sharkey3c28b792013-07-01 17:22:02 -070050import com.android.documentsui.DocumentsActivity.DisplayState;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070051import com.android.documentsui.DocumentsActivity.Document;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070052import com.google.android.collect.Lists;
53
54import java.util.ArrayList;
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 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";
74
75 private static final int LOADER_DOCUMENTS = 2;
76
Jeff Sharkey46165b52013-07-31 20:53:22 -070077 public static void show(
78 FragmentManager fm, Uri uri, String displayName, boolean addToBackStack) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070079 final Bundle args = new Bundle();
80 args.putParcelable(EXTRA_URI, uri);
81
82 final DirectoryFragment fragment = new DirectoryFragment();
83 fragment.setArguments(args);
84
85 final FragmentTransaction ft = fm.beginTransaction();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070086 ft.replace(R.id.directory, fragment);
Jeff Sharkey46165b52013-07-31 20:53:22 -070087 if (addToBackStack) {
88 ft.addToBackStack(displayName);
89 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070090 ft.setBreadCrumbTitle(displayName);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070091 ft.commitAllowingStateLoss();
92 }
93
94 @Override
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070095 public void onCreate(Bundle savedInstanceState) {
96 super.onCreate(savedInstanceState);
97 setHasOptionsMenu(true);
98 }
99
100 @Override
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700101 public View onCreateView(
102 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
103 final Context context = inflater.getContext();
104
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700105 final View view = inflater.inflate(R.layout.fragment_directory, container, false);
106
107 mListView = (ListView) view.findViewById(R.id.list);
108 mListView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700109 mListView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700110
111 mGridView = (GridView) view.findViewById(R.id.grid);
112 mGridView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700113 mGridView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700114
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700115 mAdapter = new DocumentsAdapter(context);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700116 updateMode();
117
118 // TODO: migrate flags query to loader
119 final Uri uri = getArguments().getParcelable(EXTRA_URI);
120 mFlags = getDocumentFlags(context, uri);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700121
122 mCallbacks = new LoaderCallbacks<Cursor>() {
123 @Override
124 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700125 final DisplayState state = getDisplayState(DirectoryFragment.this);
126 final String sortOrder;
127 if (state.sortBy == DisplayState.SORT_BY_NAME) {
128 sortOrder = DocumentColumns.DISPLAY_NAME + " ASC";
129 } else if (state.sortBy == DisplayState.SORT_BY_DATE) {
130 sortOrder = DocumentColumns.LAST_MODIFIED + " DESC";
131 } else {
132 sortOrder = null;
133 }
134
Jeff Sharkey46165b52013-07-31 20:53:22 -0700135 final Uri contentsUri;
136 if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
137 contentsUri = uri;
138 } else {
139 contentsUri = DocumentsContract.buildContentsUri(uri);
140 }
141
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700142 return new CursorLoader(context, contentsUri, null, null, null, sortOrder);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700143 }
144
145 @Override
146 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
147 mAdapter.swapCursor(data);
148 }
149
150 @Override
151 public void onLoaderReset(Loader<Cursor> loader) {
152 mAdapter.swapCursor(null);
153 }
154 };
155
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700156 return view;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700157 }
158
159 @Override
160 public void onStart() {
161 super.onStart();
162 getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700163
164 // TODO: clean up tracking of current directory
165 final Uri uri = getArguments().getParcelable(EXTRA_URI);
166 ((DocumentsActivity) getActivity()).onDirectoryChanged(uri, mFlags);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700167 }
168
169 @Override
170 public void onStop() {
171 super.onStop();
172 getLoaderManager().destroyLoader(LOADER_DOCUMENTS);
173 }
174
175 @Override
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700176 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
177 super.onCreateOptionsMenu(menu, inflater);
178 inflater.inflate(R.menu.directory, menu);
179 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700180
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700181 @Override
182 public void onPrepareOptionsMenu(Menu menu) {
183 super.onPrepareOptionsMenu(menu);
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700184 final DisplayState state = getDisplayState(this);
185 menu.findItem(R.id.menu_grid).setVisible(state.mode != DisplayState.MODE_GRID);
186 menu.findItem(R.id.menu_list).setVisible(state.mode != DisplayState.MODE_LIST);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700187 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700188
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700189 @Override
190 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700191 final DisplayState state = getDisplayState(this);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700192 final int id = item.getItemId();
193 if (id == R.id.menu_grid) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700194 state.mode = DisplayState.MODE_GRID;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700195 updateMode();
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700196 getFragmentManager().invalidateOptionsMenu();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700197 return true;
198 } else if (id == R.id.menu_list) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700199 state.mode = DisplayState.MODE_LIST;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700200 updateMode();
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700201 getFragmentManager().invalidateOptionsMenu();
202 return true;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700203 } else {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700204 return super.onOptionsItemSelected(item);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700205 }
206 }
207
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700208 private void updateMode() {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700209 final DisplayState state = getDisplayState(this);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700210
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700211 mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE);
212 mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700213
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700214 final int choiceMode;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700215 if (state.allowMultiple) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700216 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
217 } else {
218 choiceMode = ListView.CHOICE_MODE_NONE;
219 }
220
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700221 if (state.mode == DisplayState.MODE_GRID) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700222 mListView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700223 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700224 mGridView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700225 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700226 mGridView.setNumColumns(GridView.AUTO_FIT);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700227 mGridView.setChoiceMode(choiceMode);
228 mCurrentView = mGridView;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700229 } else if (state.mode == DisplayState.MODE_LIST) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700230 mGridView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700231 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700232 mListView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700233 mListView.setChoiceMode(choiceMode);
234 mCurrentView = mListView;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700235 } else {
236 throw new IllegalStateException();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700237 }
238 }
239
Jeff Sharkey46165b52013-07-31 20:53:22 -0700240 public void updateSortBy() {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700241 getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
242 }
243
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700244 private OnItemClickListener mItemListener = new OnItemClickListener() {
245 @Override
246 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
247 final Cursor cursor = (Cursor) mAdapter.getItem(position);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700248 final Uri uri = getArguments().getParcelable(EXTRA_URI);
249 final Document doc = Document.fromCursor(uri.getAuthority(), cursor);
250 ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
251 }
252 };
253
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700254 private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
255 @Override
256 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
257 mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
258 return true;
259 }
260
261 @Override
262 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
263 return true;
264 }
265
266 @Override
267 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
268 if (item.getItemId() == R.id.menu_open) {
269 final Uri uri = getArguments().getParcelable(EXTRA_URI);
270 final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
271 final ArrayList<Document> docs = Lists.newArrayList();
272
273 final int size = checked.size();
274 for (int i = 0; i < size; i++) {
275 if (checked.valueAt(i)) {
276 final Cursor cursor = (Cursor) mAdapter.getItem(checked.keyAt(i));
277 docs.add(Document.fromCursor(uri.getAuthority(), cursor));
278 }
279 }
280
281 ((DocumentsActivity) getActivity()).onDocumentsPicked(docs);
282 return true;
283 } else {
284 return false;
285 }
286 }
287
288 @Override
289 public void onDestroyActionMode(ActionMode mode) {
290 // ignored
291 }
292
293 @Override
294 public void onItemCheckedStateChanged(
295 ActionMode mode, int position, long id, boolean checked) {
296 if (checked) {
297 final Cursor cursor = (Cursor) mAdapter.getItem(position);
298 final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
299
300 // Directories cannot be checked
301 if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
302 mCurrentView.setItemChecked(position, false);
303 }
304 }
305
306 mode.setTitle(getResources()
307 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
308 }
309 };
310
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700311 private static DisplayState getDisplayState(Fragment fragment) {
312 return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700313 }
314
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700315 private class DocumentsAdapter extends CursorAdapter {
316 public DocumentsAdapter(Context context) {
317 super(context, null, false);
318 }
319
320 @Override
321 public View newView(Context context, Cursor cursor, ViewGroup parent) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700322 final LayoutInflater inflater = LayoutInflater.from(context);
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700323 final DisplayState state = getDisplayState(DirectoryFragment.this);
324 if (state.mode == DisplayState.MODE_LIST) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700325 return inflater.inflate(R.layout.item_doc_list, parent, false);
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700326 } else if (state.mode == DisplayState.MODE_GRID) {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700327 return inflater.inflate(R.layout.item_doc_grid, parent, false);
328 } else {
329 throw new IllegalStateException();
330 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700331 }
332
333 @Override
334 public void bindView(View view, Context context, Cursor cursor) {
335 final TextView title = (TextView) view.findViewById(android.R.id.title);
336 final TextView summary = (TextView) view.findViewById(android.R.id.summary);
337 final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
338
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700339 final String guid = getCursorString(cursor, DocumentColumns.GUID);
340 final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
341 final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700342 final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700343 final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
344
345 if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
346 final Uri uri = getArguments().getParcelable(EXTRA_URI);
347 final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid);
348 icon.setImageURI(childUri);
349 } else {
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700350 icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(context, mimeType));
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700351 }
352
353 title.setText(displayName);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700354 if (summary != null) {
355 summary.setText(DateUtils.getRelativeTimeSpanString(lastModified));
356 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700357 }
358 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700359
360 private static int getDocumentFlags(Context context, Uri uri) {
361 final Cursor cursor = context.getContentResolver().query(uri, new String[] {
362 DocumentColumns.FLAGS }, null, null, null);
363 try {
364 if (cursor.moveToFirst()) {
365 return getCursorInt(cursor, DocumentColumns.FLAGS);
366 } else {
367 return 0;
368 }
369 } finally {
370 cursor.close();
371 }
372 }
373
374 public static String getCursorString(Cursor cursor, String columnName) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700375 return cursor.getString(cursor.getColumnIndex(columnName));
376 }
377
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700378 public static long getCursorLong(Cursor cursor, String columnName) {
379 return cursor.getLong(cursor.getColumnIndex(columnName));
380 }
381
382 public static int getCursorInt(Cursor cursor, String columnName) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700383 return cursor.getInt(cursor.getColumnIndex(columnName));
384 }
385}