am 8c58b792: am cd6d7dee: Merge "Refactoring of DocumentsContract." into klp-dev

* commit '8c58b79243748eeb98b650c8e660a742ad876452':
  Refactoring of DocumentsContract.
diff --git a/api/current.txt b/api/current.txt
index ed62477..f05b105 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20840,67 +20840,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;
@@ -20908,10 +20910,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