blob: c46dfb2c009b0ce276e14617f771e1078e66dd5a [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 Sharkey6defd072013-09-23 14:23:41 -070020import static com.android.documentsui.DocumentsActivity.State.ACTION_CREATE;
Jeff Sharkeyb3620442013-09-01 18:41:04 -070021import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
22import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
23import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
Jeff Sharkeya4d1f222013-09-07 14:45:03 -070024import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN;
25import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070026import static com.android.documentsui.model.DocumentInfo.getCursorInt;
27import static com.android.documentsui.model.DocumentInfo.getCursorLong;
28import static com.android.documentsui.model.DocumentInfo.getCursorString;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070029
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070030import android.app.Fragment;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070031import android.app.FragmentManager;
32import android.app.FragmentTransaction;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070033import android.app.LoaderManager.LoaderCallbacks;
Jeff Sharkey873daa32013-08-18 17:38:20 -070034import android.content.ContentResolver;
Jeff Sharkeyd10f0492013-09-09 17:35:46 -070035import android.content.ContentValues;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070036import android.content.Context;
Jeff Sharkey873daa32013-08-18 17:38:20 -070037import android.content.Intent;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070038import android.content.Loader;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070039import android.database.Cursor;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070040import android.graphics.Bitmap;
41import android.graphics.Point;
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -070042import android.graphics.drawable.Drawable;
Jeff Sharkeydb884f52013-09-21 15:11:19 -070043import android.graphics.drawable.InsetDrawable;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070044import android.net.Uri;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070045import android.os.AsyncTask;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070046import android.os.Bundle;
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -070047import android.os.CancellationSignal;
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -070048import android.os.Parcelable;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070049import android.provider.DocumentsContract;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -070050import android.provider.DocumentsContract.Document;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070051import android.text.format.DateUtils;
Jeff Sharkey2e694f82013-08-06 16:26:14 -070052import android.text.format.Formatter;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -070053import android.text.format.Time;
54import android.util.Log;
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -070055import android.util.SparseArray;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070056import android.util.SparseBooleanArray;
57import android.view.ActionMode;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070058import android.view.LayoutInflater;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070059import android.view.Menu;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070060import android.view.MenuItem;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070061import android.view.View;
62import android.view.ViewGroup;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070063import android.widget.AbsListView;
64import android.widget.AbsListView.MultiChoiceModeListener;
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -070065import android.widget.AbsListView.RecyclerListener;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070066import android.widget.AdapterView;
67import android.widget.AdapterView.OnItemClickListener;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070068import android.widget.BaseAdapter;
Jeff Sharkeydb884f52013-09-21 15:11:19 -070069import android.widget.FrameLayout;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070070import android.widget.GridView;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070071import android.widget.ImageView;
72import android.widget.ListView;
73import android.widget.TextView;
Jeff Sharkey873daa32013-08-18 17:38:20 -070074import android.widget.Toast;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -070075
Jeff Sharkeyb3620442013-09-01 18:41:04 -070076import com.android.documentsui.DocumentsActivity.State;
Jeff Sharkeyd10f0492013-09-09 17:35:46 -070077import com.android.documentsui.RecentsProvider.StateColumns;
Jeff Sharkey724deeb2013-08-31 15:02:20 -070078import com.android.documentsui.model.DocumentInfo;
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -070079import com.android.documentsui.model.RootInfo;
Jeff Sharkeyc317af82013-07-01 16:56:54 -070080import com.google.android.collect.Lists;
81
82import java.util.ArrayList;
Jeff Sharkeya5defe32013-08-05 17:56:48 -070083import java.util.List;
Jeff Sharkey2e694f82013-08-06 16:26:14 -070084import java.util.concurrent.atomic.AtomicInteger;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070085
86/**
87 * Display the documents inside a single directory.
88 */
89public class DirectoryFragment extends Fragment {
90
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -070091 private View mEmptyView;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -070092 private ListView mListView;
93 private GridView mGridView;
94
Jeff Sharkeyc317af82013-07-01 16:56:54 -070095 private AbsListView mCurrentView;
96
Jeff Sharkeya5defe32013-08-05 17:56:48 -070097 public static final int TYPE_NORMAL = 1;
98 public static final int TYPE_SEARCH = 2;
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -070099 public static final int TYPE_RECENT_OPEN = 3;
Jeff Sharkey5b535922013-08-02 15:55:26 -0700100
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700101 public static final int ANIM_NONE = 1;
102 public static final int ANIM_SIDE = 2;
103 public static final int ANIM_DOWN = 3;
104 public static final int ANIM_UP = 4;
105
Jeff Sharkey5b535922013-08-02 15:55:26 -0700106 private int mType = TYPE_NORMAL;
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700107 private String mStateKey;
Jeff Sharkey5b535922013-08-02 15:55:26 -0700108
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700109 private int mLastMode = MODE_UNKNOWN;
110 private int mLastSortOrder = SORT_ORDER_UNKNOWN;
Jeff Sharkey5e1884d2013-09-10 17:56:39 -0700111 private boolean mLastShowSize = false;
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700112
Jeff Sharkey9656a532013-09-13 13:42:19 -0700113 private boolean mHideGridTitles = false;
114
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700115 private Point mThumbSize;
116
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700117 private DocumentsAdapter mAdapter;
Jeff Sharkey46899c82013-08-18 22:26:48 -0700118 private LoaderCallbacks<DirectoryResult> mCallbacks;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700119
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700120 private static final String EXTRA_TYPE = "type";
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700121 private static final String EXTRA_ROOT = "root";
122 private static final String EXTRA_DOC = "doc";
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700123 private static final String EXTRA_QUERY = "query";
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700124 private static final String EXTRA_IGNORE_STATE = "ignoreState";
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700125
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700126 private static AtomicInteger sLoaderId = new AtomicInteger(4000);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700127
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700128 private final int mLoaderId = sLoaderId.incrementAndGet();
129
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700130 public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
131 show(fm, TYPE_NORMAL, root, doc, null, anim);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700132 }
133
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700134 public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
135 show(fm, TYPE_SEARCH, root, null, query, anim);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700136 }
137
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700138 public static void showRecentsOpen(FragmentManager fm, int anim) {
139 show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700140 }
141
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700142 private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
143 String query, int anim) {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700144 final Bundle args = new Bundle();
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700145 args.putInt(EXTRA_TYPE, type);
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700146 args.putParcelable(EXTRA_ROOT, root);
147 args.putParcelable(EXTRA_DOC, doc);
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700148 args.putString(EXTRA_QUERY, query);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700149
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700150 final FragmentTransaction ft = fm.beginTransaction();
151 switch (anim) {
152 case ANIM_SIDE:
153 args.putBoolean(EXTRA_IGNORE_STATE, true);
154 break;
155 case ANIM_DOWN:
156 args.putBoolean(EXTRA_IGNORE_STATE, true);
157 ft.setCustomAnimations(R.animator.dir_down, R.animator.dir_frozen);
158 break;
159 case ANIM_UP:
160 ft.setCustomAnimations(R.animator.dir_frozen, R.animator.dir_up);
161 break;
162 }
163
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700164 final DirectoryFragment fragment = new DirectoryFragment();
165 fragment.setArguments(args);
166
Jeff Sharkey76112212013-08-06 11:26:10 -0700167 ft.replace(R.id.container_directory, fragment);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700168 ft.commitAllowingStateLoss();
169 }
170
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700171 private static String buildStateKey(RootInfo root, DocumentInfo doc) {
172 final StringBuilder builder = new StringBuilder();
173 builder.append(root != null ? root.authority : "null").append(';');
174 builder.append(root != null ? root.rootId : "null").append(';');
175 builder.append(doc != null ? doc.documentId : "null");
176 return builder.toString();
177 }
178
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700179 public static DirectoryFragment get(FragmentManager fm) {
180 // TODO: deal with multiple directories shown at once
Jeff Sharkey76112212013-08-06 11:26:10 -0700181 return (DirectoryFragment) fm.findFragmentById(R.id.container_directory);
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700182 }
183
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700184 @Override
185 public View onCreateView(
186 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
187 final Context context = inflater.getContext();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700188 final View view = inflater.inflate(R.layout.fragment_directory, container, false);
189
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700190 mEmptyView = view.findViewById(android.R.id.empty);
191
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700192 mListView = (ListView) view.findViewById(R.id.list);
193 mListView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700194 mListView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700195 mListView.setRecyclerListener(mRecycleListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700196
197 mGridView = (GridView) view.findViewById(R.id.grid);
198 mGridView.setOnItemClickListener(mItemListener);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700199 mGridView.setMultiChoiceModeListener(mMultiListener);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700200 mGridView.setRecyclerListener(mRecycleListener);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700201
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700202 return view;
203 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700204
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700205 @Override
206 public void onActivityCreated(Bundle savedInstanceState) {
207 super.onActivityCreated(savedInstanceState);
208
209 final Context context = getActivity();
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700210 final State state = getDisplayState(DirectoryFragment.this);
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700211
Jeff Sharkey9656a532013-09-13 13:42:19 -0700212 final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
213 final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
214
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700215 mAdapter = new DocumentsAdapter();
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700216 mType = getArguments().getInt(EXTRA_TYPE);
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700217 mStateKey = buildStateKey(root, doc);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700218
Jeff Sharkey9656a532013-09-13 13:42:19 -0700219 if (mType == TYPE_RECENT_OPEN) {
220 // Hide titles when showing recents for picking images/videos
221 mHideGridTitles = MimePredicate.mimeMatches(
222 MimePredicate.VISUAL_MIMES, state.acceptMimes);
223 } else {
224 mHideGridTitles = (doc != null) && doc.isGridTitlesHidden();
225 }
226
Jeff Sharkey46899c82013-08-18 22:26:48 -0700227 mCallbacks = new LoaderCallbacks<DirectoryResult>() {
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700228 @Override
Jeff Sharkey46899c82013-08-18 22:26:48 -0700229 public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700230 final String query = getArguments().getString(EXTRA_QUERY);
Jeff Sharkey46165b52013-07-31 20:53:22 -0700231
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700232 Uri contentsUri;
233 switch (mType) {
234 case TYPE_NORMAL:
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700235 contentsUri = DocumentsContract.buildChildDocumentsUri(
236 doc.authority, doc.documentId);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700237 if (state.action == ACTION_MANAGE) {
238 contentsUri = DocumentsContract.setManageMode(contentsUri);
239 }
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700240 return new DirectoryLoader(
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700241 context, mType, root, doc, contentsUri, state.userSortOrder);
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700242 case TYPE_SEARCH:
243 contentsUri = DocumentsContract.buildSearchDocumentsUri(
Jeff Sharkey0e8c8712013-09-12 21:59:06 -0700244 root.authority, root.rootId, query);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700245 if (state.action == ACTION_MANAGE) {
246 contentsUri = DocumentsContract.setManageMode(contentsUri);
247 }
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700248 return new DirectoryLoader(
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700249 context, mType, root, doc, contentsUri, state.userSortOrder);
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700250 case TYPE_RECENT_OPEN:
Jeff Sharkey1c903cc2013-09-02 17:19:40 -0700251 final RootsCache roots = DocumentsApplication.getRootsCache(context);
Jeff Sharkey8b997042013-09-19 15:25:56 -0700252 return new RecentLoader(context, roots, state);
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700253 default:
254 throw new IllegalStateException("Unknown type " + mType);
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700255 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700256 }
257
258 @Override
Jeff Sharkey46899c82013-08-18 22:26:48 -0700259 public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700260 if (!isAdded()) return;
261
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700262 mAdapter.swapCursor(result.cursor);
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700263
264 // Push latest state up to UI
265 // TODO: if mode change was racing with us, don't overwrite it
Jeff Sharkey7d58fc62013-09-12 16:25:02 -0700266 if (result.mode != MODE_UNKNOWN) {
267 state.derivedMode = result.mode;
268 }
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700269 state.derivedSortOrder = result.sortOrder;
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700270 ((DocumentsActivity) context).onStateChanged();
271
272 updateDisplayState();
273
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700274 // Restore any previous instance state
275 final SparseArray<Parcelable> container = state.dirState.remove(mStateKey);
276 if (container != null && !getArguments().getBoolean(EXTRA_IGNORE_STATE, false)) {
277 getView().restoreHierarchyState(container);
278 } else if (mLastSortOrder != state.derivedSortOrder) {
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700279 mListView.smoothScrollToPosition(0);
280 mGridView.smoothScrollToPosition(0);
281 }
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700282
283 mLastSortOrder = state.derivedSortOrder;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700284 }
285
286 @Override
Jeff Sharkey46899c82013-08-18 22:26:48 -0700287 public void onLoaderReset(Loader<DirectoryResult> loader) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700288 mAdapter.swapCursor(null);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700289 }
290 };
291
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700292 // Kick off loader at least once
293 getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
294
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700295 updateDisplayState();
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700296 }
297
Jeff Sharkey42d26792013-09-06 13:22:09 -0700298 @Override
Jeff Sharkeyc8ae7a52013-09-18 16:26:49 -0700299 public void onStop() {
300 super.onStop();
301
302 // Remember last scroll location
303 final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
304 getView().saveHierarchyState(container);
305 final State state = getDisplayState(this);
306 state.dirState.put(mStateKey, container);
307 }
308
309 @Override
Jeff Sharkey7d58fc62013-09-12 16:25:02 -0700310 public void onResume() {
311 super.onResume();
Jeff Sharkey42d26792013-09-06 13:22:09 -0700312 updateDisplayState();
313 }
314
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700315 public void onUserSortOrderChanged() {
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700316 // Sort order change always triggers reload; we'll trigger state change
317 // on the flip side.
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700318 getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
319 }
320
321 public void onUserModeChanged() {
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700322 final ContentResolver resolver = getActivity().getContentResolver();
323 final State state = getDisplayState(this);
324
325 final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
326 final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
327
Jeff Sharkey0e8c8712013-09-12 21:59:06 -0700328 if (root != null && doc != null) {
Jeff Sharkey7d58fc62013-09-12 16:25:02 -0700329 final Uri stateUri = RecentsProvider.buildState(
330 root.authority, root.rootId, doc.documentId);
331 final ContentValues values = new ContentValues();
332 values.put(StateColumns.MODE, state.userMode);
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700333
Jeff Sharkey7d58fc62013-09-12 16:25:02 -0700334 new AsyncTask<Void, Void, Void>() {
335 @Override
336 protected Void doInBackground(Void... params) {
337 resolver.insert(stateUri, values);
338 return null;
339 }
340 }.execute();
341 }
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700342
343 // Mode change is just visual change; no need to kick loader, and
344 // deliver change event immediately.
345 state.derivedMode = state.userMode;
346 ((DocumentsActivity) getActivity()).onStateChanged();
347
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700348 updateDisplayState();
349 }
350
351 private void updateDisplayState() {
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700352 final State state = getDisplayState(this);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700353
Jeff Sharkey5e1884d2013-09-10 17:56:39 -0700354 if (mLastMode == state.derivedMode && mLastShowSize == state.showSize) return;
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700355 mLastMode = state.derivedMode;
Jeff Sharkey5e1884d2013-09-10 17:56:39 -0700356 mLastShowSize = state.showSize;
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700357
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700358 mListView.setVisibility(state.derivedMode == MODE_LIST ? View.VISIBLE : View.GONE);
359 mGridView.setVisibility(state.derivedMode == MODE_GRID ? View.VISIBLE : View.GONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700360
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700361 final int choiceMode;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700362 if (state.allowMultiple) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700363 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
364 } else {
365 choiceMode = ListView.CHOICE_MODE_NONE;
366 }
367
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700368 final int thumbSize;
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700369 if (state.derivedMode == MODE_GRID) {
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700370 thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700371 mListView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700372 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700373 mGridView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700374 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700375 mGridView.setNumColumns(GridView.AUTO_FIT);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700376 mGridView.setChoiceMode(choiceMode);
377 mCurrentView = mGridView;
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700378 } else if (state.derivedMode == MODE_LIST) {
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700379 thumbSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700380 mGridView.setAdapter(null);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700381 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700382 mListView.setAdapter(mAdapter);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700383 mListView.setChoiceMode(choiceMode);
384 mCurrentView = mListView;
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700385 } else {
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700386 throw new IllegalStateException("Unknown state " + state.derivedMode);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700387 }
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700388
389 mThumbSize = new Point(thumbSize, thumbSize);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700390 }
391
392 private OnItemClickListener mItemListener = new OnItemClickListener() {
393 @Override
394 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700395 final Cursor cursor = mAdapter.getItem(position);
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700396 if (cursor != null) {
Jeff Sharkey7cf49032013-09-26 10:54:16 -0700397 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
398 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
399 if (isDocumentEnabled(docMimeType, docFlags)) {
400 final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700401 ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
402 }
Jeff Sharkeyf339f252013-08-15 16:17:41 -0700403 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700404 }
405 };
406
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700407 private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
408 @Override
409 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
410 mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
411 return true;
412 }
413
414 @Override
415 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700416 final State state = getDisplayState(DirectoryFragment.this);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700417
418 final MenuItem open = menu.findItem(R.id.menu_open);
419 final MenuItem share = menu.findItem(R.id.menu_share);
420 final MenuItem delete = menu.findItem(R.id.menu_delete);
421
422 final boolean manageMode = state.action == ACTION_MANAGE;
423 open.setVisible(!manageMode);
424 share.setVisible(manageMode);
425 delete.setVisible(manageMode);
426
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700427 return true;
428 }
429
430 @Override
431 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700432 final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700433 final ArrayList<DocumentInfo> docs = Lists.newArrayList();
Jeff Sharkey873daa32013-08-18 17:38:20 -0700434 final int size = checked.size();
435 for (int i = 0; i < size; i++) {
436 if (checked.valueAt(i)) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700437 final Cursor cursor = mAdapter.getItem(checked.keyAt(i));
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700438 final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700439 docs.add(doc);
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700440 }
Jeff Sharkey873daa32013-08-18 17:38:20 -0700441 }
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700442
Jeff Sharkey873daa32013-08-18 17:38:20 -0700443 final int id = item.getItemId();
444 if (id == R.id.menu_open) {
445 DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700446 mode.finish();
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700447 return true;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700448
449 } else if (id == R.id.menu_share) {
450 onShareDocuments(docs);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700451 mode.finish();
Jeff Sharkey873daa32013-08-18 17:38:20 -0700452 return true;
453
454 } else if (id == R.id.menu_delete) {
455 onDeleteDocuments(docs);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700456 mode.finish();
Jeff Sharkey873daa32013-08-18 17:38:20 -0700457 return true;
458
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700459 } else {
460 return false;
461 }
462 }
463
464 @Override
465 public void onDestroyActionMode(ActionMode mode) {
466 // ignored
467 }
468
469 @Override
470 public void onItemCheckedStateChanged(
471 ActionMode mode, int position, long id, boolean checked) {
472 if (checked) {
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700473 // Directories and footer items cannot be checked
474 boolean valid = false;
475
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700476 final Cursor cursor = mAdapter.getItem(position);
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700477 if (cursor != null) {
478 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
Jeff Sharkey7cf49032013-09-26 10:54:16 -0700479 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
480 if (!Document.MIME_TYPE_DIR.equals(docMimeType)) {
481 valid = isDocumentEnabled(docMimeType, docFlags);
482 }
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700483 }
484
485 if (!valid) {
Jeff Sharkeyc317af82013-07-01 16:56:54 -0700486 mCurrentView.setItemChecked(position, false);
487 }
488 }
489
490 mode.setTitle(getResources()
491 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount()));
492 }
493 };
494
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700495 private RecyclerListener mRecycleListener = new RecyclerListener() {
496 @Override
497 public void onMovedToScrapHeap(View view) {
498 final ImageView iconThumb = (ImageView) view.findViewById(R.id.icon_thumb);
499 if (iconThumb != null) {
500 final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag();
501 if (oldTask != null) {
502 oldTask.reallyCancel();
503 iconThumb.setTag(null);
504 }
505 }
506 }
507 };
508
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700509 private void onShareDocuments(List<DocumentInfo> docs) {
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700510 Intent intent;
511 if (docs.size() == 1) {
512 final DocumentInfo doc = docs.get(0);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700513
Jeff Sharkey873daa32013-08-18 17:38:20 -0700514 intent = new Intent(Intent.ACTION_SEND);
515 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
516 intent.addCategory(Intent.CATEGORY_DEFAULT);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700517 intent.setType(doc.mimeType);
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700518 intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700519
520 } else if (docs.size() > 1) {
521 intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
522 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
523 intent.addCategory(Intent.CATEGORY_DEFAULT);
524
525 final ArrayList<String> mimeTypes = Lists.newArrayList();
526 final ArrayList<Uri> uris = Lists.newArrayList();
527 for (DocumentInfo doc : docs) {
528 mimeTypes.add(doc.mimeType);
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700529 uris.add(doc.derivedUri);
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700530 }
531
532 intent.setType(findCommonMimeType(mimeTypes));
533 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
534
535 } else {
536 return;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700537 }
538
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700539 intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via));
Jeff Sharkey873daa32013-08-18 17:38:20 -0700540 startActivity(intent);
541 }
542
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700543 private void onDeleteDocuments(List<DocumentInfo> docs) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700544 final Context context = getActivity();
545 final ContentResolver resolver = context.getContentResolver();
546
547 boolean hadTrouble = false;
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700548 for (DocumentInfo doc : docs) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700549 if (!doc.isDeleteSupported()) {
550 Log.w(TAG, "Skipping " + doc);
551 hadTrouble = true;
552 continue;
553 }
554
Jeff Sharkeya4d1f222013-09-07 14:45:03 -0700555 if (!DocumentsContract.deleteDocument(resolver, doc.derivedUri)) {
Jeff Sharkey4c5fe522013-09-03 14:17:06 -0700556 Log.w(TAG, "Failed to delete " + doc);
Jeff Sharkey873daa32013-08-18 17:38:20 -0700557 hadTrouble = true;
558 }
559 }
560
561 if (hadTrouble) {
562 Toast.makeText(context, R.string.toast_failed_delete, Toast.LENGTH_SHORT).show();
563 }
564 }
565
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700566 private static State getDisplayState(Fragment fragment) {
Jeff Sharkey3c28b792013-07-01 17:22:02 -0700567 return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700568 }
569
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700570 private static abstract class Footer {
571 private final int mItemViewType;
572
573 public Footer(int itemViewType) {
574 mItemViewType = itemViewType;
575 }
576
577 public abstract View getView(View convertView, ViewGroup parent);
578
579 public int getItemViewType() {
580 return mItemViewType;
581 }
Jeff Sharkey20b32272013-09-03 15:25:52 -0700582 }
583
Jeff Sharkey5e1884d2013-09-10 17:56:39 -0700584 private class LoadingFooter extends Footer {
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700585 public LoadingFooter() {
586 super(1);
587 }
588
Jeff Sharkey20b32272013-09-03 15:25:52 -0700589 @Override
590 public View getView(View convertView, ViewGroup parent) {
591 final Context context = parent.getContext();
Jeff Sharkey5e1884d2013-09-10 17:56:39 -0700592 final State state = getDisplayState(DirectoryFragment.this);
593
Jeff Sharkey20b32272013-09-03 15:25:52 -0700594 if (convertView == null) {
595 final LayoutInflater inflater = LayoutInflater.from(context);
Jeff Sharkey5e1884d2013-09-10 17:56:39 -0700596 if (state.derivedMode == MODE_LIST) {
597 convertView = inflater.inflate(R.layout.item_loading_list, parent, false);
598 } else if (state.derivedMode == MODE_GRID) {
599 convertView = inflater.inflate(R.layout.item_loading_grid, parent, false);
600 } else {
601 throw new IllegalStateException();
602 }
Jeff Sharkey20b32272013-09-03 15:25:52 -0700603 }
Jeff Sharkey5e1884d2013-09-10 17:56:39 -0700604
Jeff Sharkey20b32272013-09-03 15:25:52 -0700605 return convertView;
606 }
607 }
608
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700609 private class MessageFooter extends Footer {
Jeff Sharkey20b32272013-09-03 15:25:52 -0700610 private final int mIcon;
611 private final String mMessage;
612
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700613 public MessageFooter(int itemViewType, int icon, String message) {
614 super(itemViewType);
Jeff Sharkey20b32272013-09-03 15:25:52 -0700615 mIcon = icon;
616 mMessage = message;
617 }
618
619 @Override
620 public View getView(View convertView, ViewGroup parent) {
621 final Context context = parent.getContext();
622 final State state = getDisplayState(DirectoryFragment.this);
623
624 if (convertView == null) {
625 final LayoutInflater inflater = LayoutInflater.from(context);
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700626 if (state.derivedMode == MODE_LIST) {
Jeff Sharkey20b32272013-09-03 15:25:52 -0700627 convertView = inflater.inflate(R.layout.item_message_list, parent, false);
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700628 } else if (state.derivedMode == MODE_GRID) {
Jeff Sharkey20b32272013-09-03 15:25:52 -0700629 convertView = inflater.inflate(R.layout.item_message_grid, parent, false);
630 } else {
631 throw new IllegalStateException();
632 }
633 }
634
635 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
636 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
637 icon.setImageResource(mIcon);
638 title.setText(mMessage);
639 return convertView;
640 }
641 }
642
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700643 private class DocumentsAdapter extends BaseAdapter {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700644 private Cursor mCursor;
Jeff Sharkey20b32272013-09-03 15:25:52 -0700645 private int mCursorCount;
646
647 private List<Footer> mFooters = Lists.newArrayList();
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700648
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700649 public void swapCursor(Cursor cursor) {
650 mCursor = cursor;
Jeff Sharkey20b32272013-09-03 15:25:52 -0700651 mCursorCount = cursor != null ? cursor.getCount() : 0;
652
653 mFooters.clear();
654
655 final Bundle extras = cursor != null ? cursor.getExtras() : null;
656 if (extras != null) {
657 final String info = extras.getString(DocumentsContract.EXTRA_INFO);
658 if (info != null) {
Jeff Sharkeydb884f52013-09-21 15:11:19 -0700659 mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_info, info));
Jeff Sharkey20b32272013-09-03 15:25:52 -0700660 }
661 final String error = extras.getString(DocumentsContract.EXTRA_ERROR);
662 if (error != null) {
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700663 mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, error));
Jeff Sharkey20b32272013-09-03 15:25:52 -0700664 }
665 if (extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)) {
666 mFooters.add(new LoadingFooter());
667 }
668 }
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700669
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700670 if (isEmpty()) {
Jeff Sharkeyc6cbdf12013-08-07 16:22:02 -0700671 mEmptyView.setVisibility(View.VISIBLE);
672 } else {
673 mEmptyView.setVisibility(View.GONE);
674 }
675
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700676 notifyDataSetChanged();
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700677 }
678
679 @Override
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700680 public View getView(int position, View convertView, ViewGroup parent) {
Jeff Sharkey20b32272013-09-03 15:25:52 -0700681 if (position < mCursorCount) {
682 return getDocumentView(position, convertView, parent);
683 } else {
684 position -= mCursorCount;
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700685 convertView = mFooters.get(position).getView(convertView, parent);
686 // Only the view itself is disabled; contents inside shouldn't
687 // be dimmed.
688 convertView.setEnabled(false);
689 return convertView;
Jeff Sharkey20b32272013-09-03 15:25:52 -0700690 }
691 }
692
693 private View getDocumentView(int position, View convertView, ViewGroup parent) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700694 final Context context = parent.getContext();
Jeff Sharkeyb3620442013-09-01 18:41:04 -0700695 final State state = getDisplayState(DirectoryFragment.this);
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700696
Jeff Sharkey9656a532013-09-13 13:42:19 -0700697 final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
698
Jeff Sharkey873daa32013-08-18 17:38:20 -0700699 final RootsCache roots = DocumentsApplication.getRootsCache(context);
700 final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
701 context, mThumbSize);
702
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700703 if (convertView == null) {
704 final LayoutInflater inflater = LayoutInflater.from(context);
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700705 if (state.derivedMode == MODE_LIST) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700706 convertView = inflater.inflate(R.layout.item_doc_list, parent, false);
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700707 } else if (state.derivedMode == MODE_GRID) {
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700708 convertView = inflater.inflate(R.layout.item_doc_grid, parent, false);
Jeff Sharkeydb884f52013-09-21 15:11:19 -0700709
710 // Apply padding to grid items
711 final FrameLayout grid = (FrameLayout) convertView;
712 final int gridPadding = getResources()
713 .getDimensionPixelSize(R.dimen.grid_padding);
Jeff Sharkey38ec2522013-09-24 12:07:12 -0700714
715 // Tricksy hobbitses! We need to fully clear the drawable so
716 // the view doesn't clobber the new InsetDrawable callback
717 // when setting back later.
718 final Drawable fg = grid.getForeground();
719 final Drawable bg = grid.getBackground();
720 grid.setForeground(null);
721 grid.setBackground(null);
722 grid.setForeground(new InsetDrawable(fg, gridPadding));
723 grid.setBackground(new InsetDrawable(bg, gridPadding));
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700724 } else {
725 throw new IllegalStateException();
726 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700727 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700728
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700729 final Cursor cursor = getItem(position);
730
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700731 final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
732 final String docRootId = getCursorString(cursor, RootCursorWrapper.COLUMN_ROOT_ID);
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700733 final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
734 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
735 final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
736 final long docLastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
737 final int docIcon = getCursorInt(cursor, Document.COLUMN_ICON);
738 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
739 final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
740 final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700741
Jeff Sharkey9656a532013-09-13 13:42:19 -0700742 final View line1 = convertView.findViewById(R.id.line1);
743 final View line2 = convertView.findViewById(R.id.line2);
744
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700745 final View icon = convertView.findViewById(android.R.id.icon);
746 final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime);
747 final ImageView iconThumb = (ImageView) convertView.findViewById(R.id.icon_thumb);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700748 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
749 final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1);
Jeff Sharkey9656a532013-09-13 13:42:19 -0700750 final ImageView icon2 = (ImageView) convertView.findViewById(android.R.id.icon2);
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700751 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
752 final TextView date = (TextView) convertView.findViewById(R.id.date);
753 final TextView size = (TextView) convertView.findViewById(R.id.size);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700754
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700755 final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag();
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700756 if (oldTask != null) {
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700757 oldTask.reallyCancel();
758 iconThumb.setTag(null);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700759 }
760
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700761 iconMime.animate().cancel();
762 iconThumb.animate().cancel();
763
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700764 final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
Jeff Sharkeyd10f0492013-09-09 17:35:46 -0700765 final boolean allowThumbnail = (state.derivedMode == MODE_GRID)
Jeff Sharkey9656a532013-09-13 13:42:19 -0700766 || MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, docMimeType);
767 final boolean showThumbnail = supportsThumbnail && allowThumbnail;
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700768
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700769 boolean cacheHit = false;
Jeff Sharkey9656a532013-09-13 13:42:19 -0700770 if (showThumbnail) {
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700771 final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700772 final Bitmap cachedResult = thumbs.get(uri);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700773 if (cachedResult != null) {
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700774 iconThumb.setImageBitmap(cachedResult);
775 cacheHit = true;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700776 } else {
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700777 iconThumb.setImageDrawable(null);
778 final ThumbnailAsyncTask task = new ThumbnailAsyncTask(
779 uri, iconMime, iconThumb, mThumbSize);
780 iconThumb.setTag(task);
781 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700782 }
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700783 }
784
785 // Always throw MIME icon into place, even when a thumbnail is being
786 // loaded in background.
787 if (cacheHit) {
788 iconMime.setAlpha(0f);
789 iconThumb.setAlpha(1f);
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700790 } else {
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700791 iconMime.setAlpha(1f);
792 iconThumb.setAlpha(0f);
793 if (docIcon != 0) {
794 iconMime.setImageDrawable(
795 IconUtils.loadPackageIcon(context, docAuthority, docIcon));
796 } else {
Jeff Sharkey2ceff512013-09-18 18:03:49 -0700797 iconMime.setImageDrawable(IconUtils.loadMimeIcon(
798 context, docMimeType, docAuthority, docId, state.derivedMode));
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700799 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700800 }
801
Jeff Sharkey9656a532013-09-13 13:42:19 -0700802 boolean hasLine1 = false;
Jeff Sharkey42d26792013-09-06 13:22:09 -0700803 boolean hasLine2 = false;
804
Jeff Sharkey9656a532013-09-13 13:42:19 -0700805 final boolean hideTitle = (state.derivedMode == MODE_GRID) && mHideGridTitles;
806 if (!hideTitle) {
807 title.setText(docDisplayName);
808 hasLine1 = true;
809 }
810
811 Drawable iconDrawable = null;
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700812 if (mType == TYPE_RECENT_OPEN) {
Jeff Sharkey8b997042013-09-19 15:25:56 -0700813 // We've already had to enumerate roots before any results can
814 // be shown, so this will never block.
815 final RootInfo root = roots.getRootBlocking(docAuthority, docRootId);
Jeff Sharkey9656a532013-09-13 13:42:19 -0700816 iconDrawable = root.loadIcon(context);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700817
Jeff Sharkey7d58fc62013-09-12 16:25:02 -0700818 if (summary != null) {
819 final boolean alwaysShowSummary = getResources()
820 .getBoolean(R.bool.always_show_summary);
821 if (alwaysShowSummary) {
822 summary.setText(root.getDirectoryString());
823 summary.setVisibility(View.VISIBLE);
824 hasLine2 = true;
825 } else {
Jeff Sharkey8b997042013-09-19 15:25:56 -0700826 if (iconDrawable != null && roots.isIconUniqueBlocking(root)) {
Jeff Sharkey7d58fc62013-09-12 16:25:02 -0700827 // No summary needed if icon speaks for itself
828 summary.setVisibility(View.INVISIBLE);
829 } else {
830 summary.setText(root.getDirectoryString());
831 summary.setVisibility(View.VISIBLE);
832 summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
833 hasLine2 = true;
834 }
835 }
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700836 }
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700837 } else {
Jeff Sharkey9656a532013-09-13 13:42:19 -0700838 // Directories showing thumbnails in grid mode get a little icon
839 // hint to remind user they're a directory.
840 if (Document.MIME_TYPE_DIR.equals(docMimeType) && state.derivedMode == MODE_GRID
841 && showThumbnail) {
842 iconDrawable = context.getResources().getDrawable(R.drawable.ic_root_folder);
843 }
844
Jeff Sharkey7d58fc62013-09-12 16:25:02 -0700845 if (summary != null) {
846 if (docSummary != null) {
847 summary.setText(docSummary);
848 summary.setVisibility(View.VISIBLE);
849 hasLine2 = true;
850 } else {
851 summary.setVisibility(View.INVISIBLE);
852 }
Jeff Sharkeyd82b26b2013-09-02 15:07:28 -0700853 }
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700854 }
855
Jeff Sharkey9656a532013-09-13 13:42:19 -0700856 if (icon1 != null) icon1.setVisibility(View.GONE);
857 if (icon2 != null) icon2.setVisibility(View.GONE);
858
859 if (iconDrawable != null) {
860 if (hasLine1) {
861 icon1.setVisibility(View.VISIBLE);
862 icon1.setImageDrawable(iconDrawable);
863 } else {
864 icon2.setVisibility(View.VISIBLE);
865 icon2.setImageDrawable(iconDrawable);
866 }
867 }
868
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700869 if (docLastModified == -1) {
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700870 date.setText(null);
871 } else {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700872 date.setText(formatTime(context, docLastModified));
Jeff Sharkey42d26792013-09-06 13:22:09 -0700873 hasLine2 = true;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700874 }
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700875
876 if (state.showSize) {
877 size.setVisibility(View.VISIBLE);
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700878 if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700879 size.setText(null);
880 } else {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700881 size.setText(Formatter.formatFileSize(context, docSize));
Jeff Sharkey42d26792013-09-06 13:22:09 -0700882 hasLine2 = true;
Jeff Sharkey2e694f82013-08-06 16:26:14 -0700883 }
884 } else {
885 size.setVisibility(View.GONE);
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700886 }
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700887
Jeff Sharkey9656a532013-09-13 13:42:19 -0700888 if (line1 != null) {
889 line1.setVisibility(hasLine1 ? View.VISIBLE : View.GONE);
890 }
Jeff Sharkey7d58fc62013-09-12 16:25:02 -0700891 if (line2 != null) {
892 line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE);
893 }
Jeff Sharkey42d26792013-09-06 13:22:09 -0700894
Jeff Sharkey7cf49032013-09-26 10:54:16 -0700895 final boolean enabled = isDocumentEnabled(docMimeType, docFlags);
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700896 if (enabled) {
897 setEnabledRecursive(convertView, true);
898 icon.setAlpha(1f);
Jeff Sharkey9656a532013-09-13 13:42:19 -0700899 if (icon1 != null) icon1.setAlpha(1f);
900 if (icon2 != null) icon2.setAlpha(1f);
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700901 } else {
902 setEnabledRecursive(convertView, false);
903 icon.setAlpha(0.5f);
Jeff Sharkey9656a532013-09-13 13:42:19 -0700904 if (icon1 != null) icon1.setAlpha(0.5f);
905 if (icon2 != null) icon2.setAlpha(0.5f);
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700906 }
907
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700908 return convertView;
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700909 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700910
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700911 @Override
912 public int getCount() {
Jeff Sharkey20b32272013-09-03 15:25:52 -0700913 return mCursorCount + mFooters.size();
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700914 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700915
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700916 @Override
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700917 public Cursor getItem(int position) {
Jeff Sharkey20b32272013-09-03 15:25:52 -0700918 if (position < mCursorCount) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700919 mCursor.moveToPosition(position);
Jeff Sharkey20b32272013-09-03 15:25:52 -0700920 return mCursor;
921 } else {
922 return null;
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700923 }
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700924 }
Jeff Sharkey09c10bf2013-06-30 20:02:59 -0700925
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700926 @Override
927 public long getItemId(int position) {
Jeff Sharkeyac9e6272013-08-31 21:27:44 -0700928 return position;
Jeff Sharkeya5defe32013-08-05 17:56:48 -0700929 }
Jeff Sharkey20b32272013-09-03 15:25:52 -0700930
931 @Override
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700932 public int getViewTypeCount() {
933 return 4;
934 }
935
936 @Override
Jeff Sharkey20b32272013-09-03 15:25:52 -0700937 public int getItemViewType(int position) {
938 if (position < mCursorCount) {
939 return 0;
940 } else {
Jeff Sharkeyaed873d2013-09-09 16:51:06 -0700941 position -= mCursorCount;
942 return mFooters.get(position).getItemViewType();
Jeff Sharkey20b32272013-09-03 15:25:52 -0700943 }
944 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -0700945 }
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700946
947 private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> {
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700948 private final Uri mUri;
949 private final ImageView mIconMime;
950 private final ImageView mIconThumb;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700951 private final Point mThumbSize;
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700952 private final CancellationSignal mSignal;
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700953
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700954 public ThumbnailAsyncTask(
955 Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize) {
956 mUri = uri;
957 mIconMime = iconMime;
958 mIconThumb = iconThumb;
Jeff Sharkey873daa32013-08-18 17:38:20 -0700959 mThumbSize = thumbSize;
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700960 mSignal = new CancellationSignal();
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700961 }
962
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700963 public void reallyCancel() {
964 cancel(false);
965 mSignal.cancel();
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700966 }
967
968 @Override
969 protected Bitmap doInBackground(Uri... params) {
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700970 final Context context = mIconThumb.getContext();
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700971
972 Bitmap result = null;
973 try {
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700974 // TODO: switch to using unstable provider
Jeff Sharkey724deeb2013-08-31 15:02:20 -0700975 result = DocumentsContract.getDocumentThumbnail(
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700976 context.getContentResolver(), mUri, mThumbSize, mSignal);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700977 if (result != null) {
Jeff Sharkey873daa32013-08-18 17:38:20 -0700978 final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
979 context, mThumbSize);
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700980 thumbs.put(mUri, result);
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700981 }
982 } catch (Exception e) {
983 Log.w(TAG, "Failed to load thumbnail: " + e);
984 }
985 return result;
986 }
987
988 @Override
989 protected void onPostExecute(Bitmap result) {
Jeff Sharkeya35ac2d2013-09-10 12:04:26 -0700990 if (mIconThumb.getTag() == this && result != null) {
991 mIconThumb.setTag(null);
992 mIconThumb.setImageBitmap(result);
993
994 mIconMime.setAlpha(1f);
995 mIconMime.animate().alpha(0f).start();
996 mIconThumb.setAlpha(0f);
997 mIconThumb.animate().alpha(1f).start();
Jeff Sharkey8a8fb672013-05-07 12:41:33 -0700998 }
999 }
1000 }
1001
1002 private static String formatTime(Context context, long when) {
1003 // TODO: DateUtils should make this easier
1004 Time then = new Time();
1005 then.set(when);
1006 Time now = new Time();
1007 now.setToNow();
1008
1009 int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT
1010 | DateUtils.FORMAT_ABBREV_ALL;
1011
1012 if (then.year != now.year) {
1013 flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE;
1014 } else if (then.yearDay != now.yearDay) {
1015 flags |= DateUtils.FORMAT_SHOW_DATE;
1016 } else {
1017 flags |= DateUtils.FORMAT_SHOW_TIME;
1018 }
1019
1020 return DateUtils.formatDateTime(context, when, flags);
1021 }
Jeff Sharkeyb3620442013-09-01 18:41:04 -07001022
1023 private String findCommonMimeType(List<String> mimeTypes) {
1024 String[] commonType = mimeTypes.get(0).split("/");
1025 if (commonType.length != 2) {
1026 return "*/*";
1027 }
1028
1029 for (int i = 1; i < mimeTypes.size(); i++) {
1030 String[] type = mimeTypes.get(i).split("/");
1031 if (type.length != 2) continue;
1032
1033 if (!commonType[1].equals(type[1])) {
1034 commonType[1] = "*";
1035 }
1036
1037 if (!commonType[0].equals(type[0])) {
1038 commonType[0] = "*";
1039 commonType[1] = "*";
1040 break;
1041 }
1042 }
1043
1044 return commonType[0] + "/" + commonType[1];
1045 }
Jeff Sharkeyaed873d2013-09-09 16:51:06 -07001046
1047 private void setEnabledRecursive(View v, boolean enabled) {
Jeff Sharkey9656a532013-09-13 13:42:19 -07001048 if (v == null) return;
Jeff Sharkeyaed873d2013-09-09 16:51:06 -07001049 if (v.isEnabled() == enabled) return;
1050 v.setEnabled(enabled);
1051
1052 if (v instanceof ViewGroup) {
1053 final ViewGroup vg = (ViewGroup) v;
1054 for (int i = vg.getChildCount() - 1; i >= 0; i--) {
1055 setEnabledRecursive(vg.getChildAt(i), enabled);
1056 }
1057 }
1058 }
Jeff Sharkey7cf49032013-09-26 10:54:16 -07001059
1060 private boolean isDocumentEnabled(String docMimeType, int docFlags) {
1061 final State state = getDisplayState(DirectoryFragment.this);
1062
Jeff Sharkey7cf49032013-09-26 10:54:16 -07001063 // Directories are always enabled
1064 if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
1065 return true;
1066 }
1067
Jeff Sharkey783ebc22013-09-26 19:42:52 -07001068 // Read-only files are disabled when creating
1069 if (state.action == ACTION_CREATE && (docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
1070 return false;
1071 }
1072
Jeff Sharkey7cf49032013-09-26 10:54:16 -07001073 return MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
1074 }
Jeff Sharkeye22d02e2013-04-26 16:54:55 -07001075}