blob: c24341e4202feb8c249240a36aa2c7bedb9baea3 [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 Sharkey8a8fb672013-05-07 12:41:33 -070019import static com.android.documentsui.DocumentsActivity.TAG;
Jeff Sharkeyb3620442013-09-01 18:41:04 -070020import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
21import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
22import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070023import static com.android.documentsui.model.DocumentInfo.getCursorInt;
24import static com.android.documentsui.model.DocumentInfo.getCursorLong;
25import static com.android.documentsui.model.DocumentInfo.getCursorString;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070026
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070027import android.app.Fragment;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070028import android.app.FragmentManager;
29import android.app.FragmentTransaction;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070030import android.app.LoaderManager.LoaderCallbacks;
Jeff Sharkey873daa32013-08-18 17:38:20 -070031import android.content.ContentResolver;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070032import android.content.Context;
Jeff Sharkey873daa32013-08-18 17:38:20 -070033import android.content.Intent;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070034import android.content.Loader;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070035import android.database.Cursor;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070036import android.graphics.Bitmap;
37import android.graphics.Point;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070038import android.net.Uri;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070039import android.os.AsyncTask;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070040import android.os.Bundle;
41import android.provider.DocumentsContract;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070042import android.provider.DocumentsContract.Document;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070043import android.text.format.DateUtils;
Jeff Sharkey2e694f82013-08-06 16:26:14 -070044import android.text.format.Formatter;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070045import android.text.format.Time;
46import android.util.Log;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070047import android.util.SparseBooleanArray;
48import android.view.ActionMode;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070049import android.view.LayoutInflater;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070050import android.view.Menu;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070051import android.view.MenuItem;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070052import android.view.View;
53import android.view.ViewGroup;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070054import android.widget.AbsListView;
55import android.widget.AbsListView.MultiChoiceModeListener;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070056import android.widget.AdapterView;
57import android.widget.AdapterView.OnItemClickListener;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070058import android.widget.BaseAdapter;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070059import android.widget.GridView;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070060import android.widget.ImageView;
61import android.widget.ListView;
62import android.widget.TextView;
Jeff Sharkey873daa32013-08-18 17:38:20 -070063import android.widget.Toast;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070064
Jeff Sharkeyb3620442013-09-01 18:41:04 -070065import com.android.documentsui.DocumentsActivity.State;
Jeff Sharkey724deeb2013-08-31 15:02:20 -070066import com.android.documentsui.model.DocumentInfo;
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -070067import com.android.documentsui.model.RootInfo;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070068import com.android.internal.util.Predicate;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070069import com.google.android.collect.Lists;
70
71import java.util.ArrayList;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070072import java.util.List;
Jeff Sharkey2e694f82013-08-06 16:26:14 -070073import java.util.concurrent.atomic.AtomicInteger;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070074
75/**
76 * Display the documents inside a single directory.
77 */
78public class DirectoryFragment extends Fragment {
79
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070080 private View mEmptyView;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070081 private ListView mListView;
82 private GridView mGridView;
83
Jeff Sharkeyc317af82013-07-01 16:56:54 -070084 private AbsListView mCurrentView;
85
Jeff Sharkey724deeb2013-08-31 15:02:20 -070086 private Predicate<DocumentInfo> mFilter;
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070087
Jeff Sharkeya5defe32013-08-05 17:56:48 -070088 public static final int TYPE_NORMAL = 1;
89 public static final int TYPE_SEARCH = 2;
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -070090 public static final int TYPE_RECENT_OPEN = 3;
Jeff Sharkey5b535922013-08-02 15:55:26 -070091
92 private int mType = TYPE_NORMAL;
93
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070094 private Point mThumbSize;
95
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070096 private DocumentsAdapter mAdapter;
Jeff Sharkey46899c82013-08-18 22:26:48 -070097 private LoaderCallbacks<DirectoryResult> mCallbacks;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070098
Jeff Sharkey2e694f82013-08-06 16:26:14 -070099 private static final String EXTRA_TYPE = "type";
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700100 private static final String EXTRA_AUTHORITY = "authority";
101 private static final String EXTRA_ROOT_ID = "rootId";
102 private static final String EXTRA_DOC_ID = "docId";
103 private static final String EXTRA_QUERY = "query";
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700104
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700105 private static AtomicInteger sLoaderId = new AtomicInteger(4000);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700106
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700107 private int mLastSortOrder = -1;
108
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700109 private final int mLoaderId = sLoaderId.incrementAndGet();
110
111 public static void showNormal(FragmentManager fm, Uri uri) {
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700112 show(fm, TYPE_NORMAL, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), null);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700113 }
114
115 public static void showSearch(FragmentManager fm, Uri uri, String query) {
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700116 show(fm, TYPE_SEARCH, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri),
117 query);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700118 }
119
120 public static void showRecentsOpen(FragmentManager fm) {
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700121 show(fm, TYPE_RECENT_OPEN, null, null, null, null);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700122 }
123
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700124 private static void show(FragmentManager fm, int type, String authority, String rootId,
125 String docId, String query) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700126 final Bundle args = new Bundle();
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700127 args.putInt(EXTRA_TYPE, type);
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700128 args.putString(EXTRA_AUTHORITY, authority);
129 args.putString(EXTRA_ROOT_ID, rootId);
130 args.putString(EXTRA_DOC_ID, docId);
131 args.putString(EXTRA_QUERY, query);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700132
133 final DirectoryFragment fragment = new DirectoryFragment();
134 fragment.setArguments(args);
135
136 final FragmentTransaction ft = fm.beginTransaction();
Jeff Sharkey76112212013-08-06 11:26:10 -0700137 ft.replace(R.id.container_directory, fragment);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700138 ft.commitAllowingStateLoss();
139 }
140
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700141 public static DirectoryFragment get(FragmentManager fm) {
142 // TODO: deal with multiple directories shown at once
Jeff Sharkey76112212013-08-06 11:26:10 -0700143 return (DirectoryFragment) fm.findFragmentById(R.id.container_directory);
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700144 }
145
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700146 @Override
147 public View onCreateView(
148 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
149 final Context context = inflater.getContext();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700150 final View view = inflater.inflate(R.layout.fragment_directory, container, false);
151
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700152 mEmptyView = view.findViewById(android.R.id.empty);
153
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700154 mListView = (ListView) view.findViewById(R.id.list);
155 mListView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700156 mListView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700157
158 mGridView = (GridView) view.findViewById(R.id.grid);
159 mGridView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700160 mGridView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700161
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700162 return view;
163 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700164
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700165 @Override
166 public void onActivityCreated(Bundle savedInstanceState) {
167 super.onActivityCreated(savedInstanceState);
168
169 final Context context = getActivity();
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700170
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700171 mAdapter = new DocumentsAdapter();
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700172 mType = getArguments().getInt(EXTRA_TYPE);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700173
Jeff Sharkey46899c82013-08-18 22:26:48 -0700174 mCallbacks = new LoaderCallbacks<DirectoryResult>() {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700175 @Override
Jeff Sharkey46899c82013-08-18 22:26:48 -0700176 public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700177 final State state = getDisplayState(DirectoryFragment.this);
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700178
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700179 final String authority = getArguments().getString(EXTRA_AUTHORITY);
180 final String rootId = getArguments().getString(EXTRA_ROOT_ID);
181 final String docId = getArguments().getString(EXTRA_DOC_ID);
182 final String query = getArguments().getString(EXTRA_QUERY);
Jeff Sharkey46165b52013-07-31 20:53:22 -0700183
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700184 Uri contentsUri;
185 switch (mType) {
186 case TYPE_NORMAL:
187 contentsUri = DocumentsContract.buildChildDocumentsUri(authority, docId);
188 return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
189 case TYPE_SEARCH:
190 contentsUri = DocumentsContract.buildSearchDocumentsUri(
191 authority, docId, query);
192 return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
193 case TYPE_RECENT_OPEN:
194 return new RecentLoader(context);
195 default:
196 throw new IllegalStateException("Unknown type " + mType);
197
198 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700199 }
200
201 @Override
Jeff Sharkey46899c82013-08-18 22:26:48 -0700202 public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700203 mAdapter.swapCursor(result.cursor);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700204 }
205
206 @Override
Jeff Sharkey46899c82013-08-18 22:26:48 -0700207 public void onLoaderReset(Loader<DirectoryResult> loader) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700208 mAdapter.swapCursor(null);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700209 }
210 };
211
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700212 updateDisplayState();
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700213 }
214
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700215 public void updateDisplayState() {
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700216 final State state = getDisplayState(this);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700217
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700218 if (mLastSortOrder != state.sortOrder) {
219 getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
220 mLastSortOrder = state.sortOrder;
221 }
222
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700223 mListView.smoothScrollToPosition(0);
224 mGridView.smoothScrollToPosition(0);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700225
Jeff Sharkey873daa32013-08-18 17:38:20 -0700226 mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE);
227 mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700228
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700229 mFilter = new MimePredicate(state.acceptMimes);
230
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700231 final int choiceMode;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700232 if (state.allowMultiple) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700233 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
234 } else {
235 choiceMode = ListView.CHOICE_MODE_NONE;
236 }
237
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700238 final int thumbSize;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700239 if (state.mode == MODE_GRID) {
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700240 thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700241 mListView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700242 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700243 mGridView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700244 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700245 mGridView.setNumColumns(GridView.AUTO_FIT);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700246 mGridView.setChoiceMode(choiceMode);
247 mCurrentView = mGridView;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700248 } else if (state.mode == MODE_LIST) {
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700249 thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700250 mGridView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700251 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700252 mListView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700253 mListView.setChoiceMode(choiceMode);
254 mCurrentView = mListView;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700255 } else {
256 throw new IllegalStateException();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700257 }
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700258
259 mThumbSize = new Point(thumbSize, thumbSize);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700260 }
261
262 private OnItemClickListener mItemListener = new OnItemClickListener() {
263 @Override
264 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700265 final Cursor cursor = mAdapter.getItem(position);
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700266 final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
Jeff Sharkeyf339f252013-08-15 16:17:41 -0700267 if (mFilter.apply(doc)) {
268 ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
269 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700270 }
271 };
272
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700273 private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
274 @Override
275 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
276 mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
277 return true;
278 }
279
280 @Override
281 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700282 final State state = getDisplayState(DirectoryFragment.this);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700283
284 final MenuItem open = menu.findItem(R.id.menu_open);
285 final MenuItem share = menu.findItem(R.id.menu_share);
286 final MenuItem delete = menu.findItem(R.id.menu_delete);
287
288 final boolean manageMode = state.action == ACTION_MANAGE;
289 open.setVisible(!manageMode);
290 share.setVisible(manageMode);
291 delete.setVisible(manageMode);
292
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700293 return true;
294 }
295
296 @Override
297 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700298 final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700299 final ArrayList<DocumentInfo> docs = Lists.newArrayList();
Jeff Sharkey873daa32013-08-18 17:38:20 -0700300 final int size = checked.size();
301 for (int i = 0; i < size; i++) {
302 if (checked.valueAt(i)) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700303 final Cursor cursor = mAdapter.getItem(checked.keyAt(i));
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700304 final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700305 docs.add(doc);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700306 }
Jeff Sharkey873daa32013-08-18 17:38:20 -0700307 }
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700308
Jeff Sharkey873daa32013-08-18 17:38:20 -0700309 final int id = item.getItemId();
310 if (id == R.id.menu_open) {
311 DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700312 mode.finish();
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700313 return true;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700314
315 } else if (id == R.id.menu_share) {
316 onShareDocuments(docs);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700317 mode.finish();
Jeff Sharkey873daa32013-08-18 17:38:20 -0700318 return true;
319
320 } else if (id == R.id.menu_delete) {
321 onDeleteDocuments(docs);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700322 mode.finish();
Jeff Sharkey873daa32013-08-18 17:38:20 -0700323 return true;
324
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700325 } else {
326 return false;
327 }
328 }
329
330 @Override
331 public void onDestroyActionMode(ActionMode mode) {
332 // ignored
333 }
334
335 @Override
336 public void onItemCheckedStateChanged(
337 ActionMode mode, int position, long id, boolean checked) {
338 if (checked) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700339 // Directories cannot be checked
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700340 final Cursor cursor = mAdapter.getItem(position);
341 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
342 if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700343 mCurrentView.setItemChecked(position, false);
344 }
345 }
346
347 mode.setTitle(getResources()
348 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
349 }
350 };
351
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700352 private void onShareDocuments(List<DocumentInfo> docs) {
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700353 Intent intent;
354 if (docs.size() == 1) {
355 final DocumentInfo doc = docs.get(0);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700356
Jeff Sharkey873daa32013-08-18 17:38:20 -0700357 intent = new Intent(Intent.ACTION_SEND);
358 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
359 intent.addCategory(Intent.CATEGORY_DEFAULT);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700360 intent.setType(doc.mimeType);
361 intent.putExtra(Intent.EXTRA_STREAM, doc.uri);
362
363 } else if (docs.size() > 1) {
364 intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
365 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
366 intent.addCategory(Intent.CATEGORY_DEFAULT);
367
368 final ArrayList<String> mimeTypes = Lists.newArrayList();
369 final ArrayList<Uri> uris = Lists.newArrayList();
370 for (DocumentInfo doc : docs) {
371 mimeTypes.add(doc.mimeType);
372 uris.add(doc.uri);
373 }
374
375 intent.setType(findCommonMimeType(mimeTypes));
376 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
377
378 } else {
379 return;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700380 }
381
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700382 intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via));
Jeff Sharkey873daa32013-08-18 17:38:20 -0700383 startActivity(intent);
384 }
385
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700386 private void onDeleteDocuments(List<DocumentInfo> docs) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700387 final Context context = getActivity();
388 final ContentResolver resolver = context.getContentResolver();
389
390 boolean hadTrouble = false;
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700391 for (DocumentInfo doc : docs) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700392 if (!doc.isDeleteSupported()) {
393 Log.w(TAG, "Skipping " + doc);
394 hadTrouble = true;
395 continue;
396 }
397
398 try {
399 if (resolver.delete(doc.uri, null, null) != 1) {
400 Log.w(TAG, "Failed to delete " + doc);
401 hadTrouble = true;
402 }
403 } catch (Exception e) {
404 Log.w(TAG, "Failed to delete " + doc + ": " + e);
405 hadTrouble = true;
406 }
407 }
408
409 if (hadTrouble) {
410 Toast.makeText(context, R.string.toast_failed_delete, Toast.LENGTH_SHORT).show();
411 }
412 }
413
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700414 private static State getDisplayState(Fragment fragment) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700415 return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700416 }
417
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700418 private class DocumentsAdapter extends BaseAdapter {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700419 private Cursor mCursor;
420
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700421 public void swapCursor(Cursor cursor) {
422 mCursor = cursor;
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700423
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700424 if (isEmpty()) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700425 mEmptyView.setVisibility(View.VISIBLE);
426 } else {
427 mEmptyView.setVisibility(View.GONE);
428 }
429
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700430 notifyDataSetChanged();
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700431 }
432
433 @Override
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700434 public View getView(int position, View convertView, ViewGroup parent) {
435 final Context context = parent.getContext();
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700436 final State state = getDisplayState(DirectoryFragment.this);
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700437
Jeff Sharkey873daa32013-08-18 17:38:20 -0700438 final RootsCache roots = DocumentsApplication.getRootsCache(context);
439 final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
440 context, mThumbSize);
441
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700442 if (convertView == null) {
443 final LayoutInflater inflater = LayoutInflater.from(context);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700444 if (state.mode == MODE_LIST) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700445 convertView = inflater.inflate(R.layout.item_doc_list, parent, false);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700446 } else if (state.mode == MODE_GRID) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700447 convertView = inflater.inflate(R.layout.item_doc_grid, parent, false);
448 } else {
449 throw new IllegalStateException();
450 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700451 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700452
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700453 final Cursor cursor = getItem(position);
454
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700455 final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
456 final String docRootId = getCursorString(cursor, RootCursorWrapper.COLUMN_ROOT_ID);
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700457 final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
458 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
459 final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
460 final long docLastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
461 final int docIcon = getCursorInt(cursor, Document.COLUMN_ICON);
462 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
463 final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
464 final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700465
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700466 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700467 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700468 final View summaryGrid = convertView.findViewById(R.id.summary_grid);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700469 final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1);
470 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
471 final TextView date = (TextView) convertView.findViewById(R.id.date);
472 final TextView size = (TextView) convertView.findViewById(R.id.size);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700473
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700474 final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) icon.getTag();
475 if (oldTask != null) {
476 oldTask.cancel(false);
477 }
478
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700479 if ((docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0) {
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700480 final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700481 final Bitmap cachedResult = thumbs.get(uri);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700482 if (cachedResult != null) {
483 icon.setImageBitmap(cachedResult);
484 } else {
485 final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize);
486 icon.setImageBitmap(null);
487 icon.setTag(task);
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700488 task.execute(uri);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700489 }
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700490 } else if (docIcon != 0) {
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700491 icon.setImageDrawable(DocumentInfo.loadIcon(context, docAuthority, docIcon));
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700492 } else {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700493 icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, docMimeType));
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700494 }
495
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700496 title.setText(docDisplayName);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700497
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700498 if (mType == TYPE_RECENT_OPEN) {
499 final RootInfo root = roots.getRoot(docAuthority, docRootId);
500 icon1.setVisibility(View.VISIBLE);
501 icon1.setImageDrawable(root.loadIcon(context));
502 summary.setText(root.getDirectoryString());
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700503 summary.setVisibility(View.VISIBLE);
504 } else {
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700505 icon1.setVisibility(View.GONE);
506 if (docSummary != null) {
507 summary.setText(docSummary);
508 summary.setVisibility(View.VISIBLE);
509 } else {
510 summary.setVisibility(View.INVISIBLE);
511 }
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700512 }
513
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700514 if (summaryGrid != null) {
515 summaryGrid.setVisibility(
516 (summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE);
517 }
518
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700519 if (docLastModified == -1) {
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700520 date.setText(null);
521 } else {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700522 date.setText(formatTime(context, docLastModified));
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700523 }
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700524
525 if (state.showSize) {
526 size.setVisibility(View.VISIBLE);
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700527 if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700528 size.setText(null);
529 } else {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700530 size.setText(Formatter.formatFileSize(context, docSize));
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700531 }
532 } else {
533 size.setVisibility(View.GONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700534 }
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700535
536 return convertView;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700537 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700538
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700539 @Override
540 public int getCount() {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700541 return mCursor != null ? mCursor.getCount() : 0;
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700542 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700543
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700544 @Override
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700545 public Cursor getItem(int position) {
546 if (mCursor != null) {
547 mCursor.moveToPosition(position);
548 }
549 return mCursor;
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700550 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700551
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700552 @Override
553 public long getItemId(int position) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700554 return position;
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700555 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700556 }
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700557
558 private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> {
559 private final ImageView mTarget;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700560 private final Point mThumbSize;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700561
Jeff Sharkey873daa32013-08-18 17:38:20 -0700562 public ThumbnailAsyncTask(ImageView target, Point thumbSize) {
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700563 mTarget = target;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700564 mThumbSize = thumbSize;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700565 }
566
567 @Override
568 protected void onPreExecute() {
569 mTarget.setTag(this);
570 }
571
572 @Override
573 protected Bitmap doInBackground(Uri... params) {
574 final Context context = mTarget.getContext();
575 final Uri uri = params[0];
576
577 Bitmap result = null;
578 try {
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700579 result = DocumentsContract.getDocumentThumbnail(
580 context.getContentResolver(), uri, mThumbSize, null);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700581 if (result != null) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700582 final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
583 context, mThumbSize);
584 thumbs.put(uri, result);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700585 }
586 } catch (Exception e) {
587 Log.w(TAG, "Failed to load thumbnail: " + e);
588 }
589 return result;
590 }
591
592 @Override
593 protected void onPostExecute(Bitmap result) {
594 if (mTarget.getTag() == this) {
595 mTarget.setImageBitmap(result);
596 mTarget.setTag(null);
597 }
598 }
599 }
600
601 private static String formatTime(Context context, long when) {
602 // TODO: DateUtils should make this easier
603 Time then = new Time();
604 then.set(when);
605 Time now = new Time();
606 now.setToNow();
607
608 int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT
609 | DateUtils.FORMAT_ABBREV_ALL;
610
611 if (then.year != now.year) {
612 flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE;
613 } else if (then.yearDay != now.yearDay) {
614 flags |= DateUtils.FORMAT_SHOW_DATE;
615 } else {
616 flags |= DateUtils.FORMAT_SHOW_TIME;
617 }
618
619 return DateUtils.formatDateTime(context, when, flags);
620 }
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700621
622 private String findCommonMimeType(List<String> mimeTypes) {
623 String[] commonType = mimeTypes.get(0).split("/");
624 if (commonType.length != 2) {
625 return "*/*";
626 }
627
628 for (int i = 1; i < mimeTypes.size(); i++) {
629 String[] type = mimeTypes.get(i).split("/");
630 if (type.length != 2) continue;
631
632 if (!commonType[1].equals(type[1])) {
633 commonType[1] = "*";
634 }
635
636 if (!commonType[0].equals(type[0])) {
637 commonType[0] = "*";
638 commonType[1] = "*";
639 break;
640 }
641 }
642
643 return commonType[0] + "/" + commonType[1];
644 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700645}