diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-08-27 18:26:48 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2013-08-28 20:49:42 -0700 |
commit | aeb16e2435f9975b9fa1fc4b747796647a21292e (patch) | |
tree | 2e228b0ca25799bf59472d5df08d71e068498b2b /packages/DocumentsUI | |
parent | 61f0f9ada51a6718075acfe7fafbb9954f38e980 (diff) | |
download | frameworks_base-aeb16e2435f9975b9fa1fc4b747796647a21292e.zip frameworks_base-aeb16e2435f9975b9fa1fc4b747796647a21292e.tar.gz frameworks_base-aeb16e2435f9975b9fa1fc4b747796647a21292e.tar.bz2 |
Stronger DocumentsProvider contract.
Using a contract class requires that a provider implement it exactly
with little help. This change introduces a DocumentsProvider abstract
class that provides a client-side implementation of the contract that
greatly reduces developer burden, and improves correctness.
This also moves to first-class DocumentRoot objects, and moves calls
with complex side effects to be ContentProvider.call() invocations,
offering more granular permission control over Uri operations that
shouldn't be available through Uri grants.
This new design also relaxes the requirement that root information be
burned into every Uri. Migrate ExternalDocumentsProvider and
DocumentsUI to adopt new API.
Bug: 10497206
Change-Id: I6f2b3f519bfd62a9d693223ea5628a971ce2e743
Diffstat (limited to 'packages/DocumentsUI')
14 files changed, 172 insertions, 463 deletions
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml index d79f5c6..6cc92e3 100644 --- a/packages/DocumentsUI/AndroidManifest.xml +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -35,9 +35,9 @@ </intent-filter> <!-- data expected to point at existing root to manage --> <intent-filter> - <action android:name="android.intent.action.MANAGE_DOCUMENT" /> + <action android:name="android.provider.action.MANAGE_DOCUMENTS" /> <category android:name="android.intent.category.DEFAULT" /> - <data android:mimeType="vnd.android.cursor.item/root" /> + <data android:mimeType="vnd.android.doc/dir" /> </intent-filter> </activity> diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java index 575947f..6bc554f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java @@ -20,14 +20,14 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.FragmentManager; +import android.content.ContentProviderClient; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.net.Uri; import android.os.Bundle; -import android.provider.DocumentsContract.DocumentColumns; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Documents; import android.view.LayoutInflater; import android.view.View; @@ -36,8 +36,6 @@ import android.widget.Toast; import com.android.documentsui.model.Document; -import java.io.FileNotFoundException; - /** * Dialog to create a new directory. */ @@ -58,7 +56,7 @@ public class CreateDirectoryFragment extends DialogFragment { final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); final View view = dialogInflater.inflate(R.layout.dialog_create_dir, null, false); - final EditText text1 = (EditText)view.findViewById(android.R.id.text1); + final EditText text1 = (EditText) view.findViewById(android.R.id.text1); builder.setTitle(R.string.menu_create_dir); builder.setView(view); @@ -68,24 +66,25 @@ public class CreateDirectoryFragment extends DialogFragment { public void onClick(DialogInterface dialog, int which) { final String displayName = text1.getText().toString(); - final ContentValues values = new ContentValues(); - values.put(DocumentColumns.MIME_TYPE, Documents.MIME_TYPE_DIR); - values.put(DocumentColumns.DISPLAY_NAME, displayName); - final DocumentsActivity activity = (DocumentsActivity) getActivity(); final Document cwd = activity.getCurrentDirectory(); - Uri childUri = resolver.insert(cwd.uri, values); + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + cwd.uri.getAuthority()); try { + final String docId = DocumentsContract.createDocument(client, + DocumentsContract.getDocId(cwd.uri), Documents.MIME_TYPE_DIR, + displayName); + // Navigate into newly created child + final Uri childUri = DocumentsContract.buildDocumentUri( + cwd.uri.getAuthority(), docId); final Document childDoc = Document.fromUri(resolver, childUri); activity.onDocumentPicked(childDoc); - } catch (FileNotFoundException e) { - childUri = null; - } - - if (childUri == null) { + } catch (Exception e) { Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show(); + } finally { + ContentProviderClient.closeQuietly(client); } } }); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index dd9aee5..783b6ff 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -20,8 +20,8 @@ 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_DISPLAY_NAME; +import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED; import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_SIZE; import android.app.Fragment; @@ -32,7 +32,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.Loader; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Point; import android.net.Uri; @@ -55,7 +54,6 @@ import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; -import android.widget.Button; import android.widget.GridView; import android.widget.ImageView; import android.widget.ListView; @@ -64,7 +62,6 @@ import android.widget.Toast; import com.android.documentsui.DocumentsActivity.DisplayState; import com.android.documentsui.model.Document; -import com.android.documentsui.model.Root; import com.android.internal.util.Predicate; import com.google.android.collect.Lists; @@ -81,7 +78,6 @@ public class DirectoryFragment extends Fragment { private View mEmptyView; private ListView mListView; private GridView mGridView; - private Button mMoreView; private AbsListView mCurrentView; @@ -110,7 +106,8 @@ public class DirectoryFragment extends Fragment { } public static void showSearch(FragmentManager fm, Uri uri, String query) { - final Uri searchUri = DocumentsContract.buildSearchUri(uri, query); + final Uri searchUri = DocumentsContract.buildSearchUri( + uri.getAuthority(), DocumentsContract.getDocId(uri), query); show(fm, TYPE_SEARCH, searchUri); } @@ -153,8 +150,6 @@ public class DirectoryFragment extends Fragment { mGridView.setOnItemClickListener(mItemListener); mGridView.setMultiChoiceModeListener(mMultiListener); - mMoreView = (Button) view.findViewById(R.id.more); - mAdapter = new DocumentsAdapter(); final Uri uri = getArguments().getParcelable(EXTRA_URI); @@ -168,22 +163,19 @@ public class DirectoryFragment extends Fragment { Uri contentsUri; if (mType == TYPE_NORMAL) { - contentsUri = DocumentsContract.buildContentsUri(uri); + contentsUri = DocumentsContract.buildChildrenUri( + uri.getAuthority(), DocumentsContract.getDocId(uri)); } else if (mType == TYPE_RECENT_OPEN) { contentsUri = RecentsProvider.buildRecentOpen(); } else { contentsUri = uri; } - if (state.localOnly) { - contentsUri = DocumentsContract.setLocalOnly(contentsUri); - } - final Comparator<Document> sortOrder; - if (state.sortOrder == SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN) { - sortOrder = new Document.DateComparator(); - } else if (state.sortOrder == SORT_ORDER_NAME) { - sortOrder = new Document.NameComparator(); + if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) { + sortOrder = new Document.LastModifiedComparator(); + } else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) { + sortOrder = new Document.DisplayNameComparator(); } else if (state.sortOrder == SORT_ORDER_SIZE) { sortOrder = new Document.SizeComparator(); } else { @@ -196,28 +188,6 @@ public class DirectoryFragment extends Fragment { @Override public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) { mAdapter.swapDocuments(result.contents); - - final Cursor cursor = result.cursor; - if (cursor != null && cursor.getExtras() - .getBoolean(DocumentsContract.EXTRA_HAS_MORE, false)) { - mMoreView.setText(R.string.more); - mMoreView.setVisibility(View.VISIBLE); - mMoreView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mMoreView.setText(R.string.loading); - final Bundle bundle = new Bundle(); - bundle.putBoolean(DocumentsContract.EXTRA_REQUEST_MORE, true); - try { - cursor.respond(bundle); - } catch (Exception e) { - Log.w(TAG, "Failed to respond: " + e); - } - } - }); - } else { - mMoreView.setVisibility(View.GONE); - } } @Override @@ -489,8 +459,7 @@ public class DirectoryFragment extends Fragment { task.execute(doc.uri); } } else { - icon.setImageDrawable(roots.resolveDocumentIcon( - context, doc.uri.getAuthority(), doc.mimeType)); + icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, doc.mimeType)); } title.setText(doc.displayName); @@ -504,11 +473,7 @@ public class DirectoryFragment extends Fragment { summary.setVisibility(View.INVISIBLE); } } else if (mType == TYPE_RECENT_OPEN) { - final Root root = roots.findRoot(doc); - icon1.setVisibility(View.VISIBLE); - icon1.setImageDrawable(root.icon); - summary.setText(root.getDirectoryString()); - summary.setVisibility(View.VISIBLE); + // TODO: resolve storage root } if (summaryGrid != null) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 14d6fd5..4ce5ef8 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -26,7 +26,6 @@ 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; @@ -77,9 +76,10 @@ public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> { } private void loadInBackgroundInternal( - DirectoryResult result, Uri uri, CancellationSignal signal) { + DirectoryResult result, Uri uri, CancellationSignal signal) throws RuntimeException { + // TODO: switch to using unstable CPC final ContentResolver resolver = getContext().getContentResolver(); - final Cursor cursor = resolver.query(uri, null, null, null, getQuerySortOrder(), signal); + final Cursor cursor = resolver.query(uri, null, null, null, null, signal); result.cursor = cursor; result.cursor.registerContentObserver(mObserver); @@ -110,16 +110,4 @@ public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> { Collections.sort(result.contents, mSortOrder); } } - - 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/DocumentChangedReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java index 72afd9e..0ce5968 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java @@ -21,12 +21,11 @@ import static com.android.documentsui.DocumentsActivity.TAG; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.provider.DocumentsContract.DocumentRoot; import android.util.Log; -import com.android.documentsui.model.Root; - /** - * Handles {@link Root} changes which invalidate cached data. + * Handles {@link DocumentRoot} changes which invalidate cached data. */ public class DocumentChangedReceiver extends BroadcastReceiver { @Override diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 22e3b98..73ca8fa 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -22,7 +22,7 @@ import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANA 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 static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED; import android.app.ActionBar; import android.app.ActionBar.OnNavigationListener; @@ -32,6 +32,7 @@ import android.app.FragmentManager; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ComponentName; +import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; @@ -41,7 +42,7 @@ import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; import android.provider.DocumentsContract; -import android.provider.DocumentsContract.DocumentColumns; +import android.provider.DocumentsContract.DocumentRoot; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; @@ -61,7 +62,6 @@ import android.widget.Toast; import com.android.documentsui.model.Document; import com.android.documentsui.model.DocumentStack; -import com.android.documentsui.model.Root; import java.io.FileNotFoundException; import java.util.Arrays; @@ -101,7 +101,7 @@ public class DocumentsActivity extends Activity { mAction = ACTION_CREATE; } else if (Intent.ACTION_GET_CONTENT.equals(action)) { mAction = ACTION_GET_CONTENT; - } else if (Intent.ACTION_MANAGE_DOCUMENT.equals(action)) { + } else if (DocumentsContract.ACTION_MANAGE_DOCUMENTS.equals(action)) { mAction = ACTION_MANAGE; } @@ -143,7 +143,7 @@ public class DocumentsActivity extends Activity { } if (mAction == ACTION_MANAGE) { - mDisplayState.sortOrder = SORT_ORDER_DATE; + mDisplayState.sortOrder = SORT_ORDER_LAST_MODIFIED; } mRootsContainer = findViewById(R.id.container_roots); @@ -160,10 +160,7 @@ public class DocumentsActivity extends Activity { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); final Uri rootUri = intent.getData(); - final String authority = rootUri.getAuthority(); - final String rootId = DocumentsContract.getRootId(rootUri); - - final Root root = mRoots.findRoot(authority, rootId); + final DocumentRoot root = mRoots.findRoot(rootUri); if (root != null) { onRootPicked(root, true); } else { @@ -255,10 +252,10 @@ public class DocumentsActivity extends Activity { mDrawerToggle.setDrawerIndicatorEnabled(true); } else { - final Root root = getCurrentRoot(); - actionBar.setIcon(root != null ? root.icon : null); + final DocumentRoot root = getCurrentRoot(); + actionBar.setIcon(root != null ? root.loadIcon(this) : null); - if (root.isRecents) { + if (mRoots.isRecentsRoot(root)) { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); actionBar.setTitle(root.title); } else { @@ -441,9 +438,8 @@ public class DocumentsActivity extends Activity { final TextView title = (TextView) convertView.findViewById(android.R.id.title); final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); - final Document cwd = getCurrentDirectory(); - if (cwd != null) { - title.setText(cwd.displayName); + if (mStack.size() > 0) { + title.setText(mStack.getTitle(mRoots)); } else { // No directory means recents title.setText(R.string.root_recent); @@ -477,10 +473,9 @@ public class DocumentsActivity extends Activity { } }; - public Root getCurrentRoot() { - final Document cwd = getCurrentDirectory(); - if (cwd != null) { - return mRoots.findRoot(cwd); + public DocumentRoot getCurrentRoot() { + if (mStack.size() > 0) { + return mStack.getRoot(mRoots); } else { return mRoots.getRecentsRoot(); } @@ -538,13 +533,14 @@ public class DocumentsActivity extends Activity { onCurrentDirectoryChanged(); } - public void onRootPicked(Root root, boolean closeDrawer) { + public void onRootPicked(DocumentRoot root, boolean closeDrawer) { // Clear entire backstack and start in new root mStack.clear(); - if (!root.isRecents) { + if (!mRoots.isRecentsRoot(root)) { try { - onDocumentPicked(Document.fromRoot(getContentResolver(), root)); + final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.docId); + onDocumentPicked(Document.fromUri(getContentResolver(), uri)); } catch (FileNotFoundException e) { } } else { @@ -611,16 +607,21 @@ public class DocumentsActivity extends Activity { } public void onSaveRequested(String mimeType, String displayName) { - final ContentValues values = new ContentValues(); - values.put(DocumentColumns.MIME_TYPE, mimeType); - values.put(DocumentColumns.DISPLAY_NAME, displayName); - final Document cwd = getCurrentDirectory(); - final Uri childUri = getContentResolver().insert(cwd.uri, values); - if (childUri != null) { + final String authority = cwd.uri.getAuthority(); + + final ContentProviderClient client = getContentResolver() + .acquireUnstableContentProviderClient(authority); + try { + final String docId = DocumentsContract.createDocument(client, + DocumentsContract.getDocId(cwd.uri), mimeType, displayName); + + final Uri childUri = DocumentsContract.buildDocumentUri(authority, docId); onFinished(childUri); - } else { + } catch (Exception e) { Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show(); + } finally { + ContentProviderClient.closeQuietly(client); } } @@ -680,7 +681,7 @@ public class DocumentsActivity extends Activity { public int action; public int mode = MODE_LIST; public String[] acceptMimes; - public int sortOrder = SORT_ORDER_NAME; + public int sortOrder = SORT_ORDER_DISPLAY_NAME; public boolean allowMultiple = false; public boolean showSize = false; public boolean localOnly = false; @@ -693,8 +694,8 @@ public class DocumentsActivity extends Activity { public static final int MODE_LIST = 0; public static final int MODE_GRID = 1; - public static final int SORT_ORDER_NAME = 0; - public static final int SORT_ORDER_DATE = 1; + public static final int SORT_ORDER_DISPLAY_NAME = 0; + public static final int SORT_ORDER_LAST_MODIFIED = 1; public static final int SORT_ORDER_SIZE = 2; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index 5466dbf..3447a51 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -29,6 +29,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; +import android.provider.DocumentsContract.DocumentRoot; import android.text.TextUtils.TruncateAt; import android.util.Log; import android.view.LayoutInflater; @@ -42,7 +43,6 @@ import android.widget.ListView; import android.widget.TextView; import com.android.documentsui.model.DocumentStack; -import com.android.documentsui.model.Root; import com.google.android.collect.Lists; import libcore.io.IoUtils; @@ -181,8 +181,8 @@ public class RecentsCreateFragment extends Fragment { final View summaryList = convertView.findViewById(R.id.summary_list); final DocumentStack stack = getItem(position); - final Root root = roots.findRoot(stack.peek()); - icon.setImageDrawable(root != null ? root.icon : null); + final DocumentRoot root = stack.getRoot(roots); + icon.setImageDrawable(root.loadIcon(context)); final StringBuilder builder = new StringBuilder(); for (int i = stack.size() - 1; i >= 0; i--) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index c3b498e..aa21457 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -18,30 +18,24 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; +import android.content.ContentProviderClient; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; -import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.DocumentsContract; +import android.provider.DocumentsContract.DocumentRoot; import android.provider.DocumentsContract.Documents; import android.util.Log; -import android.util.Pair; -import com.android.documentsui.model.Document; -import com.android.documentsui.model.DocumentsProviderInfo; -import com.android.documentsui.model.DocumentsProviderInfo.Icon; -import com.android.documentsui.model.Root; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Objects; import com.google.android.collect.Lists; -import com.google.android.collect.Maps; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.List; /** @@ -54,14 +48,9 @@ public class RootsCache { private final Context mContext; - /** Map from authority to cached info */ - private HashMap<String, DocumentsProviderInfo> mProviders = Maps.newHashMap(); - /** Map from (authority+rootId) to cached info */ - private HashMap<Pair<String, String>, Root> mRoots = Maps.newHashMap(); + public List<DocumentRoot> mRoots = Lists.newArrayList(); - public ArrayList<Root> mRootsList = Lists.newArrayList(); - - private Root mRecentsRoot; + private DocumentRoot mRecentsRoot; public RootsCache(Context context) { mContext = context; @@ -73,95 +62,78 @@ public class RootsCache { */ @GuardedBy("ActivityThread") public void update() { - mProviders.clear(); mRoots.clear(); - mRootsList.clear(); { // Create special root for recents - final Root root = Root.buildRecents(mContext); - mRootsList.add(root); + final DocumentRoot root = new DocumentRoot(); + root.rootType = DocumentRoot.ROOT_TYPE_SHORTCUT; + root.docId = null; + root.icon = R.drawable.ic_dir; + root.title = mContext.getString(R.string.root_recent); + root.summary = null; + root.availableBytes = -1; + + mRoots.add(root); mRecentsRoot = root; } // Query for other storage backends + final ContentResolver resolver = mContext.getContentResolver(); 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( + for (ProviderInfo info : providers) { + if (info.metaData != null && info.metaData.containsKey( DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) { - final DocumentsProviderInfo info = DocumentsProviderInfo.parseInfo( - mContext, providerInfo); - if (info == null) { - Log.w(TAG, "Missing info for " + providerInfo); - continue; - } - - mProviders.put(info.providerInfo.authority, info); + // TODO: remove deprecated customRoots flag + // TODO: populate roots on background thread, and cache results + final ContentProviderClient client = resolver + .acquireUnstableContentProviderClient(info.authority); 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 = mContext.getContentResolver() - .query(uri, null, null, null, null); - try { - while (cursor.moveToNext()) { - final Root root = Root.fromCursor(mContext, info, cursor); - mRoots.put(Pair.create(info.providerInfo.authority, root.rootId), root); - mRootsList.add(root); - } - } finally { - cursor.close(); + final List<DocumentRoot> roots = DocumentsContract.getDocumentRoots(client); + for (DocumentRoot root : roots) { + root.authority = info.authority; } + mRoots.addAll(roots); } catch (Exception e) { - Log.w(TAG, "Failed to load some roots from " + info.providerInfo.authority - + ": " + e); + Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e); + } finally { + ContentProviderClient.closeQuietly(client); } } } } - @GuardedBy("ActivityThread") - public DocumentsProviderInfo findProvider(String authority) { - return mProviders.get(authority); - } - - @GuardedBy("ActivityThread") - public Root findRoot(String authority, String rootId) { - return mRoots.get(Pair.create(authority, rootId)); + public DocumentRoot findRoot(Uri uri) { + final String authority = uri.getAuthority(); + final String docId = DocumentsContract.getDocId(uri); + for (DocumentRoot root : mRoots) { + if (Objects.equal(root.authority, authority) && Objects.equal(root.docId, docId)) { + return root; + } + } + return null; } @GuardedBy("ActivityThread") - public Root findRoot(Document doc) { - final String authority = doc.uri.getAuthority(); - final String rootId = DocumentsContract.getRootId(doc.uri); - return findRoot(authority, rootId); + public DocumentRoot getRecentsRoot() { + return mRecentsRoot; } @GuardedBy("ActivityThread") - public Root getRecentsRoot() { - return mRecentsRoot; + public boolean isRecentsRoot(DocumentRoot root) { + return mRecentsRoot == root; } @GuardedBy("ActivityThread") - public Collection<Root> getRoots() { - return mRootsList; + public List<DocumentRoot> getRoots() { + return mRoots; } @GuardedBy("ActivityThread") - public Drawable resolveDocumentIcon(Context context, String authority, String mimeType) { - // Custom icons take precedence - final DocumentsProviderInfo info = mProviders.get(authority); - if (info != null) { - for (Icon icon : info.customIcons) { - if (MimePredicate.mimeMatches(icon.mimeType, mimeType)) { - return icon.icon; - } - } - } - + public static Drawable resolveDocumentIcon(Context context, String mimeType) { if (Documents.MIME_TYPE_DIR.equals(mimeType)) { return context.getResources().getDrawable(R.drawable.ic_dir); } else { diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 8a48e2a..2cfa841 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -26,7 +26,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; -import android.provider.DocumentsContract.Roots; +import android.provider.DocumentsContract.DocumentRoot; import android.text.format.Formatter; import android.util.Log; import android.view.LayoutInflater; @@ -40,10 +40,9 @@ import android.widget.ListView; import android.widget.TextView; import com.android.documentsui.SectionedListAdapter.SectionAdapter; -import com.android.documentsui.model.Root; -import com.android.documentsui.model.Root.RootComparator; +import com.android.documentsui.model.Document; -import java.util.Collection; +import java.util.Comparator; import java.util.List; /** @@ -102,8 +101,8 @@ public class RootsFragment extends Fragment { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this); final Object item = mAdapter.getItem(position); - if (item instanceof Root) { - activity.onRootPicked((Root) item, true); + if (item instanceof DocumentRoot) { + activity.onRootPicked((DocumentRoot) item, true); } else if (item instanceof ResolveInfo) { activity.onAppPicked((ResolveInfo) item); } else { @@ -112,7 +111,7 @@ public class RootsFragment extends Fragment { } }; - private static class RootsAdapter extends ArrayAdapter<Root> implements SectionAdapter { + private static class RootsAdapter extends ArrayAdapter<DocumentRoot> implements SectionAdapter { private int mHeaderId; public RootsAdapter(Context context, int headerId) { @@ -132,14 +131,14 @@ public class RootsFragment extends Fragment { final TextView title = (TextView) convertView.findViewById(android.R.id.title); final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); - final Root root = getItem(position); - icon.setImageDrawable(root.icon); + final DocumentRoot root = getItem(position); + icon.setImageDrawable(root.loadIcon(context)); title.setText(root.title); // Device summary is always available space final String summaryText; - if ((root.rootType == Roots.ROOT_TYPE_DEVICE - || root.rootType == Roots.ROOT_TYPE_DEVICE_ADVANCED) + if ((root.rootType == DocumentRoot.ROOT_TYPE_DEVICE + || root.rootType == DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED) && root.availableBytes >= 0) { summaryText = context.getString(R.string.root_available_bytes, Formatter.formatFileSize(context, root.availableBytes)); @@ -216,27 +215,27 @@ public class RootsFragment extends Fragment { private final RootsAdapter mDevicesAdvanced; private final AppsAdapter mApps; - public SectionedRootsAdapter(Context context, Collection<Root> roots, Intent includeApps) { + public SectionedRootsAdapter(Context context, List<DocumentRoot> 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) { + for (DocumentRoot root : roots) { Log.d(TAG, "Found rootType=" + root.rootType); switch (root.rootType) { - case Roots.ROOT_TYPE_SERVICE: + case DocumentRoot.ROOT_TYPE_SERVICE: mServices.add(root); break; - case Roots.ROOT_TYPE_SHORTCUT: + case DocumentRoot.ROOT_TYPE_SHORTCUT: mShortcuts.add(root); break; - case Roots.ROOT_TYPE_DEVICE: + case DocumentRoot.ROOT_TYPE_DEVICE: mDevices.add(root); mDevicesAdvanced.add(root); break; - case Roots.ROOT_TYPE_DEVICE_ADVANCED: + case DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED: mDevicesAdvanced.add(root); break; } @@ -281,4 +280,16 @@ public class RootsFragment extends Fragment { } } } + + public static class RootComparator implements Comparator<DocumentRoot> { + @Override + public int compare(DocumentRoot lhs, DocumentRoot rhs) { + final int score = Document.compareToIgnoreCaseNullable(lhs.title, rhs.title); + if (score != 0) { + return score; + } else { + return Document.compareToIgnoreCaseNullable(lhs.summary, rhs.summary); + } + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java index 8eb81b8..7e1a297 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java @@ -73,8 +73,8 @@ public class SaveFragment extends Fragment { final View view = inflater.inflate(R.layout.fragment_save, container, false); final ImageView icon = (ImageView) view.findViewById(android.R.id.icon); - icon.setImageDrawable(roots.resolveDocumentIcon( - context, null, getArguments().getString(EXTRA_MIME_TYPE))); + icon.setImageDrawable( + RootsCache.resolveDocumentIcon(context, getArguments().getString(EXTRA_MIME_TYPE))); mDisplayName = (EditText) view.findViewById(android.R.id.title); mDisplayName.addTextChangedListener(mDisplayNameWatcher); diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java index c0f21cb..692d171 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java @@ -53,17 +53,11 @@ public class Document { this.size = size; } - public static Document fromRoot(ContentResolver resolver, Root root) - throws FileNotFoundException { - return fromUri(resolver, root.uri); - } - public static Document fromDirectoryCursor(Uri parent, Cursor cursor) { final String authority = parent.getAuthority(); - final String rootId = DocumentsContract.getRootId(parent); final String docId = getCursorString(cursor, DocumentColumns.DOC_ID); - final Uri uri = DocumentsContract.buildDocumentUri(authority, rootId, docId); + final Uri uri = DocumentsContract.buildDocumentUri(authority, docId); final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); @@ -74,6 +68,7 @@ public class Document { return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); } + @Deprecated public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor) throws FileNotFoundException { final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI)); @@ -176,7 +171,7 @@ public class Document { return (index != -1) ? cursor.getInt(index) : 0; } - public static class NameComparator implements Comparator<Document> { + public static class DisplayNameComparator implements Comparator<Document> { @Override public int compare(Document lhs, Document rhs) { final boolean leftDir = lhs.isDirectory(); @@ -185,12 +180,12 @@ public class Document { if (leftDir != rightDir) { return leftDir ? -1 : 1; } else { - return Root.compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName); + return compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName); } } } - public static class DateComparator implements Comparator<Document> { + public static class LastModifiedComparator implements Comparator<Document> { @Override public int compare(Document lhs, Document rhs) { return Long.compare(rhs.lastModified, lhs.lastModified); @@ -213,4 +208,10 @@ public class Document { fnfe.initCause(t); throw fnfe; } + + public static int compareToIgnoreCaseNullable(String lhs, String rhs) { + if (lhs == null) return -1; + if (rhs == null) return 1; + return lhs.compareToIgnoreCase(rhs); + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java index d6c852e..81f75d2 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java @@ -21,8 +21,11 @@ import static com.android.documentsui.model.Document.asFileNotFoundException; import android.content.ContentResolver; import android.net.Uri; +import android.provider.DocumentsContract.DocumentRoot; import android.util.Log; +import com.android.documentsui.RootsCache; + import org.json.JSONArray; import org.json.JSONException; @@ -62,4 +65,18 @@ public class DocumentStack extends LinkedList<Document> { // TODO: handle roots that have gone missing return stack; } + + public DocumentRoot getRoot(RootsCache roots) { + return roots.findRoot(getLast().uri); + } + + public String getTitle(RootsCache roots) { + if (size() == 1) { + return getRoot(roots).title; + } else if (size() > 1) { + return peek().displayName; + } else { + return null; + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java deleted file mode 100644 index 96eb58e..0000000 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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.model; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ProviderInfo; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; -import android.provider.DocumentsContract; -import android.util.AttributeSet; -import android.util.Log; -import android.util.Xml; - -import com.android.documentsui.DocumentsActivity; -import com.google.android.collect.Lists; - -import libcore.io.IoUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.List; - -/** - * Representation of a storage backend. - */ -public class DocumentsProviderInfo { - private static final String TAG = DocumentsActivity.TAG; - - public ProviderInfo providerInfo; - public boolean customRoots; - public List<Icon> customIcons; - - public static class Icon { - public String mimeType; - public Drawable icon; - } - - private static final String TAG_DOCUMENTS_PROVIDER = "documents-provider"; - private static final String TAG_ICON = "icon"; - - public static DocumentsProviderInfo buildRecents(Context context, ProviderInfo providerInfo) { - final DocumentsProviderInfo info = new DocumentsProviderInfo(); - info.providerInfo = providerInfo; - info.customRoots = false; - return info; - } - - public static DocumentsProviderInfo parseInfo(Context context, ProviderInfo providerInfo) { - final DocumentsProviderInfo info = new DocumentsProviderInfo(); - info.providerInfo = providerInfo; - info.customIcons = Lists.newArrayList(); - - final PackageManager pm = context.getPackageManager(); - final Resources res; - try { - res = pm.getResourcesForApplication(providerInfo.applicationInfo); - } catch (NameNotFoundException e) { - Log.w(TAG, "Failed to find resources for " + providerInfo, e); - return null; - } - - XmlResourceParser parser = null; - try { - parser = providerInfo.loadXmlMetaData( - pm, DocumentsContract.META_DATA_DOCUMENT_PROVIDER); - AttributeSet attrs = Xml.asAttributeSet(parser); - - int type = 0; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { - final String tag = parser.getName(); - if (type == XmlPullParser.START_TAG && TAG_DOCUMENTS_PROVIDER.equals(tag)) { - final TypedArray a = res.obtainAttributes( - attrs, com.android.internal.R.styleable.DocumentsProviderInfo); - info.customRoots = a.getBoolean( - com.android.internal.R.styleable.DocumentsProviderInfo_customRoots, - false); - a.recycle(); - - } else if (type == XmlPullParser.START_TAG && TAG_ICON.equals(tag)) { - final TypedArray a = res.obtainAttributes( - attrs, com.android.internal.R.styleable.Icon); - final Icon icon = new Icon(); - icon.mimeType = a.getString(com.android.internal.R.styleable.Icon_mimeType); - icon.icon = a.getDrawable(com.android.internal.R.styleable.Icon_icon); - info.customIcons.add(icon); - a.recycle(); - } - } - } catch (IOException e) { - Log.w(TAG, "Failed to parse metadata", e); - return null; - } catch (XmlPullParserException e) { - Log.w(TAG, "Failed to parse metadata", e); - return null; - } finally { - IoUtils.closeQuietly(parser); - } - - return info; - } -} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Root.java b/packages/DocumentsUI/src/com/android/documentsui/model/Root.java deleted file mode 100644 index 23d16df..0000000 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Root.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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.model; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources.NotFoundException; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.provider.DocumentsContract; -import android.provider.DocumentsContract.Documents; -import android.provider.DocumentsContract.RootColumns; -import android.provider.DocumentsContract.Roots; - -import com.android.documentsui.R; - -import java.util.Comparator; - -/** - * Representation of a root under a storage backend. - */ -public class Root { - public String rootId; - public int rootType; - public Uri uri; - public Drawable icon; - public String title; - public String summary; - public long availableBytes = -1; - public boolean isRecents; - - public static Root buildRecents(Context context) { - final PackageManager pm = context.getPackageManager(); - final Root root = new Root(); - root.rootId = null; - root.rootType = Roots.ROOT_TYPE_SHORTCUT; - root.uri = null; - root.icon = context.getResources().getDrawable(R.drawable.ic_dir); - root.title = context.getString(R.string.root_recent); - root.summary = null; - root.availableBytes = -1; - root.isRecents = true; - return root; - } - - public static Root fromCursor( - Context context, DocumentsProviderInfo info, Cursor cursor) { - final PackageManager pm = context.getPackageManager(); - - final Root root = new Root(); - root.rootId = cursor.getString(cursor.getColumnIndex(RootColumns.ROOT_ID)); - root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE)); - root.uri = DocumentsContract.buildDocumentUri( - info.providerInfo.authority, root.rootId, Documents.DOC_ID_ROOT); - root.icon = info.providerInfo.loadIcon(pm); - root.title = info.providerInfo.loadLabel(pm).toString(); - root.availableBytes = cursor.getLong(cursor.getColumnIndex(RootColumns.AVAILABLE_BYTES)); - root.summary = null; - - final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON)); - if (icon != 0) { - try { - root.icon = pm.getResourcesForApplication(info.providerInfo.applicationInfo) - .getDrawable(icon); - } catch (NotFoundException e) { - throw new RuntimeException(e); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - } - - final String title = cursor.getString(cursor.getColumnIndex(RootColumns.TITLE)); - if (title != null) { - root.title = title; - } - - root.summary = cursor.getString(cursor.getColumnIndex(RootColumns.SUMMARY)); - root.isRecents = false; - - return root; - } - - /** - * Return string most suited to showing in a directory listing. - */ - public String getDirectoryString() { - return (summary != null) ? summary : title; - } - - public static class RootComparator implements Comparator<Root> { - @Override - public int compare(Root lhs, Root rhs) { - final int score = compareToIgnoreCaseNullable(lhs.title, rhs.title); - if (score != 0) { - return score; - } else { - return compareToIgnoreCaseNullable(lhs.summary, rhs.summary); - } - } - } - - public static int compareToIgnoreCaseNullable(String lhs, String rhs) { - if (lhs == null) return -1; - if (rhs == null) return 1; - return lhs.compareToIgnoreCase(rhs); - } -} |