Refactoring of DocumentsContract.

Combines related columns and constants onto the same class so they
are easier to discover.  Move back to surfacing roots with columns
so they are consistent with documents.

Advanced roots are represented with a flag instead of distinct
types.  Flags to indicate supporting of well-known media types,
instead of arbitrary an MIME filter.  Reintroduce well-formed rootId
to support recents.

Always use the expanded version of "documents" in constants, methods,
and argument names.

Refactor DocumentProvider method names to clearly distinguish if
a single item or multiple could be returned, and of which type.  Add
documentation to clearly define which methods have already been
overridden.

Bug: 10567506, 10567557
Change-Id: I981f26ab82f2b520a19aa1ce66f659de50d7fac0
diff --git a/api/current.txt b/api/current.txt
index 8e2b623..b5c7c88 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20781,67 +20781,69 @@
   }
 
   public final class DocumentsContract {
+    method public static android.net.Uri buildChildDocumentsUri(java.lang.String, java.lang.String);
     method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
-    method public static java.lang.String getDocId(android.net.Uri);
+    method public static android.net.Uri buildRecentDocumentsUri(java.lang.String, java.lang.String);
+    method public static android.net.Uri buildRootsUri(java.lang.String);
+    method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String);
+    method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String);
+    method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri);
+    method public static java.lang.String getDocumentId(android.net.Uri);
+    method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal);
     method public static android.net.Uri[] getOpenDocuments(android.content.Context);
+    method public static java.lang.String getRootId(android.net.Uri);
+    method public static java.lang.String getSearchDocumentsQuery(android.net.Uri);
     field public static final java.lang.String EXTRA_ERROR = "error";
     field public static final java.lang.String EXTRA_INFO = "info";
     field public static final java.lang.String EXTRA_LOADING = "loading";
   }
 
