Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 92d7e697 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Reference docs by ROOT_ID and DOC_ID; recents.

The same document may be present with different sematics under
multiple storage roots, so always reference using both ROOT_ID and
DOC_ID.  This enables backends to revoke permissions for an entire
root, such as when an account is removed.

Start building provider to remember recently accessed documents.

Change-Id: I75befa2e61393dec12fcc7fd27f631fcddae46fa
parent 7e258b31
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -20267,7 +20267,9 @@ package android.provider {
  public final class DocumentsContract {
    ctor public DocumentsContract();
    method public static android.net.Uri buildContentsUri(android.net.Uri);
    method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
    method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String, java.lang.String);
    method public static android.net.Uri buildDocumentUri(android.net.Uri, java.lang.String);
    method public static android.net.Uri buildRootUri(java.lang.String, java.lang.String);
    method public static android.net.Uri buildRootsUri(java.lang.String);
    method public static android.net.Uri buildSearchUri(android.net.Uri, java.lang.String);
    method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point);
@@ -20282,7 +20284,7 @@ package android.provider {
    field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8
    field public static final java.lang.String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc";
    field public static final java.lang.String PARAM_QUERY = "query";
    field public static final java.lang.String ROOT_GUID = "0";
    field public static final java.lang.String ROOT_DOC_ID = "0";
    field public static final int ROOT_TYPE_DEVICE = 3; // 0x3
    field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4
    field public static final int ROOT_TYPE_SERVICE = 1; // 0x1
@@ -20290,16 +20292,16 @@ package android.provider {
  }
  public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns {
    field public static final java.lang.String DOC_ID = "doc_id";
    field public static final java.lang.String FLAGS = "flags";
    field public static final java.lang.String GUID = "guid";
    field public static final java.lang.String LAST_MODIFIED = "last_modified";
    field public static final java.lang.String MIME_TYPE = "mime_type";
  }
  public static abstract interface DocumentsContract.RootColumns {
    field public static final java.lang.String AVAILABLE_BYTES = "available_bytes";
    field public static final java.lang.String GUID = "guid";
    field public static final java.lang.String ICON = "icon";
    field public static final java.lang.String ROOT_ID = "root_id";
    field public static final java.lang.String ROOT_TYPE = "root_type";
    field public static final java.lang.String SUMMARY = "summary";
    field public static final java.lang.String TITLE = "title";
+27 −25
Original line number Diff line number Diff line
@@ -43,9 +43,10 @@ public final class DocumentsContract {
    private static final String TAG = "Documents";

    // content://com.example/roots/
    // content://com.example/docs/0/
    // content://com.example/docs/0/contents/
    // content://com.example/docs/0/search/?query=pony
    // content://com.example/roots/sdcard/
    // content://com.example/roots/sdcard/docs/0/
    // content://com.example/roots/sdcard/docs/0/contents/
    // content://com.example/roots/sdcard/docs/0/search/?query=pony

    /**
     * MIME type of a document which is a directory that may contain additional
@@ -59,10 +60,10 @@ public final class DocumentsContract {
    public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER";

    /**
     * {@link DocumentColumns#GUID} value representing the root directory of a
     * storage backend.
     * {@link DocumentColumns#DOC_ID} value representing the root directory of a
     * storage root.
     */
    public static final String ROOT_GUID = "0";
    public static final String ROOT_DOC_ID = "0";

    /**
     * Flag indicating that a document is a directory that supports creation of
@@ -139,20 +140,28 @@ public final class DocumentsContract {
    public static final String PARAM_QUERY = "query";

    /**
     * Build URI representing the custom roots in a storage backend.
     * Build URI representing the roots in a storage backend.
     */
    public static Uri buildRootsUri(String authority) {
        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                .authority(authority).appendPath(PATH_ROOTS).build();
    }

    public static Uri buildRootUri(String authority, String rootId) {
        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                .authority(authority).appendPath(PATH_ROOTS).appendPath(rootId).build();
    }

    public static Uri buildDocumentUri(String authority, String rootId, String docId) {
        return buildDocumentUri(buildRootUri(authority, rootId), docId);
    }

    /**
     * Build URI representing the given {@link DocumentColumns#GUID} in a
     * storage backend.
     * Build URI representing the given {@link DocumentColumns#DOC_ID} in a
     * storage root.
     */
    public static Uri buildDocumentUri(String authority, String guid) {
        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                .authority(authority).appendPath(PATH_DOCS).appendPath(guid).build();
    public static Uri buildDocumentUri(Uri rootUri, String docId) {
        return rootUri.buildUpon().appendPath(PATH_DOCS).appendPath(docId).build();
    }

    /**
@@ -184,15 +193,13 @@ public final class DocumentsContract {
     */
    public interface DocumentColumns extends OpenableColumns {
        /**
         * The globally unique ID for a document within a storage backend.
         * Values <em>must</em> never change once returned. This field is
         * read-only to document clients.
         * The ID for a document under a storage backend root. Values
         * <em>must</em> never change once returned. This field is read-only to
         * document clients.
         * <p>
         * Type: STRING
         *
         * @see DocumentsContract#ROOT_GUID
         */
        public static final String GUID = "guid";
        public static final String DOC_ID = "doc_id";

        /**
         * MIME type of a document, matching the value returned by
@@ -237,6 +244,8 @@ public final class DocumentsContract {
     * @see DocumentsContract#buildRootsUri(String)
     */
    public interface RootColumns {
        public static final String ROOT_ID = "root_id";

        /**
         * Storage root type, use for clustering.
         * <p>
@@ -247,13 +256,6 @@ public final class DocumentsContract {
         */
        public static final String ROOT_TYPE = "root_type";

        /**
         * GUID of directory entry for this storage root.
         * <p>
         * Type: STRING
         */
        public static final String GUID = "guid";

        /**
         * Icon resource ID for this storage root, or {@code 0} to use the
         * default {@link ProviderInfo#icon}.
+5 −0
Original line number Diff line number Diff line
@@ -21,6 +21,11 @@
            </intent-filter>
        </activity>

        <provider
            android:name=".RecentsProvider"
            android:authorities="com.android.documentsui.recents"
            android:exported="false" />

        <!-- TODO: remove when we have real clients -->
        <activity android:name=".TestActivity" android:enabled="false">
            <intent-filter>
+23 −19
Original line number Diff line number Diff line
@@ -59,6 +59,8 @@ import java.util.ArrayList;
public class DirectoryFragment extends Fragment {

    // TODO: show storage backend in item views when requested
    // TODO: apply sort order locally
    // TODO: apply MIME filtering locally

    private ListView mListView;
    private GridView mGridView;
@@ -70,14 +72,16 @@ public class DirectoryFragment extends Fragment {

    private int mFlags;

    private static final String EXTRA_URI = "uri";
    private static final String EXTRA_ROOT_URI = "rootUri";
    private static final String EXTRA_DOCS_URI = "docsUri";

    private static final int LOADER_DOCUMENTS = 2;

    public static void show(
            FragmentManager fm, Uri uri, String displayName, boolean addToBackStack) {
    public static void show(FragmentManager fm, Uri rootUri, Uri docsUri, String displayName,
            boolean addToBackStack) {
        final Bundle args = new Bundle();
        args.putParcelable(EXTRA_URI, uri);
        args.putParcelable(EXTRA_ROOT_URI, rootUri);
        args.putParcelable(EXTRA_DOCS_URI, docsUri);

        final DirectoryFragment fragment = new DirectoryFragment();
        fragment.setArguments(args);
@@ -116,8 +120,8 @@ public class DirectoryFragment extends Fragment {
        updateMode();

        // TODO: migrate flags query to loader
        final Uri uri = getArguments().getParcelable(EXTRA_URI);
        mFlags = getDocumentFlags(context, uri);
        final Uri docsUri = getArguments().getParcelable(EXTRA_DOCS_URI);
        mFlags = getDocumentFlags(context, docsUri);

        mCallbacks = new LoaderCallbacks<Cursor>() {
            @Override
@@ -133,10 +137,10 @@ public class DirectoryFragment extends Fragment {
                }

                final Uri contentsUri;
                if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
                    contentsUri = uri;
                if (docsUri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
                    contentsUri = docsUri;
                } else {
                    contentsUri = DocumentsContract.buildContentsUri(uri);
                    contentsUri = DocumentsContract.buildContentsUri(docsUri);
                }

                return new CursorLoader(context, contentsUri, null, null, null, sortOrder);
@@ -162,8 +166,8 @@ public class DirectoryFragment extends Fragment {
        getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);

        // TODO: clean up tracking of current directory
        final Uri uri = getArguments().getParcelable(EXTRA_URI);
        ((DocumentsActivity) getActivity()).onDirectoryChanged(uri, mFlags);
        final Uri docsUri = getArguments().getParcelable(EXTRA_DOCS_URI);
        ((DocumentsActivity) getActivity()).onDirectoryChanged(docsUri, mFlags);
    }

    @Override
@@ -245,8 +249,8 @@ public class DirectoryFragment extends Fragment {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            final Cursor cursor = (Cursor) mAdapter.getItem(position);
            final Uri uri = getArguments().getParcelable(EXTRA_URI);
            final Document doc = Document.fromCursor(uri.getAuthority(), cursor);
            final Uri rootUri = getArguments().getParcelable(EXTRA_ROOT_URI);
            final Document doc = Document.fromCursor(rootUri, cursor);
            ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
        }
    };
@@ -266,7 +270,7 @@ public class DirectoryFragment extends Fragment {
        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            if (item.getItemId() == R.id.menu_open) {
                final Uri uri = getArguments().getParcelable(EXTRA_URI);
                final Uri rootUri = getArguments().getParcelable(EXTRA_ROOT_URI);
                final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
                final ArrayList<Document> docs = Lists.newArrayList();

@@ -274,7 +278,7 @@ public class DirectoryFragment extends Fragment {
                for (int i = 0; i < size; i++) {
                    if (checked.valueAt(i)) {
                        final Cursor cursor = (Cursor) mAdapter.getItem(checked.keyAt(i));
                        docs.add(Document.fromCursor(uri.getAuthority(), cursor));
                        docs.add(Document.fromCursor(rootUri, cursor));
                    }
                }

@@ -336,17 +340,17 @@ public class DirectoryFragment extends Fragment {
            final TextView summary = (TextView) view.findViewById(android.R.id.summary);
            final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);

            final String guid = getCursorString(cursor, DocumentColumns.GUID);
            final String docId = getCursorString(cursor, DocumentColumns.DOC_ID);
            final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
            final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
            final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
            final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);

            final Uri uri = getArguments().getParcelable(EXTRA_URI);
            final String authority = uri.getAuthority();
            final Uri rootUri = getArguments().getParcelable(EXTRA_ROOT_URI);
            final String authority = rootUri.getAuthority();

            if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
                final Uri childUri = DocumentsContract.buildDocumentUri(authority, guid);
                final Uri childUri = DocumentsContract.buildDocumentUri(rootUri, docId);
                icon.setImageURI(childUri);
            } else {
                icon.setImageDrawable(
+39 −41
Original line number Diff line number Diff line
@@ -91,7 +91,6 @@ public class DocumentsActivity extends Activity {
    private static final String TAG = "Documents";

    // TODO: fragment to show recently opened documents
    // TODO: pull actionbar icon from current backend

    private static final String TAG_CREATE_DIRECTORY = "create_directory";

@@ -250,7 +249,8 @@ public class DocumentsActivity extends Activity {
            public boolean onQueryTextSubmit(String query) {
                // TODO: clear existing directory stack?
                final Uri searchUri = DocumentsContract.buildSearchUri(mCurrentDir, query);
                DirectoryFragment.show(getFragmentManager(), searchUri, query, true);
                DirectoryFragment.show(
                        getFragmentManager(), mCurrentRoot.rootUri, searchUri, query, true);
                mSearchView.setIconified(true);
                return true;
            }
@@ -398,7 +398,7 @@ public class DocumentsActivity extends Activity {
        final FragmentManager fm = getFragmentManager();
        if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
            // Nested directory picked, recurse using new fragment
            DirectoryFragment.show(fm, doc.uri, doc.displayName, true);
            DirectoryFragment.show(fm, doc.rootUri, doc.uri, doc.displayName, true);
        } else if (mAction == ACTION_OPEN) {
            // Explicit file picked, return
            onFinished(doc.uri);
@@ -418,6 +418,8 @@ public class DocumentsActivity extends Activity {
    }

    public void onSaveRequested(String mimeType, String displayName) {
        // TODO: handle overwrite by using last-selected GUID

        final ContentValues values = new ContentValues();
        values.put(DocumentColumns.MIME_TYPE, mimeType);
        values.put(DocumentColumns.DISPLAY_NAME, displayName);
@@ -426,7 +428,6 @@ public class DocumentsActivity extends Activity {
        if (uri != null) {
            onFinished(uri);
        } else {
            // TODO: ask for overwrite confirmation
            Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
        }
    }
@@ -470,35 +471,29 @@ public class DocumentsActivity extends Activity {
    public static class Root {
        public DocumentsProviderInfo info;
        public int rootType;
        public Uri rootUri;
        public Uri uri;
        public Drawable icon;
        public String title;
        public String summary;

        public static Root fromInfo(Context context, DocumentsProviderInfo info) {
        public static Root fromCursor(
                Context context, DocumentsProviderInfo info, Cursor cursor) {
            final String rootId = cursor.getString(cursor.getColumnIndex(RootColumns.ROOT_ID));

            final Root root = new Root();
            final PackageManager pm = context.getPackageManager();

            root.info = info;
            root.rootType = DocumentsContract.ROOT_TYPE_SERVICE;
            root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
            root.rootUri = DocumentsContract.buildRootUri(info.providerInfo.authority, rootId);
            root.uri = DocumentsContract.buildDocumentUri(
                    info.providerInfo.authority, DocumentsContract.ROOT_GUID);
                    root.rootUri, DocumentsContract.ROOT_DOC_ID);
            root.icon = info.providerInfo.loadIcon(pm);
            root.title = info.providerInfo.loadLabel(pm).toString();
            root.summary = null;

            return root;
        }

        public static Root fromCursor(
                Context context, DocumentsProviderInfo info, Cursor cursor) {
            final Root root = fromInfo(context, info);

            root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
            root.uri = DocumentsContract.buildDocumentUri(info.providerInfo.authority,
                    cursor.getString(cursor.getColumnIndex(RootColumns.GUID)));

            final PackageManager pm = context.getPackageManager();
            final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON));
            if (icon != 0) {
                try {
@@ -534,21 +529,24 @@ public class DocumentsActivity extends Activity {
    }

    public static class Document {
        public Uri rootUri;
        public Uri uri;
        public String mimeType;
        public String displayName;

        public static Document fromCursor(String authority, Cursor cursor) {
        public static Document fromCursor(Uri rootUri, Cursor cursor) {
            final Document doc = new Document();
            final String guid = getCursorString(cursor, DocumentColumns.GUID);
            doc.uri = DocumentsContract.buildDocumentUri(authority, guid);
            final String docId = getCursorString(cursor, DocumentColumns.DOC_ID);
            doc.rootUri = rootUri;
            doc.uri = DocumentsContract.buildDocumentUri(rootUri, docId);
            doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
            doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
            return doc;
        }

        public static Document fromUri(ContentResolver resolver, Uri uri) {
        public static Document fromUri(ContentResolver resolver, Uri rootUri, Uri uri) {
            final Document doc = new Document();
            doc.rootUri = rootUri;
            doc.uri = uri;

            final Cursor cursor = resolver.query(uri, null, null, null, null);
@@ -639,7 +637,7 @@ public class DocumentsActivity extends Activity {

                sProviders.put(info.providerInfo.authority, info);

                if (info.customRoots) {
                // TODO: remove deprecated customRoots flag
                // TODO: populate roots on background thread, and cache results
                final Uri uri = DocumentsContract.buildRootsUri(providerInfo.authority);
                final Cursor cursor = getContentResolver().query(uri, null, null, null, null);
@@ -650,9 +648,6 @@ public class DocumentsActivity extends Activity {
                } finally {
                    cursor.close();
                }
                } else if (info != null) {
                    sRoots.add(Root.fromInfo(this, info));
                }
            }
        }
    }
@@ -721,8 +716,8 @@ public class DocumentsActivity extends Activity {
            }

            mCurrentRoot = mRootsAdapter.getItem(position);
            DirectoryFragment.show(
                    getFragmentManager(), mCurrentRoot.uri, mCurrentRoot.title, false);
            DirectoryFragment.show(getFragmentManager(), mCurrentRoot.rootUri, mCurrentRoot.uri,
                    mCurrentRoot.title, false);

            mDrawerLayout.closeDrawers();
        }
@@ -784,13 +779,16 @@ public class DocumentsActivity extends Activity {
                    values.put(DocumentColumns.MIME_TYPE, DocumentsContract.MIME_TYPE_DIRECTORY);
                    values.put(DocumentColumns.DISPLAY_NAME, displayName);

                    // TODO: handle errors from remote side
                    final DocumentsActivity activity = (DocumentsActivity) getActivity();
                    final Uri uri = resolver.insert(activity.mCurrentDir, values);

                    if (uri != null) {
                        // Navigate into newly created child
                    final Document doc = Document.fromUri(resolver, uri);
                        final Document doc = Document.fromUri(
                                resolver, activity.mCurrentRoot.rootUri, uri);
                        activity.onDocumentPicked(doc);
                    } else {
                        Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show();
                    }
                }
            });
            builder.setNegativeButton(android.R.string.cancel, null);
Loading