blob: dd9aee54a1bde8ad11dbcfc5ec4842ad9675cb43 [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 Sharkey873daa32013-08-18 17:38:20 -070020import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE;
21import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID;
22import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST;
23import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DATE;
24import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_NAME;
25import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_SIZE;
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 Sharkey46899c82013-08-18 22:26:48 -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 Sharkey09c10bf2013-06-30 20:02:59 -070042import android.text.format.DateUtils;
Jeff Sharkey2e694f82013-08-06 16:26:14 -070043import android.text.format.Formatter;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070044import android.text.format.Time;
45import android.util.Log;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070046import android.util.SparseBooleanArray;
47import android.view.ActionMode;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070048import android.view.LayoutInflater;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070049import android.view.Menu;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070050import android.view.MenuItem;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070051import android.view.View;
52import android.view.ViewGroup;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070053import android.widget.AbsListView;
54import android.widget.AbsListView.MultiChoiceModeListener;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070055import android.widget.AdapterView;
56import android.widget.AdapterView.OnItemClickListener;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070057import android.widget.BaseAdapter;
Jeff Sharkey46899c82013-08-18 22:26:48 -070058import android.widget.Button;
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 Sharkey3c28b792013-07-01 17:22:02 -070065import com.android.documentsui.DocumentsActivity.DisplayState;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070066import com.android.documentsui.model.Document;
Jeff Sharkey2e694f82013-08-06 16:26:14 -070067import com.android.documentsui.model.Root;
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.Comparator;
73import java.util.List;
Jeff Sharkey2e694f82013-08-06 16:26:14 -070074import java.util.concurrent.atomic.AtomicInteger;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070075
76/**
77 * Display the documents inside a single directory.
78 */
79public class DirectoryFragment extends Fragment {
80
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070081 private View mEmptyView;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070082 private ListView mListView;
83 private GridView mGridView;
Jeff Sharkey46899c82013-08-18 22:26:48 -070084 private Button mMoreView;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070085
Jeff Sharkeyc317af82013-07-01 16:56:54 -070086 private AbsListView mCurrentView;
87
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070088 private Predicate<Document> mFilter;
89
Jeff Sharkeya5defe32013-08-05 17:56:48 -070090 public static final int TYPE_NORMAL = 1;
91 public static final int TYPE_SEARCH = 2;
92 public static final int TYPE_RECENT_OPEN = 3;
Jeff Sharkey5b535922013-08-02 15:55:26 -070093
94 private int mType = TYPE_NORMAL;
95
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070096 private Point mThumbSize;
97
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070098 private DocumentsAdapter mAdapter;
Jeff Sharkey46899c82013-08-18 22:26:48 -070099 private LoaderCallbacks<DirectoryResult> mCallbacks;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700100
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700101 private static final String EXTRA_TYPE = "type";
Jeff Sharkey5b535922013-08-02 15:55:26 -0700102 private static final String EXTRA_URI = "uri";
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700103
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700104 private static AtomicInteger sLoaderId = new AtomicInteger(4000);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700105
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700106 private final int mLoaderId = sLoaderId.incrementAndGet();
107
108 public static void showNormal(FragmentManager fm, Uri uri) {
109 show(fm, TYPE_NORMAL, uri);
110 }
111
112 public static void showSearch(FragmentManager fm, Uri uri, String query) {
113 final Uri searchUri = DocumentsContract.buildSearchUri(uri, query);
114 show(fm, TYPE_SEARCH, searchUri);
115 }
116
117 public static void showRecentsOpen(FragmentManager fm) {
118 show(fm, TYPE_RECENT_OPEN, null);
119 }
120
121 private static void show(FragmentManager fm, int type, Uri uri) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700122 final Bundle args = new Bundle();
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700123 args.putInt(EXTRA_TYPE, type);
Jeff Sharkey5b535922013-08-02 15:55:26 -0700124 args.putParcelable(EXTRA_URI, uri);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700125
126 final DirectoryFragment fragment = new DirectoryFragment();
127 fragment.setArguments(args);
128
129 final FragmentTransaction ft = fm.beginTransaction();
Jeff Sharkey76112212013-08-06 11:26:10 -0700130 ft.replace(R.id.container_directory, fragment);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700131 ft.commitAllowingStateLoss();
132 }
133
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700134 public static DirectoryFragment get(FragmentManager fm) {
135 // TODO: deal with multiple directories shown at once
Jeff Sharkey76112212013-08-06 11:26:10 -0700136 return (DirectoryFragment) fm.findFragmentById(R.id.container_directory);
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700137 }
138
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700139 @Override
140 public View onCreateView(
141 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
142 final Context context = inflater.getContext();
143
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700144 final View view = inflater.inflate(R.layout.fragment_directory, container, false);
145
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700146 mEmptyView = view.findViewById(android.R.id.empty);
147
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700148 mListView = (ListView) view.findViewById(R.id.list);
149 mListView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700150 mListView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700151
152 mGridView = (GridView) view.findViewById(R.id.grid);
153 mGridView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700154 mGridView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700155
Jeff Sharkey46899c82013-08-18 22:26:48 -0700156 mMoreView = (Button) view.findViewById(R.id.more);
157
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700158 mAdapter = new DocumentsAdapter();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700159
Jeff Sharkey5b535922013-08-02 15:55:26 -0700160 final Uri uri = getArguments().getParcelable(EXTRA_URI);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700161 mType = getArguments().getInt(EXTRA_TYPE);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700162
Jeff Sharkey46899c82013-08-18 22:26:48 -0700163 mCallbacks = new LoaderCallbacks<DirectoryResult>() {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700164 @Override
Jeff Sharkey46899c82013-08-18 22:26:48 -0700165 public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700166 final DisplayState state = getDisplayState(DirectoryFragment.this);
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700167 mFilter = new MimePredicate(state.acceptMimes);
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700168
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700169 Uri contentsUri;
Jeff Sharkey5b535922013-08-02 15:55:26 -0700170 if (mType == TYPE_NORMAL) {
171 contentsUri = DocumentsContract.buildContentsUri(uri);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700172 } else if (mType == TYPE_RECENT_OPEN) {
173 contentsUri = RecentsProvider.buildRecentOpen();
Jeff Sharkey46165b52013-07-31 20:53:22 -0700174 } else {
Jeff Sharkey5b535922013-08-02 15:55:26 -0700175 contentsUri = uri;
Jeff Sharkey46165b52013-07-31 20:53:22 -0700176 }
177
Jeff Sharkey1d890e02013-08-15 11:24:03 -0700178 if (state.localOnly) {
179 contentsUri = DocumentsContract.setLocalOnly(contentsUri);
180 }
181
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700182 final Comparator<Document> sortOrder;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700183 if (state.sortOrder == SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700184 sortOrder = new Document.DateComparator();
Jeff Sharkey873daa32013-08-18 17:38:20 -0700185 } else if (state.sortOrder == SORT_ORDER_NAME) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700186 sortOrder = new Document.NameComparator();
Jeff Sharkey873daa32013-08-18 17:38:20 -0700187 } else if (state.sortOrder == SORT_ORDER_SIZE) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700188 sortOrder = new Document.SizeComparator();
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700189 } else {
190 throw new IllegalArgumentException("Unknown sort order " + state.sortOrder);
191 }
192
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700193 return new DirectoryLoader(context, contentsUri, mType, null, sortOrder);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700194 }
195
196 @Override
Jeff Sharkey46899c82013-08-18 22:26:48 -0700197 public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
198 mAdapter.swapDocuments(result.contents);
199
200 final Cursor cursor = result.cursor;
201 if (cursor != null && cursor.getExtras()
202 .getBoolean(DocumentsContract.EXTRA_HAS_MORE, false)) {
203 mMoreView.setText(R.string.more);
204 mMoreView.setVisibility(View.VISIBLE);
205 mMoreView.setOnClickListener(new View.OnClickListener() {
206 @Override
207 public void onClick(View v) {
208 mMoreView.setText(R.string.loading);
209 final Bundle bundle = new Bundle();
210 bundle.putBoolean(DocumentsContract.EXTRA_REQUEST_MORE, true);
211 try {
212 cursor.respond(bundle);
213 } catch (Exception e) {
214 Log.w(TAG, "Failed to respond: " + e);
215 }
216 }
217 });
218 } else {
219 mMoreView.setVisibility(View.GONE);
220 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700221 }
222
223 @Override
Jeff Sharkey46899c82013-08-18 22:26:48 -0700224 public void onLoaderReset(Loader<DirectoryResult> loader) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700225 mAdapter.swapDocuments(null);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700226 }
227 };
228
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700229 updateDisplayState();
230
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700231 return view;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700232 }
233
234 @Override
235 public void onStart() {
236 super.onStart();
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700237 getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700238 }
239
240 @Override
241 public void onStop() {
242 super.onStop();
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700243 getLoaderManager().destroyLoader(mLoaderId);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700244 }
245
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700246 public void updateDisplayState() {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700247 final DisplayState state = getDisplayState(this);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700248
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700249 // TODO: avoid kicking loader when nothing changed
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700250 getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks);
251 mListView.smoothScrollToPosition(0);
252 mGridView.smoothScrollToPosition(0);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700253
Jeff Sharkey873daa32013-08-18 17:38:20 -0700254 mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE);
255 mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700256
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700257 final int choiceMode;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700258 if (state.allowMultiple) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700259 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
260 } else {
261 choiceMode = ListView.CHOICE_MODE_NONE;
262 }
263
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700264 final int thumbSize;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700265 if (state.mode == MODE_GRID) {
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700266 thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700267 mListView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700268 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700269 mGridView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700270 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700271 mGridView.setNumColumns(GridView.AUTO_FIT);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700272 mGridView.setChoiceMode(choiceMode);
273 mCurrentView = mGridView;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700274 } else if (state.mode == MODE_LIST) {
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700275 thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700276 mGridView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700277 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700278 mListView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700279 mListView.setChoiceMode(choiceMode);
280 mCurrentView = mListView;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700281 } else {
282 throw new IllegalStateException();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700283 }
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700284
285 mThumbSize = new Point(thumbSize, thumbSize);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700286 }
287
288 private OnItemClickListener mItemListener = new OnItemClickListener() {
289 @Override
290 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700291 final Document doc = mAdapter.getItem(position);
Jeff Sharkeyf339f252013-08-15 16:17:41 -0700292 if (mFilter.apply(doc)) {
293 ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
294 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700295 }
296 };
297
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700298 private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
299 @Override
300 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
301 mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
302 return true;
303 }
304
305 @Override
306 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700307 final DisplayState state = getDisplayState(DirectoryFragment.this);
308
309 final MenuItem open = menu.findItem(R.id.menu_open);
310 final MenuItem share = menu.findItem(R.id.menu_share);
311 final MenuItem delete = menu.findItem(R.id.menu_delete);
312
313 final boolean manageMode = state.action == ACTION_MANAGE;
314 open.setVisible(!manageMode);
315 share.setVisible(manageMode);
316 delete.setVisible(manageMode);
317
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700318 return true;
319 }
320
321 @Override
322 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700323 final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
324 final ArrayList<Document> docs = Lists.newArrayList();
325 final int size = checked.size();
326 for (int i = 0; i < size; i++) {
327 if (checked.valueAt(i)) {
328 final Document doc = mAdapter.getItem(checked.keyAt(i));
329 docs.add(doc);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700330 }
Jeff Sharkey873daa32013-08-18 17:38:20 -0700331 }
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700332
Jeff Sharkey873daa32013-08-18 17:38:20 -0700333 final int id = item.getItemId();
334 if (id == R.id.menu_open) {
335 DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700336 return true;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700337
338 } else if (id == R.id.menu_share) {
339 onShareDocuments(docs);
340 return true;
341
342 } else if (id == R.id.menu_delete) {
343 onDeleteDocuments(docs);
344 return true;
345
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700346 } else {
347 return false;
348 }
349 }
350
351 @Override
352 public void onDestroyActionMode(ActionMode mode) {
353 // ignored
354 }
355
356 @Override
357 public void onItemCheckedStateChanged(
358 ActionMode mode, int position, long id, boolean checked) {
359 if (checked) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700360 // Directories cannot be checked
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700361 final Document doc = mAdapter.getItem(position);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700362 if (doc.isDirectory()) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700363 mCurrentView.setItemChecked(position, false);
364 }
365 }
366
367 mode.setTitle(getResources()
368 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
369 }
370 };
371
Jeff Sharkey873daa32013-08-18 17:38:20 -0700372 private void onShareDocuments(List<Document> docs) {
373 final ArrayList<Uri> uris = Lists.newArrayList();
374 for (Document doc : docs) {
375 uris.add(doc.uri);
376 }
377
378 final Intent intent;
379 if (uris.size() > 1) {
380 intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
381 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
382 intent.addCategory(Intent.CATEGORY_DEFAULT);
383 // TODO: find common mimetype
384 intent.setType("*/*");
385 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
386 } else {
387 intent = new Intent(Intent.ACTION_SEND);
388 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
389 intent.addCategory(Intent.CATEGORY_DEFAULT);
390 intent.setData(uris.get(0));
391 }
392
393 startActivity(intent);
394 }
395
396 private void onDeleteDocuments(List<Document> docs) {
397 final Context context = getActivity();
398 final ContentResolver resolver = context.getContentResolver();
399
400 boolean hadTrouble = false;
401 for (Document doc : docs) {
402 if (!doc.isDeleteSupported()) {
403 Log.w(TAG, "Skipping " + doc);
404 hadTrouble = true;
405 continue;
406 }
407
408 try {
409 if (resolver.delete(doc.uri, null, null) != 1) {
410 Log.w(TAG, "Failed to delete " + doc);
411 hadTrouble = true;
412 }
413 } catch (Exception e) {
414 Log.w(TAG, "Failed to delete " + doc + ": " + e);
415 hadTrouble = true;
416 }
417 }
418
419 if (hadTrouble) {
420 Toast.makeText(context, R.string.toast_failed_delete, Toast.LENGTH_SHORT).show();
421 }
422 }
423
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700424 private static DisplayState getDisplayState(Fragment fragment) {
425 return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700426 }
427
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700428 private class DocumentsAdapter extends BaseAdapter {
429 private List<Document> mDocuments;
430
431 public DocumentsAdapter() {
432 }
433
434 public void swapDocuments(List<Document> documents) {
435 mDocuments = documents;
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700436
Jeff Sharkey46899c82013-08-18 22:26:48 -0700437 if (mDocuments != null && mDocuments.isEmpty()) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700438 mEmptyView.setVisibility(View.VISIBLE);
439 } else {
440 mEmptyView.setVisibility(View.GONE);
441 }
442
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700443 notifyDataSetChanged();
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700444 }
445
446 @Override
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700447 public View getView(int position, View convertView, ViewGroup parent) {
448 final Context context = parent.getContext();
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700449 final DisplayState state = getDisplayState(DirectoryFragment.this);
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700450
Jeff Sharkey873daa32013-08-18 17:38:20 -0700451 final RootsCache roots = DocumentsApplication.getRootsCache(context);
452 final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
453 context, mThumbSize);
454
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700455 if (convertView == null) {
456 final LayoutInflater inflater = LayoutInflater.from(context);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700457 if (state.mode == MODE_LIST) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700458 convertView = inflater.inflate(R.layout.item_doc_list, parent, false);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700459 } else if (state.mode == MODE_GRID) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700460 convertView = inflater.inflate(R.layout.item_doc_grid, parent, false);
461 } else {
462 throw new IllegalStateException();
463 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700464 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700465
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700466 final Document doc = getItem(position);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700467
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700468 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700469 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700470 final View summaryGrid = convertView.findViewById(R.id.summary_grid);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700471 final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1);
472 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
473 final TextView date = (TextView) convertView.findViewById(R.id.date);
474 final TextView size = (TextView) convertView.findViewById(R.id.size);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700475
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700476 final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) icon.getTag();
477 if (oldTask != null) {
478 oldTask.cancel(false);
479 }
480
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700481 if (doc.isThumbnailSupported()) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700482 final Bitmap cachedResult = thumbs.get(doc.uri);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700483 if (cachedResult != null) {
484 icon.setImageBitmap(cachedResult);
485 } else {
486 final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize);
487 icon.setImageBitmap(null);
488 icon.setTag(task);
489 task.execute(doc.uri);
490 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700491 } else {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700492 icon.setImageDrawable(roots.resolveDocumentIcon(
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700493 context, doc.uri.getAuthority(), doc.mimeType));
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700494 }
495
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700496 title.setText(doc.displayName);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700497
498 if (mType == TYPE_NORMAL || mType == TYPE_SEARCH) {
499 icon1.setVisibility(View.GONE);
500 if (doc.summary != null) {
501 summary.setText(doc.summary);
502 summary.setVisibility(View.VISIBLE);
503 } else {
504 summary.setVisibility(View.INVISIBLE);
505 }
506 } else if (mType == TYPE_RECENT_OPEN) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700507 final Root root = roots.findRoot(doc);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700508 icon1.setVisibility(View.VISIBLE);
509 icon1.setImageDrawable(root.icon);
510 summary.setText(root.getDirectoryString());
511 summary.setVisibility(View.VISIBLE);
512 }
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 Sharkey8a8fb672013-05-07 12:41:33 -0700519 if (doc.lastModified == -1) {
520 date.setText(null);
521 } else {
522 date.setText(formatTime(context, doc.lastModified));
523 }
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700524
525 if (state.showSize) {
526 size.setVisibility(View.VISIBLE);
Jeff Sharkeyf339f252013-08-15 16:17:41 -0700527 if (doc.isDirectory() || doc.size == -1) {
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700528 size.setText(null);
529 } else {
530 size.setText(Formatter.formatFileSize(context, doc.size));
531 }
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() {
541 return mDocuments != null ? mDocuments.size() : 0;
542 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700543
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700544 @Override
545 public Document getItem(int position) {
546 return mDocuments.get(position);
547 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700548
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700549 @Override
550 public long getItemId(int position) {
551 return getItem(position).uri.hashCode();
552 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700553 }
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700554
555 private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> {
556 private final ImageView mTarget;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700557 private final Point mThumbSize;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700558
Jeff Sharkey873daa32013-08-18 17:38:20 -0700559 public ThumbnailAsyncTask(ImageView target, Point thumbSize) {
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700560 mTarget = target;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700561 mThumbSize = thumbSize;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700562 }
563
564 @Override
565 protected void onPreExecute() {
566 mTarget.setTag(this);
567 }
568
569 @Override
570 protected Bitmap doInBackground(Uri... params) {
571 final Context context = mTarget.getContext();
572 final Uri uri = params[0];
573
574 Bitmap result = null;
575 try {
576 result = DocumentsContract.getThumbnail(
Jeff Sharkey873daa32013-08-18 17:38:20 -0700577 context.getContentResolver(), uri, mThumbSize);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700578 if (result != null) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700579 final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
580 context, mThumbSize);
581 thumbs.put(uri, result);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700582 }
583 } catch (Exception e) {
584 Log.w(TAG, "Failed to load thumbnail: " + e);
585 }
586 return result;
587 }
588
589 @Override
590 protected void onPostExecute(Bitmap result) {
591 if (mTarget.getTag() == this) {
592 mTarget.setImageBitmap(result);
593 mTarget.setTag(null);
594 }
595 }
596 }
597
598 private static String formatTime(Context context, long when) {
599 // TODO: DateUtils should make this easier
600 Time then = new Time();
601 then.set(when);
602 Time now = new Time();
603 now.setToNow();
604
605 int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT
606 | DateUtils.FORMAT_ABBREV_ALL;
607
608 if (then.year != now.year) {
609 flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE;
610 } else if (then.yearDay != now.yearDay) {
611 flags |= DateUtils.FORMAT_SHOW_DATE;
612 } else {
613 flags |= DateUtils.FORMAT_SHOW_TIME;
614 }
615
616 return DateUtils.formatDateTime(context, when, flags);
617 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700618}