-  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 ICON = "icon";
-    field public static final java.lang.String LAST_MODIFIED = "last_modified";
-    field public static final java.lang.String MIME_TYPE = "mime_type";
-    field public static final java.lang.String SUMMARY = "summary";
+  public static final class DocumentsContract.Document {
+    field public static final java.lang.String COLUMN_DISPLAY_NAME = "_display_name";
+    field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id";
+    field public static final java.lang.String COLUMN_FLAGS = "flags";
+    field public static final java.lang.String COLUMN_ICON = "icon";
+    field public static final java.lang.String COLUMN_LAST_MODIFIED = "last_modified";
+    field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type";
+    field public static final java.lang.String COLUMN_SIZE = "_size";
+    field public static final java.lang.String COLUMN_SUMMARY = "summary";
+    field public static final int FLAG_DIR_PREFERS_GRID = 32; // 0x20
+    field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
+    field public static final int FLAG_DIR_SUPPORTS_SEARCH = 16; // 0x10
+    field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
+    field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
+    field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
 
-  public static final class DocumentsContract.DocumentRoot implements android.os.Parcelable {
-    ctor public DocumentsContract.DocumentRoot();
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator CREATOR;
+  public static final class DocumentsContract.Root {
+    field public static final java.lang.String COLUMN_AVAILABLE_BYTES = "available_bytes";
+    field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id";
+    field public static final java.lang.String COLUMN_FLAGS = "flags";
+    field public static final java.lang.String COLUMN_ICON = "icon";
+    field public static final java.lang.String COLUMN_ROOT_ID = "root_id";
+    field public static final java.lang.String COLUMN_ROOT_TYPE = "root_type";
+    field public static final java.lang.String COLUMN_SUMMARY = "summary";
+    field public static final java.lang.String COLUMN_TITLE = "title";
+    field public static final int FLAG_ADVANCED = 4; // 0x4
     field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
+    field public static final int FLAG_PROVIDES_AUDIO = 8; // 0x8
+    field public static final int FLAG_PROVIDES_IMAGES = 32; // 0x20
+    field public static final int FLAG_PROVIDES_VIDEO = 16; // 0x10
     field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
     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
     field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2
-    field public long availableBytes;
-    field public java.lang.String docId;
-    field public int flags;
-    field public int icon;
-    field public java.lang.String[] mimeTypes;
-    field public java.lang.String recentDocId;
-    field public int rootType;
-    field public java.lang.String summary;
-    field public java.lang.String title;
-  }
-
-  public static final class DocumentsContract.Documents {
-    field public static final int FLAG_PREFERS_GRID = 64; // 0x40
-    field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
-    field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
-    field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2
-    field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10
-    field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8
-    field public static final int FLAG_SUPPORTS_WRITE = 32; // 0x20
-    field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.doc/dir";
   }
 
   public abstract class DocumentsProvider extends android.content.ContentProvider {
     ctor public DocumentsProvider();
-    method public final android.os.Bundle callFromPackage(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle);
     method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
-    method public abstract java.util.List<android.provider.DocumentsContract.DocumentRoot> getDocumentRoots();
-    method public java.lang.String getType(java.lang.String) throws java.io.FileNotFoundException;
+    method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
     method public final java.lang.String getType(android.net.Uri);
     method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
-    method public void notifyDocumentRootsChanged();
     method public abstract android.os.ParcelFileDescriptor openDocument(java.lang.String, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public android.content.res.AssetFileDescriptor openDocumentThumbnail(java.lang.String, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -20849,10 +20851,11 @@
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
-    method public abstract android.database.Cursor queryDocument(java.lang.String) throws java.io.FileNotFoundException;
-    method public abstract android.database.Cursor queryDocumentChildren(java.lang.String) throws java.io.FileNotFoundException;
-    method public android.database.Cursor querySearch(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
-    method public void renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
+    method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
+    method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
+    method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
+    method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException;
+    method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
     method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
   }
 
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index ebb7eb8..b97b7c0 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -19,7 +19,6 @@
 import static android.net.TrafficStats.KB_IN_BYTES;
 import static libcore.io.OsConstants.SEEK_SET;
 
-import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,16 +29,13 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Point;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Parcel;
+import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
-import android.os.Parcelable;
 import android.util.Log;
 
-import com.android.internal.util.Preconditions;
 import com.google.android.collect.Lists;
 
 import libcore.io.ErrnoException;
@@ -62,9 +58,12 @@
 public final class DocumentsContract {
     private static final String TAG = "Documents";
 
-    // content://com.example/docs/12/
-    // content://com.example/docs/12/children/
-    // content://com.example/docs/12/search/?query=pony
+    // content://com.example/root/
+    // content://com.example/root/sdcard/
+    // content://com.example/root/sdcard/recent/
+    // content://com.example/document/12/
+    // content://com.example/document/12/children/
+    // content://com.example/document/12/search/?query=pony
 
     private DocumentsContract() {
     }
@@ -75,207 +74,69 @@
     /** {@hide} */
     public static final String ACTION_MANAGE_DOCUMENTS = "android.provider.action.MANAGE_DOCUMENTS";
 
-    /** {@hide} */
-    public static final String
-            ACTION_DOCUMENT_ROOT_CHANGED = "android.provider.action.DOCUMENT_ROOT_CHANGED";
-
     /**
-     * Constants for individual documents.
+     * Constants related to a document, including {@link Cursor} columns names
+     * and flags.
+     * <p>
+     * A document can be either an openable file (with a specific MIME type), or
+     * a directory containing additional documents (with the
+     * {@link #MIME_TYPE_DIR} MIME type).
+     * <p>
+     * All columns are <em>read-only</em> to client applications.
      */
-    public final static class Documents {
-        private Documents() {
+    public final static class Document {
+        private Document() {
         }
 
         /**
-         * MIME type of a document which is a directory that may contain additional
-         * documents.
-         */
-        public static final String MIME_TYPE_DIR = "vnd.android.doc/dir";
-
-        /**
-         * Flag indicating that a document is a directory that supports creation of
-         * new files within it.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_CREATE = 1;
-
-        /**
-         * Flag indicating that a document is renamable.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
-
-        /**
-         * Flag indicating that a document is deletable.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
-
-        /**
-         * Flag indicating that a document can be represented as a thumbnail.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
-
-        /**
-         * Flag indicating that a document is a directory that supports search.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
-
-        /**
-         * Flag indicating that a document supports writing.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_WRITE = 1 << 5;
-
-        /**
-         * Flag indicating that a document is a directory that prefers its contents
-         * be shown in a larger format grid. Usually suitable when a directory
-         * contains mostly pictures.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_PREFERS_GRID = 1 << 6;
-    }
-
-    /**
-     * Extra boolean flag included in a directory {@link Cursor#getExtras()}
-     * indicating that a document provider is still loading data. For example, a
-     * provider has returned some results, but is still waiting on an
-     * outstanding network request.
-     *
-     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
-     *      boolean)
-     */
-    public static final String EXTRA_LOADING = "loading";
-
-    /**
-     * Extra string included in a directory {@link Cursor#getExtras()}
-     * providing an informational message that should be shown to a user. For
-     * example, a provider may wish to indicate that not all documents are
-     * available.
-     */
-    public static final String EXTRA_INFO = "info";
-
-    /**
-     * Extra string included in a directory {@link Cursor#getExtras()} providing
-     * an error message that should be shown to a user. For example, a provider
-     * may wish to indicate that a network error occurred. The user may choose
-     * to retry, resulting in a new query.
-     */
-    public static final String EXTRA_ERROR = "error";
-
-    /** {@hide} */
-    public static final String METHOD_GET_ROOTS = "android:getRoots";
-    /** {@hide} */
-    public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
-    /** {@hide} */
-    public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
-    /** {@hide} */
-    public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
-
-    /** {@hide} */
-    public static final String EXTRA_AUTHORITY = "authority";
-    /** {@hide} */
-    public static final String EXTRA_PACKAGE_NAME = "packageName";
-    /** {@hide} */
-    public static final String EXTRA_URI = "uri";
-    /** {@hide} */
-    public static final String EXTRA_ROOTS = "roots";
-    /** {@hide} */
-    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
-
-    private static final String PATH_DOCS = "docs";
-    private static final String PATH_CHILDREN = "children";
-    private static final String PATH_SEARCH = "search";
-
-    private static final String PARAM_QUERY = "query";
-
-    /**
-     * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a
-     * document provider.
-     */
-    public static Uri buildDocumentUri(String authority, String docId) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(authority).appendPath(PATH_DOCS).appendPath(docId).build();
-    }
-
-    /**
-     * Build Uri representing the contents of the given directory in a document
-     * provider. The given document must be {@link Documents#MIME_TYPE_DIR}.
-     *
-     * @hide
-     */
-    public static Uri buildChildrenUri(String authority, String docId) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
-                .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_CHILDREN).build();
-    }
-
-    /**
-     * Build Uri representing a search for matching documents under a specific
-     * directory in a document provider. The given document must have
-     * {@link Documents#FLAG_SUPPORTS_SEARCH}.
-     *
-     * @hide
-     */
-    public static Uri buildSearchUri(String authority, String docId, String query) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
-                .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_SEARCH)
-                .appendQueryParameter(PARAM_QUERY, query).build();
-    }
-
-    /**
-     * Extract the {@link DocumentColumns#DOC_ID} from the given Uri.
-     */
-    public static String getDocId(Uri documentUri) {
-        final List<String> paths = documentUri.getPathSegments();
-        if (paths.size() < 2) {
-            throw new IllegalArgumentException("Not a document: " + documentUri);
-        }
-        if (!PATH_DOCS.equals(paths.get(0))) {
-            throw new IllegalArgumentException("Not a document: " + documentUri);
-        }
-        return paths.get(1);
-    }
-
-    /** {@hide} */
-    public static String getSearchQuery(Uri documentUri) {
-        return documentUri.getQueryParameter(PARAM_QUERY);
-    }
-
-    /**
-     * Standard columns for document queries. Document providers <em>must</em>
-     * support at least these columns when queried.
-     */
-    public interface DocumentColumns extends OpenableColumns {
-        /**
-         * Unique ID for a document. Values <em>must</em> never change once
-         * returned, since they may used for long-term Uri permission grants.
+         * Unique ID of a document. This ID is both provided by and interpreted
+         * by a {@link DocumentsProvider}, and should be treated as an opaque
+         * value by client applications.
+         * <p>
+         * Each document must have a unique ID within a provider, but that
+         * single document may be included as a child of multiple directories.
+         * <p>
+         * A provider must always return durable IDs, since they will be used to
+         * issue long-term Uri permission grants when an application interacts
+         * with {@link Intent#ACTION_OPEN_DOCUMENT} and
+         * {@link Intent#ACTION_CREATE_DOCUMENT}.
          * <p>
          * Type: STRING
          */
-        public static final String DOC_ID = "doc_id";
+        public static final String COLUMN_DOCUMENT_ID = "document_id";
 
         /**
-         * MIME type of a document.
+         * Concrete MIME type of a document. For example, "image/png" or
+         * "application/pdf" for openable files. A document can also be a
+         * directory containing additional documents, which is represented with
+         * the {@link #MIME_TYPE_DIR} MIME type.
          * <p>
          * Type: STRING
          *
-         * @see Documents#MIME_TYPE_DIR
+         * @see #MIME_TYPE_DIR
          */
-        public static final String MIME_TYPE = "mime_type";
+        public static final String COLUMN_MIME_TYPE = "mime_type";
+
+        /**
+         * Display name of a document, used as the primary title displayed to a
+         * user.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
+
+        /**
+         * Summary of a document, which may be shown to a user. The summary may
+         * be {@code null}.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_SUMMARY = "summary";
 
         /**
          * Timestamp when a document was last modified, in milliseconds since
-         * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. Document
-         * providers can update this field using events from
+         * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
+         * {@link DocumentsProvider} can update this field using events from
          * {@link OnCloseListener} or other reliable
          * {@link ParcelFileDescriptor} transports.
          * <p>
@@ -283,71 +144,227 @@
          *
          * @see System#currentTimeMillis()
          */
-        public static final String LAST_MODIFIED = "last_modified";
+        public static final String COLUMN_LAST_MODIFIED = "last_modified";
 
         /**
-         * Specific icon resource for a document, or {@code null} to resolve
-         * default using {@link #MIME_TYPE}.
+         * Specific icon resource ID for a document, or {@code null} to use
+         * platform default icon based on {@link #COLUMN_MIME_TYPE}.
          * <p>
          * Type: INTEGER (int)
          */
-        public static final String ICON = "icon";
+        public static final String COLUMN_ICON = "icon";
 
         /**
-         * Summary for a document, or {@code null} to omit.
-         * <p>
-         * Type: STRING
-         */
-        public static final String SUMMARY = "summary";
-
-        /**
-         * Flags that apply to a specific document.
+         * Flags that apply to a document.
          * <p>
          * Type: INTEGER (int)
+         *
+         * @see #FLAG_SUPPORTS_WRITE
+         * @see #FLAG_SUPPORTS_DELETE
+         * @see #FLAG_SUPPORTS_THUMBNAIL
+         * @see #FLAG_DIR_PREFERS_GRID
+         * @see #FLAG_DIR_SUPPORTS_CREATE
+         * @see #FLAG_DIR_SUPPORTS_SEARCH
          */
-        public static final String FLAGS = "flags";
+        public static final String COLUMN_FLAGS = "flags";
+
+        /**
+         * Size of a document, in bytes, or {@code null} if unknown.
+         * <p>
+         * Type: INTEGER (long)
+         */
+        public static final String COLUMN_SIZE = OpenableColumns.SIZE;
+
+        /**
+         * MIME type of a document which is a directory that may contain
+         * additional documents.
+         *
+         * @see #COLUMN_MIME_TYPE
+         */
+        public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
+
+        /**
+         * Flag indicating that a document can be represented as a thumbnail.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
+         *      Point, CancellationSignal)
+         * @see DocumentsProvider#openDocumentThumbnail(String, Point,
+         *      android.os.CancellationSignal)
+         */
+        public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
+
+        /**
+         * Flag indicating that a document supports writing.
+         * <p>
+         * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
+         * the calling application is granted both
+         * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
+         * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
+         * writability of a document may change over time, for example due to
+         * remote access changes. This flag indicates that a document client can
+         * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
+         *
+         * @see #COLUMN_FLAGS
+         */
+        public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
+
+        /**
+         * Flag indicating that a document is deletable.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
+         * @see DocumentsProvider#deleteDocument(String)
+         */
+        public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
+
+        /**
+         * Flag indicating that a document is a directory that supports creation
+         * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
+         * {@link #MIME_TYPE_DIR}.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#createDocument(ContentResolver, Uri, String,
+         *      String)
+         * @see DocumentsProvider#createDocument(String, String, String)
+         */
+        public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
+
+        /**
+         * Flag indicating that a directory supports search. Only valid when
+         * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsProvider#querySearchDocuments(String, String,
+         *      String[])
+         */
+        public static final int FLAG_DIR_SUPPORTS_SEARCH = 1 << 4;
+
+        /**
+         * Flag indicating that a directory prefers its contents be shown in a
+         * larger format grid. Usually suitable when a directory contains mostly
+         * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
+         * {@link #MIME_TYPE_DIR}.
+         *
+         * @see #COLUMN_FLAGS
+         */
+        public static final int FLAG_DIR_PREFERS_GRID = 1 << 5;
     }
 
     /**
-     * Metadata about a specific root of documents.
+     * Constants related to a root of documents, including {@link Cursor}
+     * columns names and flags.
+     * <p>
+     * All columns are <em>read-only</em> to client applications.
      */
-    public final static class DocumentRoot implements Parcelable {
+    public final static class Root {
+        private Root() {
+        }
+
         /**
-         * Root that represents a storage service, such as a cloud-based
+         * Unique ID of a root. This ID is both provided by and interpreted by a
+         * {@link DocumentsProvider}, and should be treated as an opaque value
+         * by client applications.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_ROOT_ID = "root_id";
+
+        /**
+         * Type of a root, used for clustering when presenting multiple roots to
+         * a user.
+         * <p>
+         * Type: INTEGER (int)
+         *
+         * @see #ROOT_TYPE_SERVICE
+         * @see #ROOT_TYPE_SHORTCUT
+         * @see #ROOT_TYPE_DEVICE
+         */
+        public static final String COLUMN_ROOT_TYPE = "root_type";
+
+        /**
+         * Flags that apply to a root.
+         * <p>
+         * Type: INTEGER (int)
+         *
+         * @see #FLAG_LOCAL_ONLY
+         * @see #FLAG_SUPPORTS_CREATE
+         * @see #FLAG_ADVANCED
+         * @see #FLAG_PROVIDES_AUDIO
+         * @see #FLAG_PROVIDES_IMAGES
+         * @see #FLAG_PROVIDES_VIDEO
+         */
+        public static final String COLUMN_FLAGS = "flags";
+
+        /**
+         * Icon resource ID for a root.
+         * <p>
+         * Type: INTEGER (int)
+         */
+        public static final String COLUMN_ICON = "icon";
+
+        /**
+         * Title for a root, which will be shown to a user.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_TITLE = "title";
+
+        /**
+         * Summary for this root, which may be shown to a user. The summary may
+         * be {@code null}.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_SUMMARY = "summary";
+
+        /**
+         * Document which is a directory that represents the top directory of
+         * this root.
+         * <p>
+         * Type: STRING
+         *
+         * @see Document#COLUMN_DOCUMENT_ID
+         */
+        public static final String COLUMN_DOCUMENT_ID = "document_id";
+
+        /**
+         * Number of bytes available in this root, or {@code null} if unknown or
+         * unbounded.
+         * <p>
+         * Type: INTEGER (long)
+         */
+        public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
+
+        /**
+         * Type of root that represents a storage service, such as a cloud-based
          * service.
          *
-         * @see #rootType
+         * @see #COLUMN_ROOT_TYPE
          */
         public static final int ROOT_TYPE_SERVICE = 1;
 
         /**
-         * Root that represents a shortcut to content that may be available
-         * elsewhere through another storage root.
+         * Type of root that represents a shortcut to content that may be
+         * available elsewhere through another storage root.
          *
-         * @see #rootType
+         * @see #COLUMN_ROOT_TYPE
          */
         public static final int ROOT_TYPE_SHORTCUT = 2;
 
         /**
-         * Root that represents a physical storage device.
+         * Type of root that represents a physical storage device.
          *
-         * @see #rootType
+         * @see #COLUMN_ROOT_TYPE
          */
         public static final int ROOT_TYPE_DEVICE = 3;
 
         /**
-         * Root that represents a physical storage device that should only be
-         * displayed to advanced users.
-         *
-         * @see #rootType
-         */
-        public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
-
-        /**
          * Flag indicating that at least one directory under this root supports
-         * creating content.
+         * creating content. Roots with this flag will be shown when an
+         * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
          *
-         * @see #flags
+         * @see #COLUMN_FLAGS
          */
         public static final int FLAG_SUPPORTS_CREATE = 1;
 
@@ -355,138 +372,201 @@
          * Flag indicating that this root offers content that is strictly local
          * on the device. That is, no network requests are made for the content.
          *
-         * @see #flags
+         * @see #COLUMN_FLAGS
+         * @see Intent#EXTRA_LOCAL_ONLY
          */
         public static final int FLAG_LOCAL_ONLY = 1 << 1;
 
-        /** {@hide} */
-        public String authority;
-
         /**
-         * Root type, use for clustering.
+         * Flag indicating that this root should only be visible to advanced
+         * users.
          *
-         * @see #ROOT_TYPE_SERVICE
-         * @see #ROOT_TYPE_DEVICE
+         * @see #COLUMN_FLAGS
          */
-        public int rootType;
+        public static final int FLAG_ADVANCED = 1 << 2;
 
         /**
-         * Flags for this root.
+         * Flag indicating that a root offers audio documents. When a user is
+         * selecting audio, roots not providing audio may be excluded.
          *
-         * @see #FLAG_LOCAL_ONLY
+         * @see #COLUMN_FLAGS
+         * @see Intent#EXTRA_MIME_TYPES
          */
-        public int flags;
+        public static final int FLAG_PROVIDES_AUDIO = 1 << 3;
 
         /**
-         * Icon resource ID for this root.
-         */
-        public int icon;
-
-        /**
-         * Title for this root.
-         */
-        public String title;
-
-        /**
-         * Summary for this root. May be {@code null}.
-         */
-        public String summary;
-
-        /**
-         * Document which is a directory that represents the top of this root.
-         * Must not be {@code null}.
+         * Flag indicating that a root offers video documents. When a user is
+         * selecting video, roots not providing video may be excluded.
          *
-         * @see DocumentColumns#DOC_ID
+         * @see #COLUMN_FLAGS
+         * @see Intent#EXTRA_MIME_TYPES
          */
-        public String docId;
+        public static final int FLAG_PROVIDES_VIDEO = 1 << 4;
 
         /**
-         * Document which is a directory representing recently modified
-         * documents under this root. This directory should return at most two
-         * dozen documents modified within the last 90 days. May be {@code null}
-         * if this root doesn't support recents.
+         * Flag indicating that a root offers image documents. When a user is
+         * selecting images, roots not providing images may be excluded.
          *
-         * @see DocumentColumns#DOC_ID
+         * @see #COLUMN_FLAGS
+         * @see Intent#EXTRA_MIME_TYPES
          */
-        public String recentDocId;
+        public static final int FLAG_PROVIDES_IMAGES = 1 << 5;
+    }
 
-        /**
-         * Number of free bytes of available in this root, or -1 if unknown or
-         * unbounded.
-         */
-        public long availableBytes;
+    /**
+     * Optional boolean flag included in a directory {@link Cursor#getExtras()}
+     * indicating that a document provider is still loading data. For example, a
+     * provider has returned some results, but is still waiting on an
+     * outstanding network request. The provider must send a content changed
+     * notification when loading is finished.
+     *
+     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
+     *      boolean)
+     */
+    public static final String EXTRA_LOADING = "loading";
 
-        /**
-         * Set of MIME type filters describing the content offered by this root,
-         * or {@code null} to indicate that all MIME types are supported. For
-         * example, a provider only supporting audio and video might set this to
-         * {@code ["audio/*", "video/*"]}.
-         */
-        public String[] mimeTypes;
+    /**
+     * Optional string included in a directory {@link Cursor#getExtras()}
+     * providing an informational message that should be shown to a user. For
+     * example, a provider may wish to indicate that not all documents are
+     * available.
+     */
+    public static final String EXTRA_INFO = "info";
 
-        public DocumentRoot() {
+    /**
+     * Optional string included in a directory {@link Cursor#getExtras()}
+     * providing an error message that should be shown to a user. For example, a
+     * provider may wish to indicate that a network error occurred. The user may
+     * choose to retry, resulting in a new query.
+     */
+    public static final String EXTRA_ERROR = "error";
+
+    /** {@hide} */
+    public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
+    /** {@hide} */
+    public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
+
+    /** {@hide} */
+    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
+
+    private static final String PATH_ROOT = "root";
+    private static final String PATH_RECENT = "recent";
+    private static final String PATH_DOCUMENT = "document";
+    private static final String PATH_CHILDREN = "children";
+    private static final String PATH_SEARCH = "search";
+
+    private static final String PARAM_QUERY = "query";
+
+    /**
+     * Build Uri representing the roots of a document provider. When queried, a
+     * provider will return one or more rows with columns defined by
+     * {@link Root}.
+     *
+     * @see DocumentsProvider#queryRoots(String[])
+     */
+    public static Uri buildRootsUri(String authority) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority).appendPath(PATH_ROOT).build();
+    }
+
+    /**
+     * Build Uri representing the recently modified documents of a specific
+     * root. When queried, a provider will return zero or more rows with columns
+     * defined by {@link Document}.
+     *
+     * @see DocumentsProvider#queryRecentDocuments(String, String[])
+     * @see #getRootId(Uri)
+     */
+    public static Uri buildRecentDocumentsUri(String authority, String rootId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
+                .appendPath(PATH_RECENT).build();
+    }
+
+    /**
+     * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
+     * document provider. When queried, a provider will return a single row with
+     * columns defined by {@link Document}.
+     *
+     * @see DocumentsProvider#queryDocument(String, String[])
+     * @see #getDocumentId(Uri)
+     */
+    public static Uri buildDocumentUri(String authority, String documentId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
+    }
+
+    /**
+     * Build Uri representing the children of the given directory in a document
+     * provider. When queried, a provider will return zero or more rows with
+     * columns defined by {@link Document}.
+     *
+     * @param parentDocumentId the document to return children for, which must
+     *            be a directory with MIME type of
+     *            {@link Document#MIME_TYPE_DIR}.
+     * @see DocumentsProvider#queryChildDocuments(String, String[], String)
+     * @see #getDocumentId(Uri)
+     */
+    public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+                .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
+                .build();
+    }
+
+    /**
+     * Build Uri representing a search for matching documents under a specific
+     * directory in a document provider. When queried, a provider will return
+     * zero or more rows with columns defined by {@link Document}.
+     *
+     * @param parentDocumentId the document to return children for, which must
+     *            be both a directory with MIME type of
+     *            {@link Document#MIME_TYPE_DIR} and have
+     *            {@link Document#FLAG_DIR_SUPPORTS_SEARCH} set.
+     * @see DocumentsProvider#querySearchDocuments(String, String, String[])
+     * @see #getDocumentId(Uri)
+     * @see #getSearchDocumentsQuery(Uri)
+     */
+    public static Uri buildSearchDocumentsUri(
+            String authority, String parentDocumentId, String query) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+                .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_SEARCH)
+                .appendQueryParameter(PARAM_QUERY, query).build();
+    }
+
+    /**
+     * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri.
+     */
+    public static String getRootId(Uri rootUri) {
+        final List<String> paths = rootUri.getPathSegments();
+        if (paths.size() < 2) {
+            throw new IllegalArgumentException("Not a root: " + rootUri);
         }
-
-        /** {@hide} */
-        public DocumentRoot(Parcel in) {
-            rootType = in.readInt();
-            flags = in.readInt();
-            icon = in.readInt();
-            title = in.readString();
-            summary = in.readString();
-            docId = in.readString();
-            recentDocId = in.readString();
-            availableBytes = in.readLong();
-            mimeTypes = in.readStringArray();
+        if (!PATH_ROOT.equals(paths.get(0))) {
+            throw new IllegalArgumentException("Not a root: " + rootUri);
         }
+        return paths.get(1);
+    }
 
-        /** {@hide} */
-        public Drawable loadIcon(Context context) {
-            if (icon != 0) {
-                if (authority != null) {
-                    final PackageManager pm = context.getPackageManager();
-                    final ProviderInfo info = pm.resolveContentProvider(authority, 0);
-                    if (info != null) {
-                        return pm.getDrawable(info.packageName, icon, info.applicationInfo);
-                    }
-                } else {
-                    return context.getResources().getDrawable(icon);
-                }
-            }
-            return null;
+    /**
+     * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
+     */
+    public static String getDocumentId(Uri documentUri) {
+        final List<String> paths = documentUri.getPathSegments();
+        if (paths.size() < 2) {
+            throw new IllegalArgumentException("Not a document: " + documentUri);
         }
-
-        @Override
-        public int describeContents() {
-            return 0;
+        if (!PATH_DOCUMENT.equals(paths.get(0))) {
+            throw new IllegalArgumentException("Not a document: " + documentUri);
         }
+        return paths.get(1);
+    }
 
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            Preconditions.checkNotNull(docId);
-
-            dest.writeInt(rootType);
-            dest.writeInt(flags);
-            dest.writeInt(icon);
-            dest.writeString(title);
-            dest.writeString(summary);
-            dest.writeString(docId);
-            dest.writeString(recentDocId);
-            dest.writeLong(availableBytes);
-            dest.writeStringArray(mimeTypes);
-        }
-
-        public static final Creator<DocumentRoot> CREATOR = new Creator<DocumentRoot>() {
-            @Override
-            public DocumentRoot createFromParcel(Parcel in) {
-                return new DocumentRoot(in);
-            }
-
-            @Override
-            public DocumentRoot[] newArray(int size) {
-                return new DocumentRoot[size];
-            }
-        };
+    /**
+     * Extract the search query from a Uri built by
+     * {@link #buildSearchDocumentsUri(String, String, String)}.
+     */
+    public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
+        return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
     }
 
     /**
@@ -497,6 +577,7 @@
      * {@link Intent#ACTION_CREATE_DOCUMENT}.
      *
      * @see Context#grantUriPermission(String, Uri, int)
+     * @see Context#revokeUriPermission(Uri, int)
      * @see ContentResolver#getIncomingUriPermissionGrants(int, int)
      */
     public static Uri[] getOpenDocuments(Context context) {
@@ -520,20 +601,28 @@
     }
 
     /**
-     * Return thumbnail representing the document at the given URI. Callers are
-     * responsible for their own in-memory caching. Given document must have
-     * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set.
+     * Return thumbnail representing the document at the given Uri. Callers are
+     * responsible for their own in-memory caching.
      *
+     * @param documentUri document to return thumbnail for, which must have
+     *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
+     * @param size optimal thumbnail size desired. A provider may return a
+     *            thumbnail of a different size, but never more than double the
+     *            requested size.
+     * @param signal signal used to indicate that caller is no longer interested
+     *            in the thumbnail.
      * @return decoded thumbnail, or {@code null} if problem was encountered.
-     * @hide
+     * @see DocumentsProvider#openDocumentThumbnail(String, Point,
+     *      android.os.CancellationSignal)
      */
-    public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
+    public static Bitmap getDocumentThumbnail(
+            ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
         final Bundle openOpts = new Bundle();
         openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
 
         AssetFileDescriptor afd = null;
         try {
-            afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts);
+            afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
 
             final FileDescriptor fd = afd.getFileDescriptor();
             final long offset = afd.getStartOffset();
@@ -583,38 +672,26 @@
         }
     }
 
-    /** {@hide} */
-    public static List<DocumentRoot> getDocumentRoots(ContentProviderClient client) {
-        try {
-            final Bundle out = client.call(METHOD_GET_ROOTS, null, null);
-            final List<DocumentRoot> roots = out.getParcelableArrayList(EXTRA_ROOTS);
-            return roots;
-        } catch (Exception e) {
-            Log.w(TAG, "Failed to get roots", e);
-            return null;
-        }
-    }
-
     /**
-     * Create a new document under the given parent document with MIME type and
-     * display name.
+     * Create a new document with given MIME type and display name.
      *
-     * @param docId document with {@link Documents#FLAG_SUPPORTS_CREATE}
+     * @param parentDocumentUri directory with
+     *            {@link Document#FLAG_DIR_SUPPORTS_CREATE}
      * @param mimeType MIME type of new document
      * @param displayName name of new document
      * @return newly created document, or {@code null} if failed
-     * @hide
      */
-    public static String createDocument(
-            ContentProviderClient client, String docId, String mimeType, String displayName) {
+    public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
+            String mimeType, String displayName) {
         final Bundle in = new Bundle();
-        in.putString(DocumentColumns.DOC_ID, docId);
-        in.putString(DocumentColumns.MIME_TYPE, mimeType);
-        in.putString(DocumentColumns.DISPLAY_NAME, displayName);
+        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
+        in.putString(Document.COLUMN_MIME_TYPE, mimeType);
+        in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
 
         try {
-            final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
-            return out.getString(DocumentColumns.DOC_ID);
+            final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in);
+            return buildDocumentUri(
+                    parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
         } catch (Exception e) {
             Log.w(TAG, "Failed to create document", e);
             return null;
@@ -622,40 +699,16 @@
     }
 
     /**
-     * Rename the given document.
-     *
-     * @param docId document with {@link Documents#FLAG_SUPPORTS_RENAME}
-     * @return document which may have changed due to rename, or {@code null} if
-     *         rename failed.
-     * @hide
-     */
-    public static String renameDocument(
-            ContentProviderClient client, String docId, String displayName) {
-        final Bundle in = new Bundle();
-        in.putString(DocumentColumns.DOC_ID, docId);
-        in.putString(DocumentColumns.DISPLAY_NAME, displayName);
-
-        try {
-            final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
-            return out.getString(DocumentColumns.DOC_ID);
-        } catch (Exception e) {
-            Log.w(TAG, "Failed to rename document", e);
-            return null;
-        }
-    }
-
-    /**
      * Delete the given document.
      *
-     * @param docId document with {@link Documents#FLAG_SUPPORTS_DELETE}
-     * @hide
+     * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
      */
-    public static boolean deleteDocument(ContentProviderClient client, String docId) {
+    public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
         final Bundle in = new Bundle();
-        in.putString(DocumentColumns.DOC_ID, docId);
+        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
 
         try {
-            client.call(METHOD_DELETE_DOCUMENT, null, in);
+            final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in);
             return true;
         } catch (Exception e) {
             Log.w(TAG, "Failed to delete document", e);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index eeb8c41..09f4866 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -16,16 +16,12 @@
 
 package android.provider;
 
-import static android.provider.DocumentsContract.ACTION_DOCUMENT_ROOT_CHANGED;
-import static android.provider.DocumentsContract.EXTRA_AUTHORITY;
-import static android.provider.DocumentsContract.EXTRA_ROOTS;
 import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
 import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
 import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
-import static android.provider.DocumentsContract.METHOD_GET_ROOTS;
-import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
-import static android.provider.DocumentsContract.getDocId;
-import static android.provider.DocumentsContract.getSearchQuery;
+import static android.provider.DocumentsContract.getDocumentId;
+import static android.provider.DocumentsContract.getRootId;
+import static android.provider.DocumentsContract.getSearchDocumentsQuery;
 
 import android.content.ContentProvider;
 import android.content.ContentValues;
@@ -41,15 +37,12 @@
 import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
-import android.provider.DocumentsContract.DocumentColumns;
-import android.provider.DocumentsContract.DocumentRoot;
-import android.provider.DocumentsContract.Documents;
+import android.provider.DocumentsContract.Document;
 import android.util.Log;
 
 import libcore.io.IoUtils;
 
 import java.io.FileNotFoundException;
-import java.util.List;
 
 /**
  * Base class for a document provider. A document provider should extend this
@@ -58,13 +51,13 @@
  * Each document provider expresses one or more "roots" which each serve as the
  * top-level of a tree. For example, a root could represent an account, or a
  * physical storage device. Under each root, documents are referenced by
- * {@link DocumentColumns#DOC_ID}, which must not change once returned.
+ * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned.
  * <p>
  * Documents can be either an openable file (with a specific MIME type), or a
  * directory containing additional documents (with the
- * {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different
- * capabilities, as described by {@link DocumentColumns#FLAGS}. The same
- * {@link DocumentColumns#DOC_ID} can be included in multiple directories.
+ * {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different
+ * capabilities, as described by {@link Document#COLUMN_FLAGS}. The same
+ * {@link Document#COLUMN_DOCUMENT_ID} can be included in multiple directories.
  * <p>
  * Document providers must be protected with the
  * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can
@@ -78,22 +71,29 @@
 public abstract class DocumentsProvider extends ContentProvider {
     private static final String TAG = "DocumentsProvider";
 
-    private static final int MATCH_DOCUMENT = 1;
-    private static final int MATCH_CHILDREN = 2;
-    private static final int MATCH_SEARCH = 3;
+    private static final int MATCH_ROOT = 1;
+    private static final int MATCH_RECENT = 2;
+    private static final int MATCH_DOCUMENT = 3;
+    private static final int MATCH_CHILDREN = 4;
+    private static final int MATCH_SEARCH = 5;
 
     private String mAuthority;
 
     private UriMatcher mMatcher;
 
+    /**
+     * Implementation is provided by the parent class.
+     */
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
         mAuthority = info.authority;
 
         mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-        mMatcher.addURI(mAuthority, "docs/*", MATCH_DOCUMENT);
-        mMatcher.addURI(mAuthority, "docs/*/children", MATCH_CHILDREN);
-        mMatcher.addURI(mAuthority, "docs/*/search", MATCH_SEARCH);
+        mMatcher.addURI(mAuthority, "root", MATCH_ROOT);
+        mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
+        mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
+        mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
+        mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH);
 
         // Sanity check our setup
         if (!info.exported) {
@@ -111,83 +111,80 @@
     }
 
     /**
-     * Return list of all document roots provided by this document provider.
-     * When this list changes, a provider must call
-     * {@link #notifyDocumentRootsChanged()}.
-     */
-    public abstract List<DocumentRoot> getDocumentRoots();
-
-    /**
-     * Create and return a new document. A provider must allocate a new
-     * {@link DocumentColumns#DOC_ID} to represent the document, which must not
-     * change once returned.
+     * Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}.
+     * A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to
+     * represent the document, which must not change once returned.
      *
-     * @param docId the parent directory to create the new document under.
+     * @param documentId the parent directory to create the new document under.
      * @param mimeType the MIME type associated with the new document.
      * @param displayName the display name of the new document.
      */
     @SuppressWarnings("unused")
-    public String createDocument(String docId, String mimeType, String displayName)
+    public String createDocument(String documentId, String mimeType, String displayName)
             throws FileNotFoundException {
         throw new UnsupportedOperationException("Create not supported");
     }
 
     /**
-     * Rename the given document.
+     * Delete the given document. Upon returning, any Uri permission grants for
+     * the given document will be revoked. If additional documents were deleted
+     * as a side effect of this call, such as documents inside a directory, the
+     * implementor is responsible for revoking those permissions.
      *
-     * @param docId the document to rename.
-     * @param displayName the new display name.
+     * @param documentId the document to delete.
      */
     @SuppressWarnings("unused")
-    public void renameDocument(String docId, String displayName) throws FileNotFoundException {
-        throw new UnsupportedOperationException("Rename not supported");
+    public void deleteDocument(String documentId) throws FileNotFoundException {
+        throw new UnsupportedOperationException("Delete not supported");
     }
 
-    /**
-     * Delete the given document.
-     *
-     * @param docId the document to delete.
-     */
+    public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
+
     @SuppressWarnings("unused")
-    public void deleteDocument(String docId) throws FileNotFoundException {
-        throw new UnsupportedOperationException("Delete not supported");
+    public Cursor queryRecentDocuments(String rootId, String[] projection)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Recent not supported");
     }
 
     /**
      * Return metadata for the given document. A provider should avoid making
      * network requests to keep this request fast.
      *
-     * @param docId the document to return.
+     * @param documentId the document to return.
      */
-    public abstract Cursor queryDocument(String docId) throws FileNotFoundException;
+    public abstract Cursor queryDocument(String documentId, String[] projection)
+            throws FileNotFoundException;
 
     /**
      * Return the children of the given document which is a directory.
      *
-     * @param docId the directory to return children for.
+     * @param parentDocumentId the directory to return children for.
      */
-    public abstract Cursor queryDocumentChildren(String docId) throws FileNotFoundException;
+    public abstract Cursor queryChildDocuments(
+            String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException;
 
     /**
      * Return documents that that match the given query, starting the search at
      * the given directory.
      *
-     * @param docId the directory to start search at.
+     * @param parentDocumentId the directory to start search at.
      */
     @SuppressWarnings("unused")
-    public Cursor querySearch(String docId, String query) throws FileNotFoundException {
+    public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection)
+            throws FileNotFoundException {
         throw new UnsupportedOperationException("Search not supported");
     }
 
     /**
      * Return MIME type for the given document. Must match the value of
-     * {@link DocumentColumns#MIME_TYPE} for this document.
+     * {@link Document#COLUMN_MIME_TYPE} for this document.
      */
-    public String getType(String docId) throws FileNotFoundException {
-        final Cursor cursor = queryDocument(docId);
+    public String getDocumentType(String documentId) throws FileNotFoundException {
+        final Cursor cursor = queryDocument(documentId, null);
         try {
             if (cursor.moveToFirst()) {
-                return cursor.getString(cursor.getColumnIndexOrThrow(DocumentColumns.MIME_TYPE));
+                return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
             } else {
                 return null;
             }
@@ -233,7 +230,7 @@
      * @param sizeHint hint of the optimal thumbnail dimensions.
      * @param signal used by the caller to signal if the request should be
      *            cancelled.
-     * @see Documents#FLAG_SUPPORTS_THUMBNAIL
+     * @see Document#FLAG_SUPPORTS_THUMBNAIL
      */
     @SuppressWarnings("unused")
     public AssetFileDescriptor openDocumentThumbnail(
@@ -241,17 +238,31 @@
         throw new UnsupportedOperationException("Thumbnails not supported");
     }
 
+    /**
+     * Implementation is provided by the parent class. Cannot be overriden.
+     *
+     * @see #queryRoots(String[])
+     * @see #queryRecentDocuments(String, String[])
+     * @see #queryDocument(String, String[])
+     * @see #queryChildDocuments(String, String[], String)
+     * @see #querySearchDocuments(String, String, String[])
+     */
     @Override
-    public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
+    public final Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
         try {
             switch (mMatcher.match(uri)) {
+                case MATCH_ROOT:
+                    return queryRoots(projection);
+                case MATCH_RECENT:
+                    return queryRecentDocuments(getRootId(uri), projection);
                 case MATCH_DOCUMENT:
-                    return queryDocument(getDocId(uri));
+                    return queryDocument(getDocumentId(uri), projection);
                 case MATCH_CHILDREN:
-                    return queryDocumentChildren(getDocId(uri));
+                    return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
                 case MATCH_SEARCH:
-                    return querySearch(getDocId(uri), getSearchQuery(uri));
+                    return querySearchDocuments(
+                            getDocumentId(uri), getSearchDocumentsQuery(uri), projection);
                 default:
                     throw new UnsupportedOperationException("Unsupported Uri " + uri);
             }
@@ -261,12 +272,17 @@
         }
     }
 
+    /**
+     * Implementation is provided by the parent class. Cannot be overriden.
+     *
+     * @see #getDocumentType(String)
+     */
     @Override
     public final String getType(Uri uri) {
         try {
             switch (mMatcher.match(uri)) {
                 case MATCH_DOCUMENT:
-                    return getType(getDocId(uri));
+                    return getDocumentType(getDocumentId(uri));
                 default:
                     return null;
             }
@@ -276,22 +292,39 @@
         }
     }
 
+    /**
+     * Implementation is provided by the parent class. Throws by default, and
+     * cannot be overriden.
+     *
+     * @see #createDocument(String, String, String)
+     */
     @Override
     public final Uri insert(Uri uri, ContentValues values) {
         throw new UnsupportedOperationException("Insert not supported");
     }
 
+    /**
+     * Implementation is provided by the parent class. Throws by default, and
+     * cannot be overriden.
+     *
+     * @see #deleteDocument(String)
+     */
     @Override
     public final int delete(Uri uri, String selection, String[] selectionArgs) {
         throw new UnsupportedOperationException("Delete not supported");
     }
 
+    /**
+     * Implementation is provided by the parent class. Throws by default, and
+     * cannot be overriden.
+     */
     @Override
     public final int update(
             Uri uri, ContentValues values, String selection, String[] selectionArgs) {
         throw new UnsupportedOperationException("Update not supported");
     }
 
+    /** {@hide} */
     @Override
     public final Bundle callFromPackage(
             String callingPackage, String method, String arg, Bundle extras) {
@@ -300,33 +333,25 @@
             return super.callFromPackage(callingPackage, method, arg, extras);
         }
 
-        // Platform operations require the caller explicitly hold manage
-        // permission; Uri permissions don't extend management operations.
-        getContext().enforceCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_DOCUMENTS, "Document management");
+        // Require that caller can manage given document
+        final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
+        final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
+        getContext().enforceCallingOrSelfUriPermission(
+                documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method);
 
         final Bundle out = new Bundle();
         try {
-            if (METHOD_GET_ROOTS.equals(method)) {
-                final List<DocumentRoot> roots = getDocumentRoots();
-                out.putParcelableList(EXTRA_ROOTS, roots);
+            if (METHOD_CREATE_DOCUMENT.equals(method)) {
+                final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
+                final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
 
-            } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
-                final String docId = extras.getString(DocumentColumns.DOC_ID);
-                final String mimeType = extras.getString(DocumentColumns.MIME_TYPE);
-                final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME);
-
-                // TODO: issue Uri grant towards caller
-                final String newDocId = createDocument(docId, mimeType, displayName);
-                out.putString(DocumentColumns.DOC_ID, newDocId);
-
-            } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
-                final String docId = extras.getString(DocumentColumns.DOC_ID);
-                final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME);
-                renameDocument(docId, displayName);
+                // TODO: issue Uri grant towards calling package
+                // TODO: enforce that package belongs to caller
+                final String newDocumentId = createDocument(documentId, mimeType, displayName);
+                out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
 
             } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
-                final String docId = extras.getString(DocumentColumns.DOC_ID);
+                final String docId = extras.getString(Document.COLUMN_DOCUMENT_ID);
                 deleteDocument(docId);
 
             } else {
@@ -338,47 +363,57 @@
         return out;
     }
 
+    /**
+     * Implementation is provided by the parent class.
+     *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
     @Override
     public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
-        return openDocument(getDocId(uri), mode, null);
+        return openDocument(getDocumentId(uri), mode, null);
     }
 
+    /**
+     * Implementation is provided by the parent class.
+     *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
     @Override
     public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
             throws FileNotFoundException {
-        return openDocument(getDocId(uri), mode, signal);
+        return openDocument(getDocumentId(uri), mode, signal);
     }
 
+    /**
+     * Implementation is provided by the parent class.
+     *
+     * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+     */
     @Override
     public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
             throws FileNotFoundException {
         if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
             final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
-            return openDocumentThumbnail(getDocId(uri), sizeHint, null);
+            return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
         } else {
             return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
         }
     }
 
+    /**
+     * Implementation is provided by the parent class.
+     *
+     * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+     */
     @Override
     public final AssetFileDescriptor openTypedAssetFile(
             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
             throws FileNotFoundException {
         if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
             final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
-            return openDocumentThumbnail(getDocId(uri), sizeHint, signal);
+            return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
         } else {
             return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
         }
     }
-
-    /**
-     * Notify system that {@link #getDocumentRoots()} has changed, usually due to an
-     * account or device change.
-     */
-    public void notifyDocumentRootsChanged() {
-        final Intent intent = new Intent(ACTION_DOCUMENT_ROOT_CHANGED);
-        intent.putExtra(EXTRA_AUTHORITY, mAuthority);
-        getContext().sendBroadcast(intent);
-    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index 6bc554f..e0b8d19 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -20,7 +20,6 @@
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.FragmentManager;
-import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -28,13 +27,13 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Documents;
+import android.provider.DocumentsContract.Document;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.EditText;
 import android.widget.Toast;
 
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 
 /**
  * Dialog to create a new directory.
@@ -67,24 +66,17 @@
                 final String displayName = text1.getText().toString();
 
                 final DocumentsActivity activity = (DocumentsActivity) getActivity();
-                final Document cwd = activity.getCurrentDirectory();
+                final DocumentInfo cwd = activity.getCurrentDirectory();
 
-                final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                        cwd.uri.getAuthority());
                 try {
-                    final String docId = DocumentsContract.createDocument(client,
-                            DocumentsContract.getDocId(cwd.uri), Documents.MIME_TYPE_DIR,
-                            displayName);
+                    final Uri childUri = DocumentsContract.createDocument(
+                            resolver, cwd.uri, Document.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);
+                    final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
                     activity.onDocumentPicked(childDoc);
                 } 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 783b6ff..79d20a4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -61,7 +61,7 @@
 import android.widget.Toast;
 
 import com.android.documentsui.DocumentsActivity.DisplayState;
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 import com.android.internal.util.Predicate;
 import com.google.android.collect.Lists;
 
@@ -81,7 +81,7 @@
 
     private AbsListView mCurrentView;
 
-    private Predicate<Document> mFilter;
+    private Predicate<DocumentInfo> mFilter;
 
     public static final int TYPE_NORMAL = 1;
     public static final int TYPE_SEARCH = 2;
@@ -106,8 +106,8 @@
     }
 
     public static void showSearch(FragmentManager fm, Uri uri, String query) {
-        final Uri searchUri = DocumentsContract.buildSearchUri(
-                uri.getAuthority(), DocumentsContract.getDocId(uri), query);
+        final Uri searchUri = DocumentsContract.buildSearchDocumentsUri(
+                uri.getAuthority(), DocumentsContract.getDocumentId(uri), query);
         show(fm, TYPE_SEARCH, searchUri);
     }
 
@@ -163,21 +163,21 @@
 
                 Uri contentsUri;
                 if (mType == TYPE_NORMAL) {
-                    contentsUri = DocumentsContract.buildChildrenUri(
-                            uri.getAuthority(), DocumentsContract.getDocId(uri));
+                    contentsUri = DocumentsContract.buildChildDocumentsUri(
+                            uri.getAuthority(), DocumentsContract.getDocumentId(uri));
                 } else if (mType == TYPE_RECENT_OPEN) {
                     contentsUri = RecentsProvider.buildRecentOpen();
                 } else {
                     contentsUri = uri;
                 }
 
-                final Comparator<Document> sortOrder;
+                final Comparator<DocumentInfo> sortOrder;
                 if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) {
-                    sortOrder = new Document.LastModifiedComparator();
+                    sortOrder = new DocumentInfo.LastModifiedComparator();
                 } else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) {
-                    sortOrder = new Document.DisplayNameComparator();
+                    sortOrder = new DocumentInfo.DisplayNameComparator();
                 } else if (state.sortOrder == SORT_ORDER_SIZE) {
-                    sortOrder = new Document.SizeComparator();
+                    sortOrder = new DocumentInfo.SizeComparator();
                 } else {
                     throw new IllegalArgumentException("Unknown sort order " + state.sortOrder);
                 }
@@ -258,7 +258,7 @@
     private OnItemClickListener mItemListener = new OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            final Document doc = mAdapter.getItem(position);
+            final DocumentInfo doc = mAdapter.getItem(position);
             if (mFilter.apply(doc)) {
                 ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
             }
@@ -291,11 +291,11 @@
         @Override
         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
             final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
-            final ArrayList<Document> docs = Lists.newArrayList();
+            final ArrayList<DocumentInfo> docs = Lists.newArrayList();
             final int size = checked.size();
             for (int i = 0; i < size; i++) {
                 if (checked.valueAt(i)) {
-                    final Document doc = mAdapter.getItem(checked.keyAt(i));
+                    final DocumentInfo doc = mAdapter.getItem(checked.keyAt(i));
                     docs.add(doc);
                 }
             }
@@ -328,7 +328,7 @@
                 ActionMode mode, int position, long id, boolean checked) {
             if (checked) {
                 // Directories cannot be checked
-                final Document doc = mAdapter.getItem(position);
+                final DocumentInfo doc = mAdapter.getItem(position);
                 if (doc.isDirectory()) {
                     mCurrentView.setItemChecked(position, false);
                 }
@@ -339,9 +339,9 @@
         }
     };
 
-    private void onShareDocuments(List<Document> docs) {
+    private void onShareDocuments(List<DocumentInfo> docs) {
         final ArrayList<Uri> uris = Lists.newArrayList();
-        for (Document doc : docs) {
+        for (DocumentInfo doc : docs) {
             uris.add(doc.uri);
         }
 
@@ -363,12 +363,12 @@
         startActivity(intent);
     }
 
-    private void onDeleteDocuments(List<Document> docs) {
+    private void onDeleteDocuments(List<DocumentInfo> docs) {
         final Context context = getActivity();
         final ContentResolver resolver = context.getContentResolver();
 
         boolean hadTrouble = false;
-        for (Document doc : docs) {
+        for (DocumentInfo doc : docs) {
             if (!doc.isDeleteSupported()) {
                 Log.w(TAG, "Skipping " + doc);
                 hadTrouble = true;
@@ -396,12 +396,12 @@
     }
 
     private class DocumentsAdapter extends BaseAdapter {
-        private List<Document> mDocuments;
+        private List<DocumentInfo> mDocuments;
 
         public DocumentsAdapter() {
         }
 
-        public void swapDocuments(List<Document> documents) {
+        public void swapDocuments(List<DocumentInfo> documents) {
             mDocuments = documents;
 
             if (mDocuments != null && mDocuments.isEmpty()) {
@@ -433,7 +433,7 @@
                 }
             }
 
-            final Document doc = getItem(position);
+            final DocumentInfo doc = getItem(position);
 
             final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
@@ -507,7 +507,7 @@
         }
 
         @Override
-        public Document getItem(int position) {
+        public DocumentInfo getItem(int position) {
             return mDocuments.get(position);
         }
 
@@ -538,8 +538,8 @@
 
             Bitmap result = null;
             try {
-                result = DocumentsContract.getThumbnail(
-                        context.getContentResolver(), uri, mThumbSize);
+                result = DocumentsContract.getDocumentThumbnail(
+                        context.getContentResolver(), uri, mThumbSize, null);
                 if (result != null) {
                     final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
                             context, mThumbSize);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 4ce5ef8..cb92d76 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -28,7 +28,7 @@
 import android.os.CancellationSignal;
 import android.util.Log;
 
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 import com.android.internal.util.Predicate;
 import com.google.android.collect.Lists;
 
@@ -41,7 +41,7 @@
 
 class DirectoryResult implements AutoCloseable {
     Cursor cursor;
-    List<Document> contents = Lists.newArrayList();
+    List<DocumentInfo> contents = Lists.newArrayList();
     Exception e;
 
     @Override
@@ -53,11 +53,11 @@
 public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
 
     private final int mType;
-    private Predicate<Document> mFilter;
-    private Comparator<Document> mSortOrder;
+    private Predicate<DocumentInfo> mFilter;
+    private Comparator<DocumentInfo> mSortOrder;
 
-    public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter,
-            Comparator<Document> sortOrder) {
+    public DirectoryLoader(Context context, Uri uri, int type, Predicate<DocumentInfo> filter,
+            Comparator<DocumentInfo> sortOrder) {
         super(context, uri);
         mType = type;
         mFilter = filter;
@@ -84,15 +84,15 @@
         result.cursor.registerContentObserver(mObserver);
 
         while (cursor.moveToNext()) {
-            Document doc = null;
+            DocumentInfo doc = null;
             switch (mType) {
                 case TYPE_NORMAL:
                 case TYPE_SEARCH:
-                    doc = Document.fromDirectoryCursor(uri, cursor);
+                    doc = DocumentInfo.fromDirectoryCursor(uri, cursor);
                     break;
                 case TYPE_RECENT_OPEN:
                     try {
-                        doc = Document.fromRecentOpenCursor(resolver, cursor);
+                        doc = DocumentInfo.fromRecentOpenCursor(resolver, cursor);
                     } catch (FileNotFoundException e) {
                         Log.w(TAG, "Failed to find recent: " + e);
                     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java
index 0ce5968..54f62ef 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java
@@ -21,11 +21,10 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.provider.DocumentsContract.DocumentRoot;
 import android.util.Log;
 
 /**
- * Handles {@link DocumentRoot} changes which invalidate cached data.
+ * Handles 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 73ca8fa..912d6dc 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -42,7 +42,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.DocumentRoot;
 import android.support.v4.app.ActionBarDrawerToggle;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.widget.DrawerLayout;
@@ -60,8 +59,9 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.model.RootInfo;
 
 import java.io.FileNotFoundException;
 import java.util.Arrays;
@@ -160,7 +160,7 @@
             mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
 
             final Uri rootUri = intent.getData();
-            final DocumentRoot root = mRoots.findRoot(rootUri);
+            final RootInfo root = mRoots.findRoot(rootUri);
             if (root != null) {
                 onRootPicked(root, true);
             } else {
@@ -252,7 +252,7 @@
             mDrawerToggle.setDrawerIndicatorEnabled(true);
 
         } else {
-            final DocumentRoot root = getCurrentRoot();
+            final RootInfo root = getCurrentRoot();
             actionBar.setIcon(root != null ? root.loadIcon(this) : null);
 
             if (mRoots.isRecentsRoot(root)) {
@@ -317,7 +317,7 @@
         super.onPrepareOptionsMenu(menu);
 
         final FragmentManager fm = getFragmentManager();
-        final Document cwd = getCurrentDirectory();
+        final DocumentInfo cwd = getCurrentDirectory();
 
         final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
         final MenuItem search = menu.findItem(R.id.menu_search);
@@ -473,7 +473,7 @@
         }
     };
 
-    public DocumentRoot getCurrentRoot() {
+    public RootInfo getCurrentRoot() {
         if (mStack.size() > 0) {
             return mStack.getRoot(mRoots);
         } else {
@@ -481,7 +481,7 @@
         }
     }
 
-    public Document getCurrentDirectory() {
+    public DocumentInfo getCurrentDirectory() {
         return mStack.peek();
     }
 
@@ -491,7 +491,7 @@
 
     private void onCurrentDirectoryChanged() {
         final FragmentManager fm = getFragmentManager();
-        final Document cwd = getCurrentDirectory();
+        final DocumentInfo cwd = getCurrentDirectory();
 
         if (cwd == null) {
             // No directory means recents
@@ -533,14 +533,14 @@
         onCurrentDirectoryChanged();
     }
 
-    public void onRootPicked(DocumentRoot root, boolean closeDrawer) {
+    public void onRootPicked(RootInfo root, boolean closeDrawer) {
         // Clear entire backstack and start in new root
         mStack.clear();
 
         if (!mRoots.isRecentsRoot(root)) {
             try {
-                final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.docId);
-                onDocumentPicked(Document.fromUri(getContentResolver(), uri));
+                final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId);
+                onDocumentPicked(DocumentInfo.fromUri(getContentResolver(), uri));
             } catch (FileNotFoundException e) {
             }
         } else {
@@ -561,7 +561,7 @@
         finish();
     }
 
-    public void onDocumentPicked(Document doc) {
+    public void onDocumentPicked(DocumentInfo doc) {
         final FragmentManager fm = getFragmentManager();
         if (doc.isDirectory()) {
             // TODO: query display mode user preference for this dir
@@ -591,7 +591,7 @@
         }
     }
 
-    public void onDocumentsPicked(List<Document> docs) {
+    public void onDocumentsPicked(List<DocumentInfo> docs) {
         if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) {
             final int size = docs.size();
             final Uri[] uris = new Uri[size];
@@ -602,21 +602,19 @@
         }
     }
 
-    public void onSaveRequested(Document replaceTarget) {
+    public void onSaveRequested(DocumentInfo replaceTarget) {
         onFinished(replaceTarget.uri);
     }
 
     public void onSaveRequested(String mimeType, String displayName) {
-        final Document cwd = getCurrentDirectory();
+        final DocumentInfo cwd = getCurrentDirectory();
         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);
+            final Uri childUri = DocumentsContract.createDocument(
+                    getContentResolver(), cwd.uri, mimeType, displayName);
             onFinished(childUri);
         } catch (Exception e) {
             Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
@@ -701,7 +699,7 @@
 
     private void dumpStack() {
         Log.d(TAG, "Current stack:");
-        for (Document doc : mStack) {
+        for (DocumentInfo doc : mStack) {
             Log.d(TAG, "--> " + doc);
         }
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
index a9929de..15ad061 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
@@ -16,10 +16,10 @@
 
 package com.android.documentsui;
 
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 import com.android.internal.util.Predicate;
 
-public class MimePredicate implements Predicate<Document> {
+public class MimePredicate implements Predicate<DocumentInfo> {
     private final String[] mFilters;
 
     public MimePredicate(String[] filters) {
@@ -27,7 +27,7 @@
     }
 
     @Override
-    public boolean apply(Document doc) {
+    public boolean apply(DocumentInfo doc) {
         if (doc.isDirectory()) {
             return true;
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 3447a51..f5d87a7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -29,7 +29,6 @@
 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;
@@ -43,6 +42,7 @@
 import android.widget.TextView;
 
 import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.model.RootInfo;
 import com.google.android.collect.Lists;
 
 import libcore.io.IoUtils;
@@ -181,7 +181,7 @@
             final View summaryList = convertView.findViewById(R.id.summary_list);
 
             final DocumentStack stack = getItem(position);
-            final DocumentRoot root = stack.getRoot(roots);
+            final RootInfo root = stack.getRoot(roots);
             icon.setImageDrawable(root.loadIcon(context));
 
             final StringBuilder builder = new StringBuilder();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index aa21457..880a92b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -25,17 +25,21 @@
 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.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
 import android.util.Log;
 
+import com.android.documentsui.model.RootInfo;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Objects;
 import com.google.android.collect.Lists;
 
+import libcore.io.IoUtils;
+
 import java.util.List;
 
 /**
@@ -48,9 +52,9 @@
 
     private final Context mContext;
 
-    public List<DocumentRoot> mRoots = Lists.newArrayList();
+    public List<RootInfo> mRoots = Lists.newArrayList();
 
-    private DocumentRoot mRecentsRoot;
+    private RootInfo mRecentsRoot;
 
     public RootsCache(Context context) {
         mContext = context;
@@ -66,12 +70,10 @@
 
         {
             // Create special root for recents
-            final DocumentRoot root = new DocumentRoot();
-            root.rootType = DocumentRoot.ROOT_TYPE_SHORTCUT;
-            root.docId = null;
+            final RootInfo root = new RootInfo();
+            root.rootType = Root.ROOT_TYPE_SHORTCUT;
             root.icon = R.drawable.ic_dir;
             root.title = mContext.getString(R.string.root_recent);
-            root.summary = null;
             root.availableBytes = -1;
 
             mRoots.add(root);
@@ -89,28 +91,32 @@
 
                 // TODO: remove deprecated customRoots flag
                 // TODO: populate roots on background thread, and cache results
+                final Uri rootsUri = DocumentsContract.buildRootsUri(info.authority);
                 final ContentProviderClient client = resolver
                         .acquireUnstableContentProviderClient(info.authority);
+                Cursor cursor = null;
                 try {
-                    final List<DocumentRoot> roots = DocumentsContract.getDocumentRoots(client);
-                    for (DocumentRoot root : roots) {
-                        root.authority = info.authority;
+                    cursor = client.query(rootsUri, null, null, null, null);
+                    while (cursor.moveToNext()) {
+                        final RootInfo root = RootInfo.fromRootsCursor(info.authority, cursor);
+                        mRoots.add(root);
                     }
-                    mRoots.addAll(roots);
                 } catch (Exception e) {
                     Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e);
                 } finally {
+                    IoUtils.closeQuietly(cursor);
                     ContentProviderClient.closeQuietly(client);
                 }
             }
         }
     }
 
-    public DocumentRoot findRoot(Uri uri) {
+    @Deprecated
+    public RootInfo 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)) {
+        final String docId = DocumentsContract.getDocumentId(uri);
+        for (RootInfo root : mRoots) {
+            if (Objects.equal(root.authority, authority) && Objects.equal(root.documentId, docId)) {
                 return root;
             }
         }
@@ -118,23 +124,23 @@
     }
 
     @GuardedBy("ActivityThread")
-    public DocumentRoot getRecentsRoot() {
+    public RootInfo getRecentsRoot() {
         return mRecentsRoot;
     }
 
     @GuardedBy("ActivityThread")
-    public boolean isRecentsRoot(DocumentRoot root) {
+    public boolean isRecentsRoot(RootInfo root) {
         return mRecentsRoot == root;
     }
 
     @GuardedBy("ActivityThread")
-    public List<DocumentRoot> getRoots() {
+    public List<RootInfo> getRoots() {
         return mRoots;
     }
 
     @GuardedBy("ActivityThread")
     public static Drawable resolveDocumentIcon(Context context, String mimeType) {
-        if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
             return context.getResources().getDrawable(R.drawable.ic_dir);
         } else {
             final PackageManager pm = context.getPackageManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 2cfa841..3102b88 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.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
-import android.provider.DocumentsContract.DocumentRoot;
+import android.provider.DocumentsContract.Root;
 import android.text.format.Formatter;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -40,7 +40,8 @@
 import android.widget.TextView;
 
 import com.android.documentsui.SectionedListAdapter.SectionAdapter;
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
 
 import java.util.Comparator;
 import java.util.List;
@@ -101,8 +102,8 @@
         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 DocumentRoot) {
-                activity.onRootPicked((DocumentRoot) item, true);
+            if (item instanceof RootInfo) {
+                activity.onRootPicked((RootInfo) item, true);
             } else if (item instanceof ResolveInfo) {
                 activity.onAppPicked((ResolveInfo) item);
             } else {
@@ -111,7 +112,7 @@
         }
     };
 
-    private static class RootsAdapter extends ArrayAdapter<DocumentRoot> implements SectionAdapter {
+    private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter {
         private int mHeaderId;
 
         public RootsAdapter(Context context, int headerId) {
@@ -131,15 +132,13 @@
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
             final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
 
-            final DocumentRoot root = getItem(position);
+            final RootInfo root = getItem(position);
             icon.setImageDrawable(root.loadIcon(context));
             title.setText(root.title);
 
             // Device summary is always available space
             final String summaryText;
-            if ((root.rootType == DocumentRoot.ROOT_TYPE_DEVICE
-                    || root.rootType == DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED)
-                    && root.availableBytes >= 0) {
+            if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) {
                 summaryText = context.getString(R.string.root_available_bytes,
                         Formatter.formatFileSize(context, root.availableBytes));
             } else {
@@ -215,28 +214,27 @@
         private final RootsAdapter mDevicesAdvanced;
         private final AppsAdapter mApps;
 
-        public SectionedRootsAdapter(Context context, List<DocumentRoot> roots, Intent includeApps) {
+        public SectionedRootsAdapter(Context context, List<RootInfo> 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 (DocumentRoot root : roots) {
+            for (RootInfo root : roots) {
                 Log.d(TAG, "Found rootType=" + root.rootType);
                 switch (root.rootType) {
-                    case DocumentRoot.ROOT_TYPE_SERVICE:
+                    case Root.ROOT_TYPE_SERVICE:
                         mServices.add(root);
                         break;
-                    case DocumentRoot.ROOT_TYPE_SHORTCUT:
+                    case Root.ROOT_TYPE_SHORTCUT:
                         mShortcuts.add(root);
                         break;
-                    case DocumentRoot.ROOT_TYPE_DEVICE:
-                        mDevices.add(root);
+                    case Root.ROOT_TYPE_DEVICE:
                         mDevicesAdvanced.add(root);
-                        break;
-                    case DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED:
-                        mDevicesAdvanced.add(root);
+                        if ((root.flags & Root.FLAG_ADVANCED) == 0) {
+                            mDevices.add(root);
+                        }
                         break;
                 }
             }
@@ -281,14 +279,14 @@
         }
     }
 
-    public static class RootComparator implements Comparator<DocumentRoot> {
+    public static class RootComparator implements Comparator<RootInfo> {
         @Override
-        public int compare(DocumentRoot lhs, DocumentRoot rhs) {
-            final int score = Document.compareToIgnoreCaseNullable(lhs.title, rhs.title);
+        public int compare(RootInfo lhs, RootInfo rhs) {
+            final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
             if (score != 0) {
                 return score;
             } else {
-                return Document.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
+                return DocumentInfo.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 7e1a297..8b0a974 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
@@ -31,7 +31,7 @@
 import android.widget.EditText;
 import android.widget.ImageView;
 
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 
 /**
  * Display document title editor and save button.
@@ -39,7 +39,7 @@
 public class SaveFragment extends Fragment {
     public static final String TAG = "SaveFragment";
 
-    private Document mReplaceTarget;
+    private DocumentInfo mReplaceTarget;
     private EditText mDisplayName;
     private Button mSave;
     private boolean mIgnoreNextEdit;
@@ -128,7 +128,7 @@
      * without changing the filename. Can be set to {@code null} if user
      * navigates outside the target directory.
      */
-    public void setReplaceTarget(Document replaceTarget) {
+    public void setReplaceTarget(DocumentInfo replaceTarget) {
         mReplaceTarget = replaceTarget;
 
         if (mReplaceTarget != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
similarity index 69%
rename from packages/DocumentsUI/src/com/android/documentsui/model/Document.java
rename to packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 692d171..f6e46a8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -20,8 +20,7 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.DocumentColumns;
-import android.provider.DocumentsContract.Documents;
+import android.provider.DocumentsContract.Document;
 
 import com.android.documentsui.RecentsProvider;
 
@@ -31,9 +30,9 @@
 import java.util.Comparator;
 
 /**
- * Representation of a single document.
+ * Representation of a {@link Document}.
  */
-public class Document {
+public class DocumentInfo {
     public final Uri uri;
     public final String mimeType;
     public final String displayName;
@@ -42,7 +41,7 @@
     public final String summary;
     public final long size;
 
-    private Document(Uri uri, String mimeType, String displayName, long lastModified, int flags,
+    private DocumentInfo(Uri uri, String mimeType, String displayName, long lastModified, int flags,
             String summary, long size) {
         this.uri = uri;
         this.mimeType = mimeType;
@@ -53,23 +52,23 @@
         this.size = size;
     }
 
-    public static Document fromDirectoryCursor(Uri parent, Cursor cursor) {
+    public static DocumentInfo fromDirectoryCursor(Uri parent, Cursor cursor) {
         final String authority = parent.getAuthority();
-        final String docId = getCursorString(cursor, DocumentColumns.DOC_ID);
+        final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
 
         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);
-        final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
-        final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
-        final long size = getCursorLong(cursor, DocumentColumns.SIZE);
+        final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+        final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
+        final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
+        final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+        final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
+        final long size = getCursorLong(cursor, Document.COLUMN_SIZE);
 
-        return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
+        return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size);
     }
 
     @Deprecated
-    public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor)
+    public static DocumentInfo fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor)
             throws FileNotFoundException {
         final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI));
         final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP);
@@ -80,14 +79,14 @@
             if (!cursor.moveToFirst()) {
                 throw new FileNotFoundException("Missing details for " + uri);
             }
-            final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
-            final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
-            final int flags = getCursorInt(cursor, DocumentColumns.FLAGS)
-                    & Documents.FLAG_SUPPORTS_THUMBNAIL;
-            final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
-            final long size = getCursorLong(cursor, DocumentColumns.SIZE);
+            final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+            final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
+            final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS)
+                    & Document.FLAG_SUPPORTS_THUMBNAIL;
+            final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
+            final long size = getCursorLong(cursor, Document.COLUMN_SIZE);
 
-            return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
+            return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size);
         } catch (Throwable t) {
             throw asFileNotFoundException(t);
         } finally {
@@ -95,21 +94,21 @@
         }
     }
 
