From 4eb407a832b7d6a2d62a535e5cab70b00a0bc8ed Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Sun, 18 Aug 2013 17:38:20 -0700 Subject: [PATCH] Management actions, invalidate caches. In manage mode, touching launches a VIEW intent for the file. Also adds actions for sharing and deleting. Move roots and thumbnail caches into Application object, and flush cache when thumbnail size changes. Listen for package changes and broadcasts that should invalidate our roots cache. Pick default grid/list mode based on provider hint. Bug: 10329983, 10330210, 10378834, 10330069 Change-Id: I75afb1c58ab71bb9d55852b1059da7257a376248 --- core/java/android/provider/DocumentsContract.java | 1 - packages/DocumentsUI/AndroidManifest.xml | 9 ++ packages/DocumentsUI/res/menu/mode_directory.xml | 10 ++ packages/DocumentsUI/res/values/strings.xml | 5 + .../com/android/documentsui/DirectoryFragment.java | 145 ++++++++++++++++----- .../com/android/documentsui/DirectoryLoader.java | 2 + .../documentsui/DocumentChangedReceiver.java | 38 ++++++ .../com/android/documentsui/DocumentsActivity.java | 75 ++++++++--- .../android/documentsui/DocumentsApplication.java | 80 ++++++++++++ .../android/documentsui/RecentsCreateFragment.java | 3 +- .../src/com/android/documentsui/RootsCache.java | 77 ++++++----- .../src/com/android/documentsui/RootsFragment.java | 3 +- .../src/com/android/documentsui/SaveFragment.java | 3 +- .../com/android/documentsui/ThumbnailCache.java | 14 -- .../com/android/documentsui/model/Document.java | 8 ++ 15 files changed, 368 insertions(+), 105 deletions(-) create mode 100644 packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java create mode 100644 packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 91d349a61294..da7647ab4de0 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -38,7 +38,6 @@ import libcore.io.IoUtils; import java.io.FileDescriptor; import java.io.IOException; -import java.io.InputStream; import java.util.List; /** diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml index 518dcdc519bd..d79f5c6945f3 100644 --- a/packages/DocumentsUI/AndroidManifest.xml +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -4,6 +4,7 @@ @@ -51,6 +52,14 @@ android:authorities="com.android.documentsui.recents" android:exported="false" /> + + + + + + + + diff --git a/packages/DocumentsUI/res/menu/mode_directory.xml b/packages/DocumentsUI/res/menu/mode_directory.xml index 6b6d7e91fdf2..624e0247ed82 100644 --- a/packages/DocumentsUI/res/menu/mode_directory.xml +++ b/packages/DocumentsUI/res/menu/mode_directory.xml @@ -19,4 +19,14 @@ android:id="@+id/menu_open" android:title="@string/menu_open" android:showAsAction="always" /> + + diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 760f99b54512..2b831832dd03 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -29,6 +29,8 @@ Open Save + Share + Delete %1$d selected @@ -55,4 +57,7 @@ No items + Can\'t open file + Unable to delete some documents + diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index fbdb3a706020..d3421e78f7e0 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -17,12 +17,20 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; +import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE; +import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID; +import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST; +import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DATE; +import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_NAME; +import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_SIZE; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.LoaderManager.LoaderCallbacks; +import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.Loader; import android.graphics.Bitmap; import android.graphics.Point; @@ -50,6 +58,7 @@ import android.widget.GridView; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import android.widget.Toast; import com.android.documentsui.DocumentsActivity.DisplayState; import com.android.documentsui.model.Document; @@ -57,7 +66,6 @@ import com.android.documentsui.model.Root; import com.android.internal.util.Predicate; import com.google.android.collect.Lists; -import java.text.DateFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -167,11 +175,11 @@ public class DirectoryFragment extends Fragment { } final Comparator sortOrder; - if (state.sortOrder == DisplayState.SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN) { + if (state.sortOrder == SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN) { sortOrder = new Document.DateComparator(); - } else if (state.sortOrder == DisplayState.SORT_ORDER_NAME) { + } else if (state.sortOrder == SORT_ORDER_NAME) { sortOrder = new Document.NameComparator(); - } else if (state.sortOrder == DisplayState.SORT_ORDER_SIZE) { + } else if (state.sortOrder == SORT_ORDER_SIZE) { sortOrder = new Document.SizeComparator(); } else { throw new IllegalArgumentException("Unknown sort order " + state.sortOrder); @@ -216,8 +224,8 @@ public class DirectoryFragment extends Fragment { mListView.smoothScrollToPosition(0); mGridView.smoothScrollToPosition(0); - mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE); - mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE); + mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE); + mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE); final int choiceMode; if (state.allowMultiple) { @@ -227,7 +235,7 @@ public class DirectoryFragment extends Fragment { } final int thumbSize; - if (state.mode == DisplayState.MODE_GRID) { + if (state.mode == MODE_GRID) { thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width); mListView.setAdapter(null); mListView.setChoiceMode(ListView.CHOICE_MODE_NONE); @@ -236,7 +244,7 @@ public class DirectoryFragment extends Fragment { mGridView.setNumColumns(GridView.AUTO_FIT); mGridView.setChoiceMode(choiceMode); mCurrentView = mGridView; - } else if (state.mode == DisplayState.MODE_LIST) { + } else if (state.mode == MODE_LIST) { thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size); mGridView.setAdapter(null); mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE); @@ -269,26 +277,45 @@ public class DirectoryFragment extends Fragment { @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + final DisplayState state = getDisplayState(DirectoryFragment.this); + + final MenuItem open = menu.findItem(R.id.menu_open); + final MenuItem share = menu.findItem(R.id.menu_share); + final MenuItem delete = menu.findItem(R.id.menu_delete); + + final boolean manageMode = state.action == ACTION_MANAGE; + open.setVisible(!manageMode); + share.setVisible(manageMode); + delete.setVisible(manageMode); + return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (item.getItemId() == R.id.menu_open) { - final Uri uri = getArguments().getParcelable(EXTRA_URI); - final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions(); - final ArrayList docs = Lists.newArrayList(); - - final int size = checked.size(); - for (int i = 0; i < size; i++) { - if (checked.valueAt(i)) { - final Document doc = mAdapter.getItem(checked.keyAt(i)); - docs.add(doc); - } + final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions(); + final ArrayList docs = Lists.newArrayList(); + final int size = checked.size(); + for (int i = 0; i < size; i++) { + if (checked.valueAt(i)) { + final Document doc = mAdapter.getItem(checked.keyAt(i)); + docs.add(doc); } + } + + final int id = item.getItemId(); + if (id == R.id.menu_open) { + DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); + return true; - ((DocumentsActivity) getActivity()).onDocumentsPicked(docs); + } else if (id == R.id.menu_share) { + onShareDocuments(docs); return true; + + } else if (id == R.id.menu_delete) { + onDeleteDocuments(docs); + return true; + } else { return false; } @@ -315,6 +342,58 @@ public class DirectoryFragment extends Fragment { } }; + private void onShareDocuments(List docs) { + final ArrayList uris = Lists.newArrayList(); + for (Document doc : docs) { + uris.add(doc.uri); + } + + final Intent intent; + if (uris.size() > 1) { + intent = new Intent(Intent.ACTION_SEND_MULTIPLE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addCategory(Intent.CATEGORY_DEFAULT); + // TODO: find common mimetype + intent.setType("*/*"); + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + } else { + intent = new Intent(Intent.ACTION_SEND); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setData(uris.get(0)); + } + + startActivity(intent); + } + + private void onDeleteDocuments(List docs) { + final Context context = getActivity(); + final ContentResolver resolver = context.getContentResolver(); + + boolean hadTrouble = false; + for (Document doc : docs) { + if (!doc.isDeleteSupported()) { + Log.w(TAG, "Skipping " + doc); + hadTrouble = true; + continue; + } + + try { + if (resolver.delete(doc.uri, null, null) != 1) { + Log.w(TAG, "Failed to delete " + doc); + hadTrouble = true; + } + } catch (Exception e) { + Log.w(TAG, "Failed to delete " + doc + ": " + e); + hadTrouble = true; + } + } + + if (hadTrouble) { + Toast.makeText(context, R.string.toast_failed_delete, Toast.LENGTH_SHORT).show(); + } + } + private static DisplayState getDisplayState(Fragment fragment) { return ((DocumentsActivity) fragment.getActivity()).getDisplayState(); } @@ -342,11 +421,15 @@ public class DirectoryFragment extends Fragment { final Context context = parent.getContext(); final DisplayState state = getDisplayState(DirectoryFragment.this); + final RootsCache roots = DocumentsApplication.getRootsCache(context); + final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( + context, mThumbSize); + if (convertView == null) { final LayoutInflater inflater = LayoutInflater.from(context); - if (state.mode == DisplayState.MODE_LIST) { + if (state.mode == MODE_LIST) { convertView = inflater.inflate(R.layout.item_doc_list, parent, false); - } else if (state.mode == DisplayState.MODE_GRID) { + } else if (state.mode == MODE_GRID) { convertView = inflater.inflate(R.layout.item_doc_grid, parent, false); } else { throw new IllegalStateException(); @@ -369,7 +452,7 @@ public class DirectoryFragment extends Fragment { } if (doc.isThumbnailSupported()) { - final Bitmap cachedResult = ThumbnailCache.get(context).get(doc.uri); + final Bitmap cachedResult = thumbs.get(doc.uri); if (cachedResult != null) { icon.setImageBitmap(cachedResult); } else { @@ -379,7 +462,7 @@ public class DirectoryFragment extends Fragment { task.execute(doc.uri); } } else { - icon.setImageDrawable(RootsCache.resolveDocumentIcon( + icon.setImageDrawable(roots.resolveDocumentIcon( context, doc.uri.getAuthority(), doc.mimeType)); } @@ -394,7 +477,7 @@ public class DirectoryFragment extends Fragment { summary.setVisibility(View.INVISIBLE); } } else if (mType == TYPE_RECENT_OPEN) { - final Root root = RootsCache.findRoot(context, doc); + final Root root = roots.findRoot(doc); icon1.setVisibility(View.VISIBLE); icon1.setImageDrawable(root.icon); summary.setText(root.getDirectoryString()); @@ -444,11 +527,11 @@ public class DirectoryFragment extends Fragment { private static class ThumbnailAsyncTask extends AsyncTask { private final ImageView mTarget; - private final Point mSize; + private final Point mThumbSize; - public ThumbnailAsyncTask(ImageView target, Point size) { + public ThumbnailAsyncTask(ImageView target, Point thumbSize) { mTarget = target; - mSize = size; + mThumbSize = thumbSize; } @Override @@ -464,9 +547,11 @@ public class DirectoryFragment extends Fragment { Bitmap result = null; try { result = DocumentsContract.getThumbnail( - context.getContentResolver(), uri, mSize); + context.getContentResolver(), uri, mThumbSize); if (result != null) { - ThumbnailCache.get(context).put(uri, result); + final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( + context, mThumbSize); + thumbs.put(uri, result); } } catch (Exception e) { Log.w(TAG, "Failed to load thumbnail: " + e); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 98f9a4dbcd2d..c99d6afa318c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -79,6 +79,8 @@ public class DirectoryLoader extends UriDerivativeLoader> { private List loadInBackgroundInternal(Uri uri, CancellationSignal signal) { final ArrayList result = Lists.newArrayList(); + // TODO: subscribe to the notify uri from query + final ContentResolver resolver = getContext().getContentResolver(); final Cursor cursor = resolver.query(uri, null, null, null, getQuerySortOrder(), signal); try { diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java new file mode 100644 index 000000000000..72afd9e42208 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import static com.android.documentsui.DocumentsActivity.TAG; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.android.documentsui.model.Root; + +/** + * Handles {@link Root} changes which invalidate cached data. + */ +public class DocumentChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "Regenerating roots cache"); + DocumentsApplication.getRootsCache(context).update(); + // TODO: invalidate cached data in recents provider + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 11ccc89a5055..091737d72da4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -16,11 +16,20 @@ package com.android.documentsui; +import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_CREATE; +import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_GET_CONTENT; +import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE; +import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_OPEN; +import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID; +import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST; +import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DATE; + import android.app.ActionBar; import android.app.ActionBar.OnNavigationListener; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; +import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ComponentName; import android.content.ContentResolver; @@ -61,11 +70,6 @@ import java.util.List; public class DocumentsActivity extends Activity { public static final String TAG = "Documents"; - public static final int ACTION_OPEN = 1; - public static final int ACTION_CREATE = 2; - public static final int ACTION_GET_CONTENT = 3; - public static final int ACTION_MANAGE = 4; - private int mAction; private SearchView mSearchView; @@ -76,6 +80,8 @@ public class DocumentsActivity extends Activity { private final DisplayState mDisplayState = new DisplayState(); + private RootsCache mRoots; + /** Current user navigation stack; empty implies recents. */ private DocumentStack mStack = new DocumentStack(); /** Currently active search, overriding any stack. */ @@ -85,6 +91,8 @@ public class DocumentsActivity extends Activity { public void onCreate(Bundle icicle) { super.onCreate(icicle); + mRoots = DocumentsApplication.getRootsCache(this); + final Intent intent = getIntent(); final String action = intent.getAction(); if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { @@ -97,6 +105,9 @@ public class DocumentsActivity extends Activity { mAction = ACTION_MANAGE; } + // TODO: unify action in single place + mDisplayState.action = mAction; + if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { mDisplayState.allowMultiple = intent.getBooleanExtra( Intent.EXTRA_ALLOW_MULTIPLE, false); @@ -104,6 +115,7 @@ public class DocumentsActivity extends Activity { if (mAction == ACTION_MANAGE) { mDisplayState.acceptMimes = new String[] { "*/*" }; + mDisplayState.allowMultiple = true; } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { mDisplayState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); } else { @@ -131,7 +143,7 @@ public class DocumentsActivity extends Activity { } if (mAction == ACTION_MANAGE) { - mDisplayState.sortOrder = DisplayState.SORT_ORDER_DATE; + mDisplayState.sortOrder = SORT_ORDER_DATE; } mRootsContainer = findViewById(R.id.container_roots); @@ -151,7 +163,7 @@ public class DocumentsActivity extends Activity { final String authority = rootUri.getAuthority(); final String rootId = DocumentsContract.getRootId(rootUri); - final Root root = RootsCache.findRoot(this, authority, rootId); + final Root root = mRoots.findRoot(authority, rootId); if (root != null) { onRootPicked(root, true); } else { @@ -316,8 +328,8 @@ public class DocumentsActivity extends Activity { final MenuItem list = menu.findItem(R.id.menu_list); final MenuItem settings = menu.findItem(R.id.menu_settings); - grid.setVisible(mDisplayState.mode != DisplayState.MODE_GRID); - list.setVisible(mDisplayState.mode != DisplayState.MODE_LIST); + grid.setVisible(mDisplayState.mode != MODE_GRID); + list.setVisible(mDisplayState.mode != MODE_LIST); final boolean searchVisible; if (mAction == ACTION_CREATE) { @@ -360,12 +372,14 @@ public class DocumentsActivity extends Activity { } else if (id == R.id.menu_search) { return false; } else if (id == R.id.menu_grid) { - mDisplayState.mode = DisplayState.MODE_GRID; + // TODO: persist explicit user mode for cwd + mDisplayState.mode = MODE_GRID; updateDisplayState(); invalidateOptionsMenu(); return true; } else if (id == R.id.menu_list) { - mDisplayState.mode = DisplayState.MODE_LIST; + // TODO: persist explicit user mode for cwd + mDisplayState.mode = MODE_LIST; updateDisplayState(); invalidateOptionsMenu(); return true; @@ -466,9 +480,9 @@ public class DocumentsActivity extends Activity { public Root getCurrentRoot() { final Document cwd = getCurrentDirectory(); if (cwd != null) { - return RootsCache.findRoot(this, cwd); + return mRoots.findRoot(cwd); } else { - return RootsCache.getRecentsRoot(this); + return mRoots.getRecentsRoot(); } } @@ -554,6 +568,12 @@ public class DocumentsActivity extends Activity { public void onDocumentPicked(Document doc) { final FragmentManager fm = getFragmentManager(); if (doc.isDirectory()) { + // TODO: query display mode user preference for this dir + if (doc.isGridPreferred()) { + mDisplayState.mode = MODE_GRID; + } else { + mDisplayState.mode = MODE_LIST; + } mStack.push(doc); onCurrentDirectoryChanged(); } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { @@ -562,16 +582,29 @@ public class DocumentsActivity extends Activity { } else if (mAction == ACTION_CREATE) { // Replace selected file SaveFragment.get(fm).setReplaceTarget(doc); + } else if (mAction == ACTION_MANAGE) { + // Open the document + // TODO: trampoline activity for launching downloaded APKs + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setData(doc.uri); + try { + startActivity(intent); + } catch (ActivityNotFoundException ex) { + Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show(); + } } } public void onDocumentsPicked(List docs) { - final int size = docs.size(); - final Uri[] uris = new Uri[size]; - for (int i = 0; i < size; i++) { - uris[i] = docs.get(i).uri; + if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + final int size = docs.size(); + final Uri[] uris = new Uri[size]; + for (int i = 0; i < size; i++) { + uris[i] = docs.get(i).uri; + } + onFinished(uris); } - onFinished(uris); } public void onSaveRequested(Document replaceTarget) { @@ -645,6 +678,7 @@ public class DocumentsActivity extends Activity { } public static class DisplayState { + public int action; public int mode = MODE_LIST; public String[] acceptMimes; public int sortOrder = SORT_ORDER_NAME; @@ -652,6 +686,11 @@ public class DocumentsActivity extends Activity { public boolean showSize = false; public boolean localOnly = false; + public static final int ACTION_OPEN = 1; + public static final int ACTION_CREATE = 2; + public static final int ACTION_GET_CONTENT = 3; + public static final int ACTION_MANAGE = 4; + public static final int MODE_LIST = 0; public static final int MODE_GRID = 1; diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java new file mode 100644 index 000000000000..0a6cbc0d4860 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import android.app.ActivityManager; +import android.app.Application; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Point; + +public class DocumentsApplication extends Application { + private RootsCache mRoots; + private Point mThumbnailsSize; + private ThumbnailCache mThumbnails; + + public static RootsCache getRootsCache(Context context) { + return ((DocumentsApplication) context.getApplicationContext()).mRoots; + } + + public static ThumbnailCache getThumbnailsCache(Context context, Point size) { + final DocumentsApplication app = (DocumentsApplication) context.getApplicationContext(); + final ThumbnailCache thumbnails = app.mThumbnails; + if (!size.equals(app.mThumbnailsSize)) { + thumbnails.evictAll(); + app.mThumbnailsSize = size; + } + return thumbnails; + } + + @Override + public void onCreate() { + final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + final int memoryClassBytes = am.getMemoryClass() * 1024 * 1024; + + mRoots = new RootsCache(this); + mThumbnails = new ThumbnailCache(memoryClassBytes / 4); + + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme("package"); + registerReceiver(mPackageReceiver, packageFilter); + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + + if (level >= TRIM_MEMORY_MODERATE) { + mThumbnails.evictAll(); + } else if (level >= TRIM_MEMORY_BACKGROUND) { + mThumbnails.trimToSize(mThumbnails.size() / 2); + } + } + + private BroadcastReceiver mPackageReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // TODO: narrow changed/removed to only packages that have backends + mRoots.update(); + } + }; +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index 5cdc915dae9d..cd8adac23ae6 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -169,6 +169,7 @@ public class RecentsCreateFragment extends Fragment { @Override public View getView(int position, View convertView, ViewGroup parent) { final Context context = parent.getContext(); + final RootsCache roots = DocumentsApplication.getRootsCache(context); if (convertView == null) { final LayoutInflater inflater = LayoutInflater.from(context); @@ -180,7 +181,7 @@ public class RecentsCreateFragment extends Fragment { final View summaryList = convertView.findViewById(R.id.summary_list); final DocumentStack stack = getItem(position); - final Root root = RootsCache.findRoot(context, stack.peek()); + final Root root = roots.findRoot(stack.peek()); icon.setImageDrawable(root != null ? root.icon : null); final StringBuilder builder = new StringBuilder(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index acd9396ff367..c3b498e33069 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -50,63 +50,67 @@ import java.util.List; public class RootsCache { // TODO: cache roots in local provider to avoid spinning up backends + // TODO: root updates should trigger UI refresh - private static boolean sCached = false; + private final Context mContext; /** Map from authority to cached info */ - private static HashMap sProviders = Maps.newHashMap(); + private HashMap mProviders = Maps.newHashMap(); /** Map from (authority+rootId) to cached info */ - private static HashMap, Root> sRoots = Maps.newHashMap(); + private HashMap, Root> mRoots = Maps.newHashMap(); - public static ArrayList sRootsList = Lists.newArrayList(); + public ArrayList mRootsList = Lists.newArrayList(); - private static Root sRecentsRoot; + private Root mRecentsRoot; + + public RootsCache(Context context) { + mContext = context; + update(); + } /** * Gather roots from all known storage providers. */ - private static void ensureCache(Context context) { - if (sCached) return; - sCached = true; - - sProviders.clear(); - sRoots.clear(); - sRootsList.clear(); + @GuardedBy("ActivityThread") + public void update() { + mProviders.clear(); + mRoots.clear(); + mRootsList.clear(); { // Create special root for recents - final Root root = Root.buildRecents(context); - sRootsList.add(root); - sRecentsRoot = root; + final Root root = Root.buildRecents(mContext); + mRootsList.add(root); + mRecentsRoot = root; } // Query for other storage backends - final PackageManager pm = context.getPackageManager(); + final PackageManager pm = mContext.getPackageManager(); final List providers = pm.queryContentProviders( null, -1, PackageManager.GET_META_DATA); for (ProviderInfo providerInfo : providers) { if (providerInfo.metaData != null && providerInfo.metaData.containsKey( DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) { final DocumentsProviderInfo info = DocumentsProviderInfo.parseInfo( - context, providerInfo); + mContext, providerInfo); if (info == null) { Log.w(TAG, "Missing info for " + providerInfo); continue; } - sProviders.put(info.providerInfo.authority, info); + mProviders.put(info.providerInfo.authority, info); try { // TODO: remove deprecated customRoots flag // TODO: populate roots on background thread, and cache results final Uri uri = DocumentsContract.buildRootsUri(providerInfo.authority); - final Cursor cursor = context.getContentResolver() + final Cursor cursor = mContext.getContentResolver() .query(uri, null, null, null, null); try { while (cursor.moveToNext()) { - final Root root = Root.fromCursor(context, info, cursor); - sRoots.put(Pair.create(info.providerInfo.authority, root.rootId), root); - sRootsList.add(root); + final Root root = Root.fromCursor(mContext, info, cursor); + mRoots.put(Pair.create(info.providerInfo.authority, root.rootId), root); + mRootsList.add(root); } } finally { cursor.close(); @@ -120,41 +124,36 @@ public class RootsCache { } @GuardedBy("ActivityThread") - public static DocumentsProviderInfo findProvider(Context context, String authority) { - ensureCache(context); - return sProviders.get(authority); + public DocumentsProviderInfo findProvider(String authority) { + return mProviders.get(authority); } @GuardedBy("ActivityThread") - public static Root findRoot(Context context, String authority, String rootId) { - ensureCache(context); - return sRoots.get(Pair.create(authority, rootId)); + public Root findRoot(String authority, String rootId) { + return mRoots.get(Pair.create(authority, rootId)); } @GuardedBy("ActivityThread") - public static Root findRoot(Context context, Document doc) { + public Root findRoot(Document doc) { final String authority = doc.uri.getAuthority(); final String rootId = DocumentsContract.getRootId(doc.uri); - return findRoot(context, authority, rootId); + return findRoot(authority, rootId); } @GuardedBy("ActivityThread") - public static Root getRecentsRoot(Context context) { - ensureCache(context); - return sRecentsRoot; + public Root getRecentsRoot() { + return mRecentsRoot; } @GuardedBy("ActivityThread") - public static Collection getRoots(Context context) { - ensureCache(context); - return sRootsList; + public Collection getRoots() { + return mRootsList; } @GuardedBy("ActivityThread") - public static Drawable resolveDocumentIcon(Context context, String authority, String mimeType) { + public Drawable resolveDocumentIcon(Context context, String authority, String mimeType) { // Custom icons take precedence - ensureCache(context); - final DocumentsProviderInfo info = sProviders.get(authority); + final DocumentsProviderInfo info = mProviders.get(authority); if (info != null) { for (Icon icon : info.customIcons) { if (MimePredicate.mimeMatches(icon.mimeType, mimeType)) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 4973e1dbd98a..8a48e2a51bc9 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -76,13 +76,14 @@ public class RootsFragment extends Fragment { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final Context context = inflater.getContext(); + final RootsCache roots = DocumentsApplication.getRootsCache(context); final View view = inflater.inflate(R.layout.fragment_roots, container, false); mList = (ListView) view.findViewById(android.R.id.list); mList.setOnItemClickListener(mItemListener); final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS); - mAdapter = new SectionedRootsAdapter(context, RootsCache.getRoots(context), includeApps); + mAdapter = new SectionedRootsAdapter(context, roots.getRoots(), includeApps); return view; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java index 69010dd64ef1..8eb81b87ad73 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java @@ -68,11 +68,12 @@ public class SaveFragment extends Fragment { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final Context context = inflater.getContext(); + final RootsCache roots = DocumentsApplication.getRootsCache(context); final View view = inflater.inflate(R.layout.fragment_save, container, false); final ImageView icon = (ImageView) view.findViewById(android.R.id.icon); - icon.setImageDrawable(RootsCache.resolveDocumentIcon( + icon.setImageDrawable(roots.resolveDocumentIcon( context, null, getArguments().getString(EXTRA_MIME_TYPE))); mDisplayName = (EditText) view.findViewById(android.R.id.title); diff --git a/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java b/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java index bc7abeba2349..ad7cbf697301 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java @@ -16,25 +16,11 @@ package com.android.documentsui; -import android.app.ActivityManager; -import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; import android.util.LruCache; public class ThumbnailCache extends LruCache { - private static ThumbnailCache sCache; - - public static ThumbnailCache get(Context context) { - if (sCache == null) { - final ActivityManager am = (ActivityManager) context.getSystemService( - Context.ACTIVITY_SERVICE); - final int memoryClassBytes = am.getMemoryClass() * 1024 * 1024; - sCache = new ThumbnailCache(memoryClassBytes / 4); - } - return sCache; - } - public ThumbnailCache(int maxSizeBytes) { super(maxSizeBytes); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java index cf45394e3fe2..c0f21cbccae6 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java @@ -143,6 +143,14 @@ public class Document { return Documents.MIME_TYPE_DIR.equals(mimeType); } + public boolean isGridPreferred() { + return (flags & Documents.FLAG_PREFERS_GRID) != 0; + } + + public boolean isDeleteSupported() { + return (flags & Documents.FLAG_SUPPORTS_DELETE) != 0; + } + private static String getCursorString(Cursor cursor, String columnName) { final int index = cursor.getColumnIndex(columnName); return (index != -1) ? cursor.getString(index) : null; -- 2.11.0