From 54ca29a5b94c2edf461c5433825d4ae17469fd7c Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Thu, 15 Aug 2013 11:24:03 -0700 Subject: [PATCH] DocumentsUI handles GET_CONTENT; hinting, errors. Document browser now takes over all GET_CONTENT requests that request openable Uris. It shows both storage backends and includes other apps that respond to GET_CONTENT. Only grants transient read permissions. Better guarding against throwing storage backends. Send sort order and local-only hinting to backends. Require that OPEN/CREATE_DOC users include openable category. Bug: 10330112, 10329976, 10340741, 10331689, 10329971 Change-Id: Ieb8768a6d71201816046f4a4c48832061a313c28 --- core/java/android/provider/DocumentsContract.java | 4 +- packages/DocumentsUI/AndroidManifest.xml | 8 ++ packages/DocumentsUI/res/values/strings.xml | 1 + .../com/android/documentsui/DirectoryFragment.java | 6 +- .../com/android/documentsui/DirectoryLoader.java | 36 +++++++- .../com/android/documentsui/DocumentsActivity.java | 55 +++++++---- .../com/android/documentsui/RecentsProvider.java | 4 +- .../src/com/android/documentsui/RootsCache.java | 27 +++--- .../src/com/android/documentsui/RootsFragment.java | 102 ++++++++++++++++++--- .../android/documentsui/SectionedListAdapter.java | 8 +- .../src/com/android/documentsui/TestActivity.java | 22 ++++- 11 files changed, 226 insertions(+), 47 deletions(-) diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 0a16d739b75c..909c4dda0c1a 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -65,7 +65,7 @@ public final class DocumentsContract { public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER"; /** {@hide} */ - public static final String ACTION_ROOTS_CHANGED = "android.provider.action.ROOTS_CHANGED"; + public static final String ACTION_DOCUMENT_CHANGED = "android.provider.action.DOCUMENT_CHANGED"; /** * {@link DocumentColumns#DOC_ID} value representing the root directory of a @@ -496,7 +496,7 @@ public final class DocumentsContract { * This signal is used to invalidate internal caches. */ public static void notifyRootsChanged(Context context, String authority) { - final Intent intent = new Intent(ACTION_ROOTS_CHANGED); + final Intent intent = new Intent(ACTION_DOCUMENT_CHANGED); intent.setData(buildRootsUri(authority)); context.sendBroadcast(intent); } diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml index 27f93c0549ae..9a1953fdeec8 100644 --- a/packages/DocumentsUI/AndroidManifest.xml +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -17,11 +17,19 @@ + + + + + + + + diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 84f89b4afb10..760f99b54512 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -47,6 +47,7 @@ Services Shortcuts Devices + More apps Display advanced devices Display file size diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 5a6060ac4467..e1b6a915dee7 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -144,7 +144,7 @@ public class DirectoryFragment extends Fragment { final DisplayState state = getDisplayState(DirectoryFragment.this); mFilter = new MimePredicate(state.acceptMimes); - final Uri contentsUri; + Uri contentsUri; if (mType == TYPE_NORMAL) { contentsUri = DocumentsContract.buildContentsUri(uri); } else if (mType == TYPE_RECENT_OPEN) { @@ -153,6 +153,10 @@ public class DirectoryFragment extends Fragment { contentsUri = uri; } + if (state.localOnly) { + contentsUri = DocumentsContract.setLocalOnly(contentsUri); + } + final Comparator sortOrder; if (state.sortOrder == DisplayState.SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN) { sortOrder = new Document.DateComparator(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 94c2b6130d65..98f9a4dbcd2d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -26,6 +26,7 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.CancellationSignal; +import android.provider.DocumentsContract.DocumentColumns; import android.util.Log; import com.android.documentsui.model.Document; @@ -38,6 +39,7 @@ import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.LinkedList; import java.util.List; public class DirectoryLoader extends UriDerivativeLoader> { @@ -46,6 +48,17 @@ public class DirectoryLoader extends UriDerivativeLoader> { private Predicate mFilter; private Comparator mSortOrder; + /** + * Stub result that represents an internal error. + */ + public static class ExceptionResult extends LinkedList { + public final Exception e; + + public ExceptionResult(Exception e) { + this.e = e; + } + } + public DirectoryLoader(Context context, Uri uri, int type, Predicate filter, Comparator sortOrder) { super(context, uri); @@ -56,11 +69,18 @@ public class DirectoryLoader extends UriDerivativeLoader> { @Override public List loadInBackground(Uri uri, CancellationSignal signal) { + try { + return loadInBackgroundInternal(uri, signal); + } catch (Exception e) { + return new ExceptionResult(e); + } + } + + private List loadInBackgroundInternal(Uri uri, CancellationSignal signal) { final ArrayList result = Lists.newArrayList(); - // TODO: send selection and sorting hints to backend final ContentResolver resolver = getContext().getContentResolver(); - final Cursor cursor = resolver.query(uri, null, null, null, null, signal); + final Cursor cursor = resolver.query(uri, null, null, null, getQuerySortOrder(), signal); try { while (cursor != null && cursor.moveToNext()) { Document doc = null; @@ -94,4 +114,16 @@ public class DirectoryLoader extends UriDerivativeLoader> { return result; } + + private String getQuerySortOrder() { + if (mSortOrder instanceof Document.DateComparator) { + return DocumentColumns.LAST_MODIFIED + " DESC"; + } else if (mSortOrder instanceof Document.NameComparator) { + return DocumentColumns.DISPLAY_NAME + " ASC"; + } else if (mSortOrder instanceof Document.SizeComparator) { + return DocumentColumns.SIZE + " DESC"; + } else { + return null; + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index a536acbc3c8c..89ba66e3d007 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -22,9 +22,11 @@ import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.content.ClipData; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; +import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.drawable.ColorDrawable; import android.net.Uri; @@ -60,6 +62,7 @@ public class DocumentsActivity extends Activity { public static final int ACTION_OPEN = 1; public static final int ACTION_CREATE = 2; + public static final int ACTION_GET_CONTENT = 3; private int mAction; @@ -84,11 +87,15 @@ public class DocumentsActivity extends Activity { final String action = intent.getAction(); if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { mAction = ACTION_OPEN; - mDisplayState.allowMultiple = intent.getBooleanExtra( - Intent.EXTRA_ALLOW_MULTIPLE, false); } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { mAction = ACTION_CREATE; - mDisplayState.allowMultiple = false; + } else if (Intent.ACTION_GET_CONTENT.equals(action)) { + mAction = ACTION_GET_CONTENT; + } + + if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + mDisplayState.allowMultiple = intent.getBooleanExtra( + Intent.EXTRA_ALLOW_MULTIPLE, false); } if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { @@ -97,11 +104,7 @@ public class DocumentsActivity extends Activity { mDisplayState.acceptMimes = new String[] { intent.getType() }; } - if (MimePredicate.mimeMatches("image/*", mDisplayState.acceptMimes)) { - mDisplayState.mode = DisplayState.MODE_GRID; - } else { - mDisplayState.mode = DisplayState.MODE_LIST; - } + mDisplayState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); setResult(Activity.RESULT_CANCELED); setContentView(R.layout.activity); @@ -112,7 +115,14 @@ public class DocumentsActivity extends Activity { SaveFragment.show(getFragmentManager(), mimeType, title); } - RootsFragment.show(getFragmentManager()); + if (mAction == ACTION_GET_CONTENT) { + final Intent moreApps = new Intent(getIntent()); + moreApps.setComponent(null); + moreApps.setPackage(null); + RootsFragment.show(getFragmentManager(), moreApps); + } else { + RootsFragment.show(getFragmentManager(), null); + } mRootsContainer = findViewById(R.id.container_roots); @@ -186,7 +196,7 @@ public class DocumentsActivity extends Activity { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); actionBar.setIcon(new ColorDrawable()); - if (mAction == ACTION_OPEN) { + if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { actionBar.setTitle(R.string.title_open); } else if (mAction == ACTION_CREATE) { actionBar.setTitle(R.string.title_save); @@ -484,12 +494,21 @@ public class DocumentsActivity extends Activity { } } + public void onAppPicked(ResolveInfo info) { + final Intent intent = new Intent(getIntent()); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + intent.setComponent(new ComponentName( + info.activityInfo.applicationInfo.packageName, info.activityInfo.name)); + startActivity(intent); + finish(); + } + public void onDocumentPicked(Document doc) { final FragmentManager fm = getFragmentManager(); if (doc.isDirectory()) { mStack.push(doc); onCurrentDirectoryChanged(); - } else if (mAction == ACTION_OPEN) { + } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { // Explicit file picked, return onFinished(doc.uri); } else if (mAction == ACTION_CREATE) { @@ -538,7 +557,7 @@ public class DocumentsActivity extends Activity { values.put(RecentsProvider.COL_PATH, rawStack); resolver.insert(RecentsProvider.buildRecentCreate(), values); - } else if (mAction == ACTION_OPEN) { + } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { // Remember opened items for (Uri uri : uris) { values.clear(); @@ -565,10 +584,13 @@ public class DocumentsActivity extends Activity { intent.setClipData(clipData); } - // TODO: omit WRITE and PERSIST for GET_CONTENT - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); + if (mAction == ACTION_GET_CONTENT) { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); + } setResult(Activity.RESULT_OK, intent); finish(); @@ -580,6 +602,7 @@ public class DocumentsActivity extends Activity { public int sortOrder = SORT_ORDER_NAME; public boolean allowMultiple = false; public boolean showSize = false; + public boolean localOnly = false; public static final int MODE_LIST = 0; public static final int MODE_GRID = 1; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index 5268c1d25c3d..dbcb0396fdc4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java @@ -129,11 +129,11 @@ public class RecentsProvider extends ContentProvider { switch (sMatcher.match(uri)) { case URI_RECENT_OPEN: { return db.query(TABLE_RECENT_OPEN, projection, - buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, sortOrder); + buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null); } case URI_RECENT_CREATE: { return db.query(TABLE_RECENT_CREATE, projection, - buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, sortOrder); + buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null); } case URI_RESUME: { final String packageName = uri.getPathSegments().get(1); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index b26db3ba4153..ceab8fcb0b11 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -95,19 +95,24 @@ public class RootsCache { sProviders.put(info.providerInfo.authority, info); - // 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() - .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); + // 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() + .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); + } + } finally { + cursor.close(); } - } finally { - cursor.close(); + } catch (Exception e) { + Log.w(TAG, "Failed to load some roots from " + info.providerInfo.authority + + ": " + e); } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 427ad42faccc..e32414b9d55e 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -22,6 +22,9 @@ import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.Bundle; import android.provider.DocumentsContract; import android.text.format.Formatter; @@ -41,6 +44,8 @@ import com.android.documentsui.model.Root; import com.android.documentsui.model.Root.RootComparator; import java.util.Collection; +import java.util.Iterator; +import java.util.List; /** * Display list of known storage backend roots. @@ -50,8 +55,14 @@ public class RootsFragment extends Fragment { private ListView mList; private SectionedRootsAdapter mAdapter; - public static void show(FragmentManager fm) { + private static final String EXTRA_INCLUDE_APPS = "includeApps"; + + public static void show(FragmentManager fm, Intent includeApps) { + final Bundle args = new Bundle(); + args.putParcelable(EXTRA_INCLUDE_APPS, includeApps); + final RootsFragment fragment = new RootsFragment(); + fragment.setArguments(args); final FragmentTransaction ft = fm.beginTransaction(); ft.replace(R.id.container_roots, fragment); @@ -69,11 +80,11 @@ public class RootsFragment extends Fragment { final View view = inflater.inflate(R.layout.fragment_roots, container, false); mList = (ListView) view.findViewById(android.R.id.list); - - mAdapter = new SectionedRootsAdapter(context, RootsCache.getRoots(context)); - mList.setAdapter(mAdapter); mList.setOnItemClickListener(mItemListener); + final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS); + mAdapter = new SectionedRootsAdapter(context, RootsCache.getRoots(context), includeApps); + return view; } @@ -82,18 +93,26 @@ public class RootsFragment extends Fragment { super.onStart(); final Context context = getActivity(); - mAdapter.setShowAdvanced(SettingsActivity.getDisplayAdvancedDevices(context)); + mAdapter.updateVisible(SettingsActivity.getDisplayAdvancedDevices(context)); + mList.setAdapter(mAdapter); } private OnItemClickListener mItemListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - final Root root = (Root) mAdapter.getItem(position); - ((DocumentsActivity) getActivity()).onRootPicked(root, true); + final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this); + final Object item = mAdapter.getItem(position); + if (item instanceof Root) { + activity.onRootPicked((Root) item, true); + } else if (item instanceof ResolveInfo) { + activity.onAppPicked((ResolveInfo) item); + } else { + throw new IllegalStateException("Unknown root: " + item); + } } }; - public static class RootsAdapter extends ArrayAdapter implements SectionAdapter { + private static class RootsAdapter extends ArrayAdapter implements SectionAdapter { private int mHeaderId; public RootsAdapter(Context context, int headerId) { @@ -148,17 +167,61 @@ public class RootsFragment extends Fragment { } } - public static class SectionedRootsAdapter extends SectionedListAdapter { + private static class AppsAdapter extends ArrayAdapter implements SectionAdapter { + public AppsAdapter(Context context) { + super(context, 0); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final Context context = parent.getContext(); + final PackageManager pm = context.getPackageManager(); + if (convertView == null) { + convertView = LayoutInflater.from(context) + .inflate(R.layout.item_root, parent, false); + } + + final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); + final TextView title = (TextView) convertView.findViewById(android.R.id.title); + final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); + + final ResolveInfo info = getItem(position); + icon.setImageDrawable(info.loadIcon(pm)); + title.setText(info.loadLabel(pm)); + + // TODO: match existing summary behavior from disambig dialog + summary.setVisibility(View.GONE); + + return convertView; + } + + @Override + public View getHeaderView(View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_root_header, parent, false); + } + + final TextView title = (TextView) convertView.findViewById(android.R.id.title); + title.setText(R.string.root_type_apps); + + return convertView; + } + } + + private static class SectionedRootsAdapter extends SectionedListAdapter { private final RootsAdapter mServices; private final RootsAdapter mShortcuts; private final RootsAdapter mDevices; private final RootsAdapter mDevicesAdvanced; + private final AppsAdapter mApps; - public SectionedRootsAdapter(Context context, Collection roots) { + public SectionedRootsAdapter(Context context, Collection roots, Intent includeApps) { mServices = new RootsAdapter(context, R.string.root_type_service); mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut); mDevices = new RootsAdapter(context, R.string.root_type_device); mDevicesAdvanced = new RootsAdapter(context, R.string.root_type_device); + mApps = new AppsAdapter(context); for (Root root : roots) { Log.d(TAG, "Found rootType=" + root.rootType); @@ -179,6 +242,19 @@ public class RootsFragment extends Fragment { } } + if (includeApps != null) { + final PackageManager pm = context.getPackageManager(); + final List infos = pm.queryIntentActivities( + includeApps, PackageManager.MATCH_DEFAULT_ONLY); + + // Omit ourselves from the list + for (ResolveInfo info : infos) { + if (!context.getPackageName().equals(info.activityInfo.packageName)) { + mApps.add(info); + } + } + } + final RootComparator comp = new RootComparator(); mServices.sort(comp); mShortcuts.sort(comp); @@ -186,7 +262,7 @@ public class RootsFragment extends Fragment { mDevicesAdvanced.sort(comp); } - public void setShowAdvanced(boolean showAdvanced) { + public void updateVisible(boolean showAdvanced) { clearSections(); if (mServices.getCount() > 0) { addSection(mServices); @@ -199,6 +275,10 @@ public class RootsFragment extends Fragment { if (devices.getCount() > 0) { addSection(devices); } + + if (mApps.getCount() > 0) { + addSection(mApps); + } } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SectionedListAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/SectionedListAdapter.java index aacce65cd02e..088e3fa70d43 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SectionedListAdapter.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SectionedListAdapter.java @@ -18,6 +18,7 @@ package com.android.documentsui; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ListAdapter; @@ -41,6 +42,11 @@ public class SectionedListAdapter extends BaseAdapter { notifyDataSetChanged(); } + /** + * After mutating sections, you must + * {@link AdapterView#setAdapter(android.widget.Adapter)} to correctly + * recount view types. + */ public void addSection(SectionAdapter adapter) { mSections.add(adapter); notifyDataSetChanged(); @@ -117,7 +123,7 @@ public class SectionedListAdapter extends BaseAdapter { if (position == 0) { return false; } else if (position < sectionSize) { - return section.isEnabled(position); + return section.isEnabled(position - 1); } // Otherwise jump into next section diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java index a086a438e44d..f6548e860fc5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java @@ -60,6 +60,7 @@ public class TestActivity extends Activity { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); if (multiple.isChecked()) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); @@ -75,6 +76,7 @@ public class TestActivity extends Activity { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); if (multiple.isChecked()) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); @@ -90,6 +92,7 @@ public class TestActivity extends Activity { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] { "text/plain", "application/msword" }); @@ -107,6 +110,7 @@ public class TestActivity extends Activity { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TITLE, "foobar.txt"); startActivityForResult(intent, 42); @@ -114,6 +118,22 @@ public class TestActivity extends Activity { }); view.addView(button); + button = new Button(context); + button.setText("GET_CONTENT */*"); + button.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + if (multiple.isChecked()) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } + startActivityForResult(Intent.createChooser(intent, "Kittens!"), 42); + } + }); + view.addView(button); + mResult = new TextView(context); view.addView(mResult); @@ -131,7 +151,7 @@ public class TestActivity extends Activity { is = getContentResolver().openInputStream(uri); final int length = Streams.readFullyNoClose(is).length; Log.d(TAG, "read length=" + length); - } catch (IOException e) { + } catch (Exception e) { Log.w(TAG, "Failed to read " + uri, e); } finally { IoUtils.closeQuietly(is); -- 2.11.0