-    public static Document fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
+    public static DocumentInfo fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
         Cursor cursor = null;
         try {
             cursor = resolver.query(uri, null, null, null, null);
             if (!cursor.moveToFirst()) {
                 throw new FileNotFoundException("Missing details for " + uri);
             }
-            final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
-            final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
-            final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
-            final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
-            final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
-            final long size = getCursorLong(cursor, DocumentColumns.SIZE);
+            final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+            final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
+            final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
+            final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+            final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
+            final long size = getCursorLong(cursor, Document.COLUMN_SIZE);
 
-            return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
+            return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size);
         } catch (Throwable t) {
             throw asFileNotFoundException(t);
         } finally {
@@ -123,30 +122,30 @@
     }
 
     public boolean isCreateSupported() {
-        return (flags & Documents.FLAG_SUPPORTS_CREATE) != 0;
+        return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0;
     }
 
     public boolean isSearchSupported() {
-        return (flags & Documents.FLAG_SUPPORTS_SEARCH) != 0;
+        return (flags & Document.FLAG_DIR_SUPPORTS_SEARCH) != 0;
     }
 
     public boolean isThumbnailSupported() {
-        return (flags & Documents.FLAG_SUPPORTS_THUMBNAIL) != 0;
+        return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
     }
 
     public boolean isDirectory() {
-        return Documents.MIME_TYPE_DIR.equals(mimeType);
+        return Document.MIME_TYPE_DIR.equals(mimeType);
     }
 
     public boolean isGridPreferred() {
-        return (flags & Documents.FLAG_PREFERS_GRID) != 0;
+        return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0;
     }
 
     public boolean isDeleteSupported() {
-        return (flags & Documents.FLAG_SUPPORTS_DELETE) != 0;
+        return (flags & Document.FLAG_SUPPORTS_DELETE) != 0;
     }
 
