summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff Sharkey <jsharkey@android.com> 2013-08-18 17:38:20 -0700
committer Jeff Sharkey <jsharkey@android.com> 2013-08-18 21:14:02 -0700
commit873daa3bd4d88e91fccf0dd6f05bacc5f2b1fef5 (patch)
treea9501133c0f735ad558bbcd8d0540d8b7223620a
parent8a8fb674767113b43ffe6c4f06b30f425aa1b67d (diff)
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
-rw-r--r--AndroidManifest.xml9
-rw-r--r--res/menu/mode_directory.xml10
-rw-r--r--res/values/strings.xml5
-rw-r--r--src/com/android/documentsui/DirectoryFragment.java145
-rw-r--r--src/com/android/documentsui/DirectoryLoader.java2
-rw-r--r--src/com/android/documentsui/DocumentChangedReceiver.java38
-rw-r--r--src/com/android/documentsui/DocumentsActivity.java75
-rw-r--r--src/com/android/documentsui/DocumentsApplication.java80
-rw-r--r--src/com/android/documentsui/RecentsCreateFragment.java3
-rw-r--r--src/com/android/documentsui/RootsCache.java77
-rw-r--r--src/com/android/documentsui/RootsFragment.java3
-rw-r--r--src/com/android/documentsui/SaveFragment.java3
-rw-r--r--src/com/android/documentsui/ThumbnailCache.java14
-rw-r--r--src/com/android/documentsui/model/Document.java8
14 files changed, 368 insertions, 104 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 518dcdc51..d79f5c694 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
<application
+ android:name=".DocumentsApplication"
android:label="@string/app_label"
android:supportsRtl="true">
@@ -51,6 +52,14 @@
android:authorities="com.android.documentsui.recents"
android:exported="false" />
+ <receiver android:name=".DocumentChangedReceiver">
+ <intent-filter>
+ <action android:name="android.provider.action.DOCUMENT_CHANGED" />
+ <data android:mimeType="vnd.android.cursor.dir/root" />
+ <data android:mimeType="vnd.android.cursor.item/root" />
+ </intent-filter>
+ </receiver>
+
<!-- TODO: remove when we have real clients -->
<activity android:name=".TestActivity" android:enabled="false">
<intent-filter>
diff --git a/res/menu/mode_directory.xml b/res/menu/mode_directory.xml
index 6b6d7e91f..624e0247e 100644
--- a/res/menu/mode_directory.xml
+++ b/res/menu/mode_directory.xml
@@ -19,4 +19,14 @@
android:id="@+id/menu_open"
android:title="@string/menu_open"
android:showAsAction="always" />
+ <item
+ android:id="@+id/menu_share"
+ android:icon="@android:drawable/ic_menu_share"
+ android:title="@string/menu_share"
+ android:showAsAction="always" />
+ <item
+ android:id="@+id/menu_delete"
+ android:icon="@android:drawable/ic_menu_delete"
+ android:title="@string/menu_delete"
+ android:showAsAction="always" />
</menu>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 760f99b54..2b831832d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -29,6 +29,8 @@
<string name="menu_open">Open</string>
<string name="menu_save">Save</string>
+ <string name="menu_share">Share</string>
+ <string name="menu_delete">Delete</string>
<string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string>
@@ -55,4 +57,7 @@
<string name="empty">No items</string>
+ <string name="toast_no_application">Can\'t open file</string>
+ <string name="toast_failed_delete">Unable to delete some documents</string>
+
</resources>
diff --git a/src/com/android/documentsui/DirectoryFragment.java b/src/com/android/documentsui/DirectoryFragment.java
index fbdb3a706..d3421e78f 100644
--- a/src/com/android/documentsui/DirectoryFragment.java
+++ b/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<Document> 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<Document> 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<Document> 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<Document> docs) {
+ final ArrayList<Uri> 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<Document> 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<Uri, Void, Bitmap> {
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/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index 98f9a4dbc..c99d6afa3 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -79,6 +79,8 @@ public class DirectoryLoader extends UriDerivativeLoader<List<Document>> {
private List<Document> loadInBackgroundInternal(Uri uri, CancellationSignal signal) {
final ArrayList<Document> 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/src/com/android/documentsui/DocumentChangedReceiver.java b/src/com/android/documentsui/DocumentChangedReceiver.java
new file mode 100644
index 000000000..72afd9e42
--- /dev/null
+++ b/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/src/com/android/documentsui/DocumentsActivity.java b/src/com/android/documentsui/DocumentsActivity.java
index 11ccc89a5..091737d72 100644
--- a/src/com/android/documentsui/DocumentsActivity.java
+++ b/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<Document> 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/src/com/android/documentsui/DocumentsApplication.java b/src/com/android/documentsui/DocumentsApplication.java
new file mode 100644
index 000000000..0a6cbc0d4
--- /dev/null
+++ b/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/src/com/android/documentsui/RecentsCreateFragment.java b/src/com/android/documentsui/RecentsCreateFragment.java
index 5cdc915da..cd8adac23 100644
--- a/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/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/src/com/android/documentsui/RootsCache.java b/src/com/android/documentsui/RootsCache.java
index acd9396ff..c3b498e33 100644
--- a/src/com/android/documentsui/RootsCache.java
+++ b/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<String, DocumentsProviderInfo> sProviders = Maps.newHashMap();
+ private HashMap<String, DocumentsProviderInfo> mProviders = Maps.newHashMap();
/** Map from (authority+rootId) to cached info */
- private static HashMap<Pair<String, String>, Root> sRoots = Maps.newHashMap();
+ private HashMap<Pair<String, String>, Root> mRoots = Maps.newHashMap();
- public static ArrayList<Root> sRootsList = Lists.newArrayList();
+ public ArrayList<Root> 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<ProviderInfo> 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<Root> getRoots(Context context) {
- ensureCache(context);
- return sRootsList;
+ public Collection<Root> 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/src/com/android/documentsui/RootsFragment.java b/src/com/android/documentsui/RootsFragment.java
index 4973e1dbd..8a48e2a51 100644
--- a/src/com/android/documentsui/RootsFragment.java
+++ b/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/src/com/android/documentsui/SaveFragment.java b/src/com/android/documentsui/SaveFragment.java
index 69010dd64..8eb81b87a 100644
--- a/src/com/android/documentsui/SaveFragment.java
+++ b/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/src/com/android/documentsui/ThumbnailCache.java b/src/com/android/documentsui/ThumbnailCache.java
index bc7abeba2..ad7cbf697 100644
--- a/src/com/android/documentsui/ThumbnailCache.java
+++ b/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<Uri, Bitmap> {
- 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/src/com/android/documentsui/model/Document.java b/src/com/android/documentsui/model/Document.java
index cf45394e3..c0f21cbcc 100644
--- a/src/com/android/documentsui/model/Document.java
+++ b/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;