diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 91d349a61294f..da7647ab4de0f 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 518dcdc519bd7..d79f5c6945f34 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 6b6d7e91fdf2b..624e0247ed82c 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 760f99b54512e..2b831832dd038 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 fbdb3a706020b..d3421e78f7e0f 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); } + } - ((DocumentsActivity) getActivity()).onDocumentsPicked(docs); + final int id = item.getItemId(); + if (id == R.id.menu_open) { + DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); return true; + + } 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 98f9a4dbcd2d8..c99d6afa318c2 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 0000000000000..72afd9e422085 --- /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 11ccc89a50559..091737d72da41 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 0000000000000..0a6cbc0d4860c --- /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 5cdc915dae9de..cd8adac23ae66 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 acd9396ff3678..c3b498e330697 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 4973e1dbd98ae..8a48e2a51bc9d 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 69010dd64ef13..8eb81b87ad73e 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 bc7abeba23498..ad7cbf697301b 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 cf45394e3fe20..c0f21cbccae65 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;