-    private static String getCursorString(Cursor cursor, String columnName) {
+    public static String getCursorString(Cursor cursor, String columnName) {
         final int index = cursor.getColumnIndex(columnName);
         return (index != -1) ? cursor.getString(index) : null;
     }
@@ -154,7 +153,7 @@
     /**
      * Missing or null values are returned as -1.
      */
-    private static long getCursorLong(Cursor cursor, String columnName) {
+    public static long getCursorLong(Cursor cursor, String columnName) {
         final int index = cursor.getColumnIndex(columnName);
         if (index == -1) return -1;
         final String value = cursor.getString(index);
@@ -166,14 +165,14 @@
         }
     }
 
-    private static int getCursorInt(Cursor cursor, String columnName) {
+    public static int getCursorInt(Cursor cursor, String columnName) {
         final int index = cursor.getColumnIndex(columnName);
         return (index != -1) ? cursor.getInt(index) : 0;
     }
 
-    public static class DisplayNameComparator implements Comparator<Document> {
+    public static class DisplayNameComparator implements Comparator<DocumentInfo> {
         @Override
-        public int compare(Document lhs, Document rhs) {
+        public int compare(DocumentInfo lhs, DocumentInfo rhs) {
             final boolean leftDir = lhs.isDirectory();
             final boolean rightDir = rhs.isDirectory();
 
@@ -185,16 +184,16 @@
         }
     }
 
-    public static class LastModifiedComparator implements Comparator<Document> {
+    public static class LastModifiedComparator implements Comparator<DocumentInfo> {
         @Override
-        public int compare(Document lhs, Document rhs) {
+        public int compare(DocumentInfo lhs, DocumentInfo rhs) {
             return Long.compare(rhs.lastModified, lhs.lastModified);
         }
     }
 
-    public static class SizeComparator implements Comparator<Document> {
+    public static class SizeComparator implements Comparator<DocumentInfo> {
         @Override
-        public int compare(Document lhs, Document rhs) {
+        public int compare(DocumentInfo lhs, DocumentInfo rhs) {
             return Long.compare(rhs.size, lhs.size);
         }
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
index 81f75d2..b123a46 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
@@ -17,11 +17,10 @@
 package com.android.documentsui.model;
 
 import static com.android.documentsui.DocumentsActivity.TAG;
-import static com.android.documentsui.model.Document.asFileNotFoundException;
+import static com.android.documentsui.model.DocumentInfo.asFileNotFoundException;
 
 import android.content.ContentResolver;
 import android.net.Uri;
-import android.provider.DocumentsContract.DocumentRoot;
 import android.util.Log;
 
 import com.android.documentsui.RootsCache;
@@ -33,10 +32,10 @@
 import java.util.LinkedList;
 
 /**
- * Representation of a stack of {@link Document}, usually the result of a
+ * Representation of a stack of {@link DocumentInfo}, usually the result of a
  * user-driven traversal.
  */
-public class DocumentStack extends LinkedList<Document> {
+public class DocumentStack extends LinkedList<DocumentInfo> {
 
     public static String serialize(DocumentStack stack) {
         final JSONArray json = new JSONArray();
@@ -55,7 +54,7 @@
             final JSONArray json = new JSONArray(raw);
             for (int i = 0; i < json.length(); i++) {
                 final Uri uri = Uri.parse(json.getString(i));
-                final Document doc = Document.fromUri(resolver, uri);
+                final DocumentInfo doc = DocumentInfo.fromUri(resolver, uri);
                 stack.add(doc);
             }
         } catch (JSONException e) {
@@ -66,7 +65,7 @@
         return stack;
     }
 
-    public DocumentRoot getRoot(RootsCache roots) {
+    public RootInfo getRoot(RootsCache roots) {
         return roots.findRoot(getLast().uri);
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
new file mode 100644
index 0000000..f7027f3
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -0,0 +1,72 @@
+/*
+ * 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 static com.android.documentsui.model.DocumentInfo.getCursorInt;
+import static com.android.documentsui.model.DocumentInfo.getCursorLong;
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.provider.DocumentsContract.Root;
+
+/**
+ * Representation of a {@link Root}.
+ */
+public class RootInfo {
+    public String authority;
+    public String rootId;
+    public int rootType;
+    public int flags;
+    public int icon;
+    public String title;
+    public String summary;
+    public String documentId;
+    public long availableBytes;
+
+    public static RootInfo fromRootsCursor(String authority, Cursor cursor) {
+        final RootInfo root = new RootInfo();
+        root.authority = authority;
+        root.rootId = getCursorString(cursor, Root.COLUMN_ROOT_ID);
+        root.rootType = getCursorInt(cursor, Root.COLUMN_ROOT_TYPE);
+        root.flags = getCursorInt(cursor, Root.COLUMN_FLAGS);
+        root.icon = getCursorInt(cursor, Root.COLUMN_ICON);
+        root.title = getCursorString(cursor, Root.COLUMN_TITLE);
+        root.summary = getCursorString(cursor, Root.COLUMN_SUMMARY);
+        root.documentId = getCursorString(cursor, Root.COLUMN_DOCUMENT_ID);
+        root.availableBytes = getCursorLong(cursor, Root.COLUMN_AVAILABLE_BYTES);
+        return root;
+    }
+
+    public Drawable loadIcon(Context context) {
+        if (icon != 0) {
+            if (authority != null) {
+                final PackageManager pm = context.getPackageManager();
+                final ProviderInfo info = pm.resolveContentProvider(authority, 0);
+                if (info != null) {
+                    return pm.getDrawable(info.packageName, icon, info.applicationInfo);
+                }
+            } else {
+                return context.getResources().getDrawable(icon);
+            }
+        }
+        return null;
+    }
+}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 583ecc9..de8c29a 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -26,9 +26,8 @@
 import android.os.CancellationSignal;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
-import android.provider.DocumentsContract.DocumentColumns;
-import android.provider.DocumentsContract.DocumentRoot;
-import android.provider.DocumentsContract.Documents;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
 import android.provider.DocumentsProvider;
 import android.webkit.MimeTypeMap;
 
@@ -41,7 +40,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 
 public class ExternalStorageProvider extends DocumentsProvider {
@@ -49,36 +47,54 @@
 
     // docId format: root:path/to/file
 
-    private static final String[] SUPPORTED_COLUMNS = new String[] {
-            DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE,
-            DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS
+    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
+            Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
+            Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+            Root.COLUMN_AVAILABLE_BYTES,
     };
 
-    private ArrayList<DocumentRoot> mRoots;
-    private HashMap<String, DocumentRoot> mTagToRoot;
-    private HashMap<String, File> mTagToPath;
+    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
+            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
+            Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
+    };
+
+    private static class RootInfo {
+        public String rootId;
+        public int rootType;
+        public int flags;
+        public int icon;
+        public String title;
+        public String docId;
+    }
+
+    private ArrayList<RootInfo> mRoots;
+    private HashMap<String, RootInfo> mIdToRoot;
+    private HashMap<String, File> mIdToPath;
 
     @Override
     public boolean onCreate() {
         mRoots = Lists.newArrayList();
-        mTagToRoot = Maps.newHashMap();
-        mTagToPath = Maps.newHashMap();
+        mIdToRoot = Maps.newHashMap();
+        mIdToPath = Maps.newHashMap();
 
         // TODO: support multiple storage devices
 
         try {
-            final String tag = "primary";
+            final String rootId = "primary";
             final File path = Environment.getExternalStorageDirectory();
-            mTagToPath.put(tag, path);
+            mIdToPath.put(rootId, path);
 
-            final DocumentRoot root = new DocumentRoot();
-            root.docId = getDocIdForFile(path);
-            root.rootType = DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED;
-            root.title = getContext().getString(R.string.root_internal_storage);
+            final RootInfo root = new RootInfo();
+            root.rootId = "primary";
+            root.rootType = Root.ROOT_TYPE_DEVICE;
+            root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
+                    | Root.FLAG_PROVIDES_AUDIO | Root.FLAG_PROVIDES_VIDEO
+                    | Root.FLAG_PROVIDES_IMAGES;
             root.icon = R.drawable.ic_pdf;
-            root.flags = DocumentRoot.FLAG_LOCAL_ONLY;
+            root.title = getContext().getString(R.string.root_internal_storage);
+            root.docId = getDocIdForFile(path);
             mRoots.add(root);
-            mTagToRoot.put(tag, root);
+            mIdToRoot.put(rootId, root);
         } catch (FileNotFoundException e) {
             throw new IllegalStateException(e);
         }
@@ -86,12 +102,20 @@
         return true;
     }
 
+    private static String[] resolveRootProjection(String[] projection) {
+        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
+    }
+
+    private static String[] resolveDocumentProjection(String[] projection) {
+        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
+    }
+
     private String getDocIdForFile(File file) throws FileNotFoundException {
         String path = file.getAbsolutePath();
 
         // Find the most-specific root path
         Map.Entry<String, File> mostSpecific = null;
-        for (Map.Entry<String, File> root : mTagToPath.entrySet()) {
+        for (Map.Entry<String, File> root : mIdToPath.entrySet()) {
             final String rootPath = root.getValue().getPath();
             if (path.startsWith(rootPath) && (mostSpecific == null
                     || rootPath.length() > mostSpecific.getValue().getPath().length())) {
@@ -121,7 +145,7 @@
         final String tag = docId.substring(0, splitIndex);
         final String path = docId.substring(splitIndex + 1);
 
-        File target = mTagToPath.get(tag);
+        File target = mIdToPath.get(tag);
         if (target == null) {
             throw new FileNotFoundException("No root for " + tag);
         }
@@ -143,41 +167,48 @@
         int flags = 0;
 
         if (file.isDirectory()) {
-            flags |= Documents.FLAG_SUPPORTS_SEARCH;
+            flags |= Document.FLAG_DIR_SUPPORTS_SEARCH;
         }
         if (file.isDirectory() && file.canWrite()) {
-            flags |= Documents.FLAG_SUPPORTS_CREATE;
+            flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
         }
         if (file.canWrite()) {
-            flags |= Documents.FLAG_SUPPORTS_WRITE;
-            flags |= Documents.FLAG_SUPPORTS_RENAME;
-            flags |= Documents.FLAG_SUPPORTS_DELETE;
+            flags |= Document.FLAG_SUPPORTS_WRITE;
+            flags |= Document.FLAG_SUPPORTS_DELETE;
         }
 
         final String displayName = file.getName();
         final String mimeType = getTypeForFile(file);
         if (mimeType.startsWith("image/")) {
-            flags |= Documents.FLAG_SUPPORTS_THUMBNAIL;
+            flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
         }
 
         final RowBuilder row = result.newRow();
-        row.offer(DocumentColumns.DOC_ID, docId);
-        row.offer(DocumentColumns.DISPLAY_NAME, displayName);
-        row.offer(DocumentColumns.SIZE, file.length());
-        row.offer(DocumentColumns.MIME_TYPE, mimeType);
-        row.offer(DocumentColumns.LAST_MODIFIED, file.lastModified());
-        row.offer(DocumentColumns.FLAGS, flags);
+        row.offer(Document.COLUMN_DOCUMENT_ID, docId);
+        row.offer(Document.COLUMN_DISPLAY_NAME, displayName);
+        row.offer(Document.COLUMN_SIZE, file.length());
+        row.offer(Document.COLUMN_MIME_TYPE, mimeType);
+        row.offer(Document.COLUMN_LAST_MODIFIED, file.lastModified());
+        row.offer(Document.COLUMN_FLAGS, flags);
     }
 
     @Override
-    public List<DocumentRoot> getDocumentRoots() {
-        // Update free space
-        for (String tag : mTagToRoot.keySet()) {
-            final DocumentRoot root = mTagToRoot.get(tag);
-            final File path = mTagToPath.get(tag);
-            root.availableBytes = path.getFreeSpace();
+    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
+        for (String rootId : mIdToPath.keySet()) {
+            final RootInfo root = mIdToRoot.get(rootId);
+            final File path = mIdToPath.get(rootId);
+
+            final RowBuilder row = result.newRow();
+            row.offer(Root.COLUMN_ROOT_ID, root.rootId);
+            row.offer(Root.COLUMN_ROOT_TYPE, root.rootType);
+            row.offer(Root.COLUMN_FLAGS, root.flags);
+            row.offer(Root.COLUMN_ICON, root.icon);
+            row.offer(Root.COLUMN_TITLE, root.title);
+            row.offer(Root.COLUMN_DOCUMENT_ID, root.docId);
+            row.offer(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace());
         }
-        return mRoots;
+        return result;
     }
 
     @Override
@@ -187,7 +218,7 @@
         displayName = validateDisplayName(mimeType, displayName);
 
         final File file = new File(parent, displayName);
-        if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
             if (!file.mkdir()) {
                 throw new IllegalStateException("Failed to mkdir " + file);
             }
@@ -204,16 +235,6 @@
     }
 
     @Override
-    public void renameDocument(String docId, String displayName) throws FileNotFoundException {
-        final File file = getFileForDocId(docId);
-        final File newFile = new File(file.getParentFile(), displayName);
-        if (!file.renameTo(newFile)) {
-            throw new IllegalStateException("Failed to rename " + docId);
-        }
-        // TODO: update any outstanding grants
-    }
-
-    @Override
     public void deleteDocument(String docId) throws FileNotFoundException {
         final File file = getFileForDocId(docId);
         if (!file.delete()) {
@@ -222,16 +243,19 @@
     }
 
     @Override
-    public Cursor queryDocument(String docId) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
-        includeFile(result, docId, null);
+    public Cursor queryDocument(String documentId, String[] projection)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        includeFile(result, documentId, null);
         return result;
     }
 
     @Override
-    public Cursor queryDocumentChildren(String docId) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
-        final File parent = getFileForDocId(docId);
+    public Cursor queryChildDocuments(
+            String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        final File parent = getFileForDocId(parentDocumentId);
         for (File file : parent.listFiles()) {
             includeFile(result, null, file);
         }
@@ -239,9 +263,10 @@
     }
 
     @Override
-    public Cursor querySearch(String docId, String query) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
-        final File parent = getFileForDocId(docId);
+    public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        final File parent = getFileForDocId(parentDocumentId);
 
         final LinkedList<File> pending = new LinkedList<File>();
         pending.add(parent);
@@ -261,22 +286,24 @@
     }
 
     @Override
-    public String getType(String docId) throws FileNotFoundException {
-        final File file = getFileForDocId(docId);
+    public String getDocumentType(String documentId) throws FileNotFoundException {
+        final File file = getFileForDocId(documentId);
         return getTypeForFile(file);
     }
 
     @Override
-    public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
+    public ParcelFileDescriptor openDocument(
+            String documentId, String mode, CancellationSignal signal)
             throws FileNotFoundException {
-        final File file = getFileForDocId(docId);
+        final File file = getFileForDocId(documentId);
         return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(null, mode));
     }
 
     @Override
     public AssetFileDescriptor openDocumentThumbnail(
-            String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
-        final File file = getFileForDocId(docId);
+            String documentId, Point sizeHint, CancellationSignal signal)
+            throws FileNotFoundException {
+        final File file = getFileForDocId(documentId);
         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
                 file, ParcelFileDescriptor.MODE_READ_ONLY);
 
@@ -294,7 +321,7 @@
 
     private static String getTypeForFile(File file) {
         if (file.isDirectory()) {
-            return Documents.MIME_TYPE_DIR;
+            return Document.MIME_TYPE_DIR;
         } else {
             return getTypeForName(file.getName());
         }
@@ -314,7 +341,7 @@
     }
 
     private static String validateDisplayName(String mimeType, String displayName) {
-        if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
             return displayName;
         } else {
             // Try appending meaningful extension if needed