Home | History | Annotate | Download | only in provider
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.provider;
     18 
     19 import static android.net.TrafficStats.KB_IN_BYTES;
     20 import static android.system.OsConstants.SEEK_SET;
     21 
     22 import static com.android.internal.util.Preconditions.checkArgument;
     23 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
     24 import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
     25 
     26 import android.annotation.Nullable;
     27 import android.content.ContentProviderClient;
     28 import android.content.ContentResolver;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.IntentSender;
     32 import android.content.pm.ResolveInfo;
     33 import android.content.res.AssetFileDescriptor;
     34 import android.database.Cursor;
     35 import android.graphics.Bitmap;
     36 import android.graphics.BitmapFactory;
     37 import android.graphics.Matrix;
     38 import android.graphics.Point;
     39 import android.media.ExifInterface;
     40 import android.net.Uri;
     41 import android.os.Build;
     42 import android.os.Bundle;
     43 import android.os.CancellationSignal;
     44 import android.os.OperationCanceledException;
     45 import android.os.Parcel;
     46 import android.os.ParcelFileDescriptor;
     47 import android.os.ParcelFileDescriptor.OnCloseListener;
     48 import android.os.Parcelable;
     49 import android.os.ParcelableException;
     50 import android.os.RemoteException;
     51 import android.os.storage.StorageVolume;
     52 import android.system.ErrnoException;
     53 import android.system.Os;
     54 import android.util.Log;
     55 
     56 import libcore.io.IoUtils;
     57 
     58 import java.io.BufferedInputStream;
     59 import java.io.File;
     60 import java.io.FileDescriptor;
     61 import java.io.FileInputStream;
     62 import java.io.FileNotFoundException;
     63 import java.io.IOException;
     64 import java.util.List;
     65 import java.util.Objects;
     66 
     67 /**
     68  * Defines the contract between a documents provider and the platform.
     69  * <p>
     70  * To create a document provider, extend {@link DocumentsProvider}, which
     71  * provides a foundational implementation of this contract.
     72  * <p>
     73  * All client apps must hold a valid URI permission grant to access documents,
     74  * typically issued when a user makes a selection through
     75  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
     76  * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
     77  * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
     78  *
     79  * @see DocumentsProvider
     80  */
     81 public final class DocumentsContract {
     82     private static final String TAG = "DocumentsContract";
     83 
     84     // content://com.example/root/
     85     // content://com.example/root/sdcard/
     86     // content://com.example/root/sdcard/recent/
     87     // content://com.example/root/sdcard/search/?query=pony
     88     // content://com.example/document/12/
     89     // content://com.example/document/12/children/
     90     // content://com.example/tree/12/document/24/
     91     // content://com.example/tree/12/document/24/children/
     92 
     93     private DocumentsContract() {
     94     }
     95 
     96     /**
     97      * Intent action used to identify {@link DocumentsProvider} instances. This
     98      * is used in the {@code <intent-filter>} of a {@code <provider>}.
     99      */
    100     public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
    101 
    102     /** {@hide} */
    103     public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
    104 
    105     /** {@hide} */
    106     public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
    107 
    108     /** {@hide} */
    109     public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI";
    110 
    111     /**
    112      * Sets the desired initial location visible to user when file chooser is shown.
    113      *
    114      * <p>Applicable to {@link Intent} with actions:
    115      * <ul>
    116      *      <li>{@link Intent#ACTION_OPEN_DOCUMENT}</li>
    117      *      <li>{@link Intent#ACTION_CREATE_DOCUMENT}</li>
    118      *      <li>{@link Intent#ACTION_OPEN_DOCUMENT_TREE}</li>
    119      * </ul>
    120      *
    121      * <p>Location should specify a document URI or a tree URI with document ID. If
    122      * this URI identifies a non-directory, document navigator will attempt to use the parent
    123      * of the document as the initial location.
    124      *
    125      * <p>The initial location is system specific if this extra is missing or document navigator
    126      * failed to locate the desired initial location.
    127      */
    128     public static final String EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI";
    129 
    130     /**
    131      * Set this in a DocumentsUI intent to cause a package's own roots to be
    132      * excluded from the roots list.
    133      */
    134     public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
    135 
    136     /**
    137      * Included in {@link AssetFileDescriptor#getExtras()} when returned
    138      * thumbnail should be rotated.
    139      *
    140      * @see MediaStore.Images.ImageColumns#ORIENTATION
    141      */
    142     public static final String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION";
    143 
    144     /**
    145      * Overrides the default prompt text in DocumentsUI when set in an intent.
    146      */
    147     public static final String EXTRA_PROMPT = "android.provider.extra.PROMPT";
    148 
    149     /**
    150      * Action of intent issued by DocumentsUI when user wishes to open/configure/manage a particular
    151      * document in the provider application.
    152      *
    153      * <p>When issued, the intent will include the URI of the document as the intent data.
    154      *
    155      * <p>A provider wishing to provide support for this action should do two things.
    156      * <li>Add an {@code <intent-filter>} matching this action.
    157      * <li>When supplying information in {@link DocumentsProvider#queryChildDocuments}, include
    158      * {@link Document#FLAG_SUPPORTS_SETTINGS} in the flags for each document that supports
    159      * settings.
    160      *
    161      * @see DocumentsContact#Document#FLAG_SUPPORTS_SETTINGS
    162      */
    163     public static final String
    164             ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS";
    165 
    166     /** {@hide} */
    167     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
    168 
    169     /** {@hide} */
    170     public static final String
    171             ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
    172 
    173     /**
    174      * Buffer is large enough to rewind past any EXIF headers.
    175      */
    176     private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
    177 
    178     /** {@hide} */
    179     public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
    180             "com.android.externalstorage.documents";
    181 
    182     /** {@hide} */
    183     public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui";
    184 
    185     /** {@hide} */
    186     public static final String METADATA_TYPES = "android:documentMetadataType";
    187 
    188     /** {@hide} */
    189     public static final String METADATA_EXIF = "android:documentExif";
    190 
    191     /** {@hide} */
    192     public static final String EXTRA_METADATA_TAGS = "android:documentMetadataTags";
    193 
    194     /**
    195      * Constants related to a document, including {@link Cursor} column names
    196      * and flags.
    197      * <p>
    198      * A document can be either an openable stream (with a specific MIME type),
    199      * or a directory containing additional documents (with the
    200      * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a
    201      * subtree containing zero or more documents, which can recursively contain
    202      * even more documents and directories.
    203      * <p>
    204      * All columns are <em>read-only</em> to client applications.
    205      */
    206     public final static class Document {
    207         private Document() {
    208         }
    209 
    210         /**
    211          * Unique ID of a document. This ID is both provided by and interpreted
    212          * by a {@link DocumentsProvider}, and should be treated as an opaque
    213          * value by client applications. This column is required.
    214          * <p>
    215          * Each document must have a unique ID within a provider, but that
    216          * single document may be included as a child of multiple directories.
    217          * <p>
    218          * A provider must always return durable IDs, since they will be used to
    219          * issue long-term URI permission grants when an application interacts
    220          * with {@link Intent#ACTION_OPEN_DOCUMENT} and
    221          * {@link Intent#ACTION_CREATE_DOCUMENT}.
    222          * <p>
    223          * Type: STRING
    224          */
    225         public static final String COLUMN_DOCUMENT_ID = "document_id";
    226 
    227         /**
    228          * Concrete MIME type of a document. For example, "image/png" or
    229          * "application/pdf" for openable files. A document can also be a
    230          * directory containing additional documents, which is represented with
    231          * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
    232          * <p>
    233          * Type: STRING
    234          *
    235          * @see #MIME_TYPE_DIR
    236          */
    237         public static final String COLUMN_MIME_TYPE = "mime_type";
    238 
    239         /**
    240          * Display name of a document, used as the primary title displayed to a
    241          * user. This column is required.
    242          * <p>
    243          * Type: STRING
    244          */
    245         public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
    246 
    247         /**
    248          * Summary of a document, which may be shown to a user. This column is
    249          * optional, and may be {@code null}.
    250          * <p>
    251          * Type: STRING
    252          */
    253         public static final String COLUMN_SUMMARY = "summary";
    254 
    255         /**
    256          * Timestamp when a document was last modified, in milliseconds since
    257          * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
    258          * {@code null} if unknown. A {@link DocumentsProvider} can update this
    259          * field using events from {@link OnCloseListener} or other reliable
    260          * {@link ParcelFileDescriptor} transports.
    261          * <p>
    262          * Type: INTEGER (long)
    263          *
    264          * @see System#currentTimeMillis()
    265          */
    266         public static final String COLUMN_LAST_MODIFIED = "last_modified";
    267 
    268         /**
    269          * Specific icon resource ID for a document. This column is optional,
    270          * and may be {@code null} to use a platform-provided default icon based
    271          * on {@link #COLUMN_MIME_TYPE}.
    272          * <p>
    273          * Type: INTEGER (int)
    274          */
    275         public static final String COLUMN_ICON = "icon";
    276 
    277         /**
    278          * Flags that apply to a document. This column is required.
    279          * <p>
    280          * Type: INTEGER (int)
    281          *
    282          * @see #FLAG_SUPPORTS_WRITE
    283          * @see #FLAG_SUPPORTS_DELETE
    284          * @see #FLAG_SUPPORTS_THUMBNAIL
    285          * @see #FLAG_DIR_PREFERS_GRID
    286          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
    287          * @see #FLAG_VIRTUAL_DOCUMENT
    288          * @see #FLAG_SUPPORTS_COPY
    289          * @see #FLAG_SUPPORTS_MOVE
    290          * @see #FLAG_SUPPORTS_REMOVE
    291          */
    292         public static final String COLUMN_FLAGS = "flags";
    293 
    294         /**
    295          * Size of a document, in bytes, or {@code null} if unknown. This column
    296          * is required.
    297          * <p>
    298          * Type: INTEGER (long)
    299          */
    300         public static final String COLUMN_SIZE = OpenableColumns.SIZE;
    301 
    302         /**
    303          * MIME type of a document which is a directory that may contain
    304          * additional documents.
    305          *
    306          * @see #COLUMN_MIME_TYPE
    307          */
    308         public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
    309 
    310         /**
    311          * Flag indicating that a document can be represented as a thumbnail.
    312          *
    313          * @see #COLUMN_FLAGS
    314          * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
    315          *      Point, CancellationSignal)
    316          * @see DocumentsProvider#openDocumentThumbnail(String, Point,
    317          *      android.os.CancellationSignal)
    318          */
    319         public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
    320 
    321         /**
    322          * Flag indicating that a document supports writing.
    323          * <p>
    324          * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
    325          * the calling application is granted both
    326          * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
    327          * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
    328          * writability of a document may change over time, for example due to
    329          * remote access changes. This flag indicates that a document client can
    330          * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
    331          *
    332          * @see #COLUMN_FLAGS
    333          */
    334         public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
    335 
    336         /**
    337          * Flag indicating that a document is deletable.
    338          *
    339          * @see #COLUMN_FLAGS
    340          * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
    341          * @see DocumentsProvider#deleteDocument(String)
    342          */
    343         public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
    344 
    345         /**
    346          * Flag indicating that a document is a directory that supports creation
    347          * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
    348          * {@link #MIME_TYPE_DIR}.
    349          *
    350          * @see #COLUMN_FLAGS
    351          * @see DocumentsProvider#createDocument(String, String, String)
    352          */
    353         public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
    354 
    355         /**
    356          * Flag indicating that a directory prefers its contents be shown in a
    357          * larger format grid. Usually suitable when a directory contains mostly
    358          * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
    359          * {@link #MIME_TYPE_DIR}.
    360          *
    361          * @see #COLUMN_FLAGS
    362          */
    363         public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
    364 
    365         /**
    366          * Flag indicating that a directory prefers its contents be sorted by
    367          * {@link #COLUMN_LAST_MODIFIED}. Only valid when
    368          * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
    369          *
    370          * @see #COLUMN_FLAGS
    371          */
    372         public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
    373 
    374         /**
    375          * Flag indicating that a document can be renamed.
    376          *
    377          * @see #COLUMN_FLAGS
    378          * @see DocumentsContract#renameDocument(ContentResolver, Uri,
    379          *      String)
    380          * @see DocumentsProvider#renameDocument(String, String)
    381          */
    382         public static final int FLAG_SUPPORTS_RENAME = 1 << 6;
    383 
    384         /**
    385          * Flag indicating that a document can be copied to another location
    386          * within the same document provider.
    387          *
    388          * @see #COLUMN_FLAGS
    389          * @see DocumentsContract#copyDocument(ContentResolver, Uri, Uri)
    390          * @see DocumentsProvider#copyDocument(String, String)
    391          */
    392         public static final int FLAG_SUPPORTS_COPY = 1 << 7;
    393 
    394         /**
    395          * Flag indicating that a document can be moved to another location
    396          * within the same document provider.
    397          *
    398          * @see #COLUMN_FLAGS
    399          * @see DocumentsContract#moveDocument(ContentResolver, Uri, Uri, Uri)
    400          * @see DocumentsProvider#moveDocument(String, String, String)
    401          */
    402         public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
    403 
    404         /**
    405          * Flag indicating that a document is virtual, and doesn't have byte
    406          * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
    407          *
    408          * <p><em>Virtual documents must have at least one alternative streamable
    409          * format via {@link DocumentsProvider#openTypedDocument}</em>
    410          *
    411          * @see #COLUMN_FLAGS
    412          * @see #COLUMN_MIME_TYPE
    413          * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
    414          *      android.os.CancellationSignal)
    415          * @see DocumentsProvider#getDocumentStreamTypes(String, String)
    416          */
    417         public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
    418 
    419         /**
    420          * Flag indicating that a document can be removed from a parent.
    421          *
    422          * @see #COLUMN_FLAGS
    423          * @see DocumentsContract#removeDocument(ContentResolver, Uri, Uri)
    424          * @see DocumentsProvider#removeDocument(String, String)
    425          */
    426         public static final int FLAG_SUPPORTS_REMOVE = 1 << 10;
    427 
    428         /**
    429          * Flag indicating that a document has settings that can be configured by user.
    430          *
    431          * @see #COLUMN_FLAGS
    432          * @see #ACTION_DOCUMENT_SETTINGS
    433          */
    434         public static final int FLAG_SUPPORTS_SETTINGS = 1 << 11;
    435 
    436         /**
    437          * Flag indicating that a Web link can be obtained for the document.
    438          *
    439          * @see #COLUMN_FLAGS
    440          * @see DocumentsContract#createWebLinkIntent(PackageManager, Uri, Bundle)
    441          */
    442         public static final int FLAG_WEB_LINKABLE = 1 << 12;
    443 
    444         /**
    445          * Flag indicating that a document is not complete, likely its
    446          * contents are being downloaded. Partial files cannot be opened,
    447          * copied, moved in the UI. But they can be deleted and retried
    448          * if they represent a failed download.
    449          *
    450          * @see #COLUMN_FLAGS
    451          * @hide
    452          */
    453         public static final int FLAG_PARTIAL = 1 << 16;
    454 
    455         /**
    456          * Flag indicating that a document has available metadata that can be read
    457          * using DocumentsContract#getDocumentMetadata
    458          * @hide
    459          */
    460         public static final int FLAG_SUPPORTS_METADATA = 1 << 17;
    461     }
    462 
    463     /**
    464      * Constants related to a root of documents, including {@link Cursor} column
    465      * names and flags. A root is the start of a tree of documents, such as a
    466      * physical storage device, or an account. Each root starts at the directory
    467      * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively
    468      * contain both documents and directories.
    469      * <p>
    470      * All columns are <em>read-only</em> to client applications.
    471      */
    472     public final static class Root {
    473         private Root() {
    474         }
    475 
    476         /**
    477          * Unique ID of a root. This ID is both provided by and interpreted by a
    478          * {@link DocumentsProvider}, and should be treated as an opaque value
    479          * by client applications. This column is required.
    480          * <p>
    481          * Type: STRING
    482          */
    483         public static final String COLUMN_ROOT_ID = "root_id";
    484 
    485         /**
    486          * Flags that apply to a root. This column is required.
    487          * <p>
    488          * Type: INTEGER (int)
    489          *
    490          * @see #FLAG_LOCAL_ONLY
    491          * @see #FLAG_SUPPORTS_CREATE
    492          * @see #FLAG_SUPPORTS_RECENTS
    493          * @see #FLAG_SUPPORTS_SEARCH
    494          */
    495         public static final String COLUMN_FLAGS = "flags";
    496 
    497         /**
    498          * Icon resource ID for a root. This column is required.
    499          * <p>
    500          * Type: INTEGER (int)
    501          */
    502         public static final String COLUMN_ICON = "icon";
    503 
    504         /**
    505          * Title for a root, which will be shown to a user. This column is
    506          * required. For a single storage service surfacing multiple accounts as
    507          * different roots, this title should be the name of the service.
    508          * <p>
    509          * Type: STRING
    510          */
    511         public static final String COLUMN_TITLE = "title";
    512 
    513         /**
    514          * Summary for this root, which may be shown to a user. This column is
    515          * optional, and may be {@code null}. For a single storage service
    516          * surfacing multiple accounts as different roots, this summary should
    517          * be the name of the account.
    518          * <p>
    519          * Type: STRING
    520          */
    521         public static final String COLUMN_SUMMARY = "summary";
    522 
    523         /**
    524          * Document which is a directory that represents the top directory of
    525          * this root. This column is required.
    526          * <p>
    527          * Type: STRING
    528          *
    529          * @see Document#COLUMN_DOCUMENT_ID
    530          */
    531         public static final String COLUMN_DOCUMENT_ID = "document_id";
    532 
    533         /**
    534          * Number of bytes available in this root. This column is optional, and
    535          * may be {@code null} if unknown or unbounded.
    536          * <p>
    537          * Type: INTEGER (long)
    538          */
    539         public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
    540 
    541         /**
    542          * Capacity of a root in bytes. This column is optional, and may be
    543          * {@code null} if unknown or unbounded.
    544          * <p>
    545          * Type: INTEGER (long)
    546          */
    547         public static final String COLUMN_CAPACITY_BYTES = "capacity_bytes";
    548 
    549         /**
    550          * MIME types supported by this root. This column is optional, and if
    551          * {@code null} the root is assumed to support all MIME types. Multiple
    552          * MIME types can be separated by a newline. For example, a root
    553          * supporting audio might return "audio/*\napplication/x-flac".
    554          * <p>
    555          * Type: STRING
    556          */
    557         public static final String COLUMN_MIME_TYPES = "mime_types";
    558 
    559         /**
    560          * MIME type for a root.
    561          */
    562         public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
    563 
    564         /**
    565          * Flag indicating that at least one directory under this root supports
    566          * creating content. Roots with this flag will be shown when an
    567          * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
    568          *
    569          * @see #COLUMN_FLAGS
    570          */
    571         public static final int FLAG_SUPPORTS_CREATE = 1;
    572 
    573         /**
    574          * Flag indicating that this root offers content that is strictly local
    575          * on the device. That is, no network requests are made for the content.
    576          *
    577          * @see #COLUMN_FLAGS
    578          * @see Intent#EXTRA_LOCAL_ONLY
    579          */
    580         public static final int FLAG_LOCAL_ONLY = 1 << 1;
    581 
    582         /**
    583          * Flag indicating that this root can be queried to provide recently
    584          * modified documents.
    585          *
    586          * @see #COLUMN_FLAGS
    587          * @see DocumentsContract#buildRecentDocumentsUri(String, String)
    588          * @see DocumentsProvider#queryRecentDocuments(String, String[])
    589          */
    590         public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
    591 
    592         /**
    593          * Flag indicating that this root supports search.
    594          *
    595          * @see #COLUMN_FLAGS
    596          * @see DocumentsContract#buildSearchDocumentsUri(String, String,
    597          *      String)
    598          * @see DocumentsProvider#querySearchDocuments(String, String,
    599          *      String[])
    600          */
    601         public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
    602 
    603         /**
    604          * Flag indicating that this root supports testing parent child
    605          * relationships.
    606          *
    607          * @see #COLUMN_FLAGS
    608          * @see DocumentsProvider#isChildDocument(String, String)
    609          */
    610         public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
    611 
    612         /**
    613          * Flag indicating that this root can be ejected.
    614          *
    615          * @see #COLUMN_FLAGS
    616          * @see DocumentsContract#ejectRoot(ContentResolver, Uri)
    617          * @see DocumentsProvider#ejectRoot(String)
    618          */
    619         public static final int FLAG_SUPPORTS_EJECT = 1 << 5;
    620 
    621         /**
    622          * Flag indicating that this root is currently empty. This may be used
    623          * to hide the root when opening documents, but the root will still be
    624          * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
    625          * also set. If the value of this flag changes, such as when a root
    626          * becomes non-empty, you must send a content changed notification for
    627          * {@link DocumentsContract#buildRootsUri(String)}.
    628          *
    629          * @see #COLUMN_FLAGS
    630          * @see ContentResolver#notifyChange(Uri,
    631          *      android.database.ContentObserver, boolean)
    632          * @hide
    633          */
    634         public static final int FLAG_EMPTY = 1 << 16;
    635 
    636         /**
    637          * Flag indicating that this root should only be visible to advanced
    638          * users.
    639          *
    640          * @see #COLUMN_FLAGS
    641          * @hide
    642          */
    643         public static final int FLAG_ADVANCED = 1 << 17;
    644 
    645         /**
    646          * Flag indicating that this root has settings.
    647          *
    648          * @see #COLUMN_FLAGS
    649          * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS
    650          * @hide
    651          */
    652         public static final int FLAG_HAS_SETTINGS = 1 << 18;
    653 
    654         /**
    655          * Flag indicating that this root is on removable SD card storage.
    656          *
    657          * @see #COLUMN_FLAGS
    658          * @hide
    659          */
    660         public static final int FLAG_REMOVABLE_SD = 1 << 19;
    661 
    662         /**
    663          * Flag indicating that this root is on removable USB storage.
    664          *
    665          * @see #COLUMN_FLAGS
    666          * @hide
    667          */
    668         public static final int FLAG_REMOVABLE_USB = 1 << 20;
    669     }
    670 
    671     /**
    672      * Optional boolean flag included in a directory {@link Cursor#getExtras()}
    673      * indicating that a document provider is still loading data. For example, a
    674      * provider has returned some results, but is still waiting on an
    675      * outstanding network request. The provider must send a content changed
    676      * notification when loading is finished.
    677      *
    678      * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
    679      *      boolean)
    680      */
    681     public static final String EXTRA_LOADING = "loading";
    682 
    683     /**
    684      * Optional string included in a directory {@link Cursor#getExtras()}
    685      * providing an informational message that should be shown to a user. For
    686      * example, a provider may wish to indicate that not all documents are
    687      * available.
    688      */
    689     public static final String EXTRA_INFO = "info";
    690 
    691     /**
    692      * Optional string included in a directory {@link Cursor#getExtras()}
    693      * providing an error message that should be shown to a user. For example, a
    694      * provider may wish to indicate that a network error occurred. The user may
    695      * choose to retry, resulting in a new query.
    696      */
    697     public static final String EXTRA_ERROR = "error";
    698 
    699     /**
    700      * Optional result (I'm thinking boolean) answer to a question.
    701      * {@hide}
    702      */
    703     public static final String EXTRA_RESULT = "result";
    704 
    705     /** {@hide} */
    706     public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
    707     /** {@hide} */
    708     public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
    709     /** {@hide} */
    710     public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
    711     /** {@hide} */
    712     public static final String METHOD_COPY_DOCUMENT = "android:copyDocument";
    713     /** {@hide} */
    714     public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument";
    715     /** {@hide} */
    716     public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument";
    717     /** {@hide} */
    718     public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument";
    719     /** {@hide} */
    720     public static final String METHOD_EJECT_ROOT = "android:ejectRoot";
    721     /** {@hide} */
    722     public static final String METHOD_FIND_DOCUMENT_PATH = "android:findDocumentPath";
    723     /** {@hide} */
    724     public static final String METHOD_CREATE_WEB_LINK_INTENT = "android:createWebLinkIntent";
    725     /** {@hide} */
    726     public static final String METHOD_GET_DOCUMENT_METADATA = "android:getDocumentMetadata";
    727 
    728     /** {@hide} */
    729     public static final String EXTRA_PARENT_URI = "parentUri";
    730     /** {@hide} */
    731     public static final String EXTRA_URI = "uri";
    732 
    733     /**
    734      * @see #createWebLinkIntent(ContentResolver, Uri, Bundle)
    735      * {@hide}
    736      */
    737     public static final String EXTRA_OPTIONS = "options";
    738 
    739     private static final String PATH_ROOT = "root";
    740     private static final String PATH_RECENT = "recent";
    741     private static final String PATH_DOCUMENT = "document";
    742     private static final String PATH_CHILDREN = "children";
    743     private static final String PATH_SEARCH = "search";
    744     private static final String PATH_TREE = "tree";
    745 
    746     private static final String PARAM_QUERY = "query";
    747     private static final String PARAM_MANAGE = "manage";
    748 
    749     /**
    750      * Build URI representing the roots of a document provider. When queried, a
    751      * provider will return one or more rows with columns defined by
    752      * {@link Root}.
    753      *
    754      * @see DocumentsProvider#queryRoots(String[])
    755      */
    756     public static Uri buildRootsUri(String authority) {
    757         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    758                 .authority(authority).appendPath(PATH_ROOT).build();
    759     }
    760 
    761     /**
    762      * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a
    763      * document provider.
    764      *
    765      * @see #getRootId(Uri)
    766      */
    767     public static Uri buildRootUri(String authority, String rootId) {
    768         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    769                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
    770     }
    771 
    772     /**
    773      * Builds URI for user home directory on external (local) storage.
    774      * {@hide}
    775      */
    776     public static Uri buildHomeUri() {
    777         // TODO: Avoid this type of interpackage copying. Added here to avoid
    778         // direct coupling, but not ideal.
    779         return DocumentsContract.buildRootUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "home");
    780     }
    781 
    782     /**
    783      * Build URI representing the recently modified documents of a specific root
    784      * in a document provider. When queried, a provider will return zero or more
    785      * rows with columns defined by {@link Document}.
    786      *
    787      * @see DocumentsProvider#queryRecentDocuments(String, String[])
    788      * @see #getRootId(Uri)
    789      */
    790     public static Uri buildRecentDocumentsUri(String authority, String rootId) {
    791         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    792                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
    793                 .appendPath(PATH_RECENT).build();
    794     }
    795 
    796     /**
    797      * Build URI representing access to descendant documents of the given
    798      * {@link Document#COLUMN_DOCUMENT_ID}.
    799      *
    800      * @see #getTreeDocumentId(Uri)
    801      */
    802     public static Uri buildTreeDocumentUri(String authority, String documentId) {
    803         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
    804                 .appendPath(PATH_TREE).appendPath(documentId).build();
    805     }
    806 
    807     /**
    808      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
    809      * a document provider. When queried, a provider will return a single row
    810      * with columns defined by {@link Document}.
    811      *
    812      * @see DocumentsProvider#queryDocument(String, String[])
    813      * @see #getDocumentId(Uri)
    814      */
    815     public static Uri buildDocumentUri(String authority, String documentId) {
    816         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    817                 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
    818     }
    819 
    820     /**
    821      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
    822      * a document provider. When queried, a provider will return a single row
    823      * with columns defined by {@link Document}.
    824      * <p>
    825      * However, instead of directly accessing the target document, the returned
    826      * URI will leverage access granted through a subtree URI, typically
    827      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document
    828      * must be a descendant (child, grandchild, etc) of the subtree.
    829      * <p>
    830      * This is typically used to access documents under a user-selected
    831      * directory tree, since it doesn't require the user to separately confirm
    832      * each new document access.
    833      *
    834      * @param treeUri the subtree to leverage to gain access to the target
    835      *            document. The target directory must be a descendant of this
    836      *            subtree.
    837      * @param documentId the target document, which the caller may not have
    838      *            direct access to.
    839      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
    840      * @see DocumentsProvider#isChildDocument(String, String)
    841      * @see #buildDocumentUri(String, String)
    842      */
    843     public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
    844         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    845                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
    846                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
    847                 .appendPath(documentId).build();
    848     }
    849 
    850     /** {@hide} */
    851     public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) {
    852         if (isTreeUri(baseUri)) {
    853             return buildDocumentUriUsingTree(baseUri, documentId);
    854         } else {
    855             return buildDocumentUri(baseUri.getAuthority(), documentId);
    856         }
    857     }
    858 
    859     /**
    860      * Build URI representing the children of the target directory in a document
    861      * provider. When queried, a provider will return zero or more rows with
    862      * columns defined by {@link Document}.
    863      *
    864      * @param parentDocumentId the document to return children for, which must
    865      *            be a directory with MIME type of
    866      *            {@link Document#MIME_TYPE_DIR}.
    867      * @see DocumentsProvider#queryChildDocuments(String, String[], String)
    868      * @see #getDocumentId(Uri)
    869      */
    870     public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
    871         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
    872                 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
    873                 .build();
    874     }
    875 
    876     /**
    877      * Build URI representing the children of the target directory in a document
    878      * provider. When queried, a provider will return zero or more rows with
    879      * columns defined by {@link Document}.
    880      * <p>
    881      * However, instead of directly accessing the target directory, the returned
    882      * URI will leverage access granted through a subtree URI, typically
    883      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target
    884      * directory must be a descendant (child, grandchild, etc) of the subtree.
    885      * <p>
    886      * This is typically used to access documents under a user-selected
    887      * directory tree, since it doesn't require the user to separately confirm
    888      * each new document access.
    889      *
    890      * @param treeUri the subtree to leverage to gain access to the target
    891      *            document. The target directory must be a descendant of this
    892      *            subtree.
    893      * @param parentDocumentId the document to return children for, which the
    894      *            caller may not have direct access to, and which must be a
    895      *            directory with MIME type of {@link Document#MIME_TYPE_DIR}.
    896      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
    897      * @see DocumentsProvider#isChildDocument(String, String)
    898      * @see #buildChildDocumentsUri(String, String)
    899      */
    900     public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) {
    901         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    902                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
    903                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
    904                 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
    905     }
    906 
    907     /**
    908      * Build URI representing a search for matching documents under a specific
    909      * root in a document provider. When queried, a provider will return zero or
    910      * more rows with columns defined by {@link Document}.
    911      *
    912      * @see DocumentsProvider#querySearchDocuments(String, String, String[])
    913      * @see #getRootId(Uri)
    914      * @see #getSearchDocumentsQuery(Uri)
    915      */
    916     public static Uri buildSearchDocumentsUri(
    917             String authority, String rootId, String query) {
    918         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
    919                 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
    920                 .appendQueryParameter(PARAM_QUERY, query).build();
    921     }
    922 
    923     /**
    924      * Test if the given URI represents a {@link Document} backed by a
    925      * {@link DocumentsProvider}.
    926      *
    927      * @see #buildDocumentUri(String, String)
    928      * @see #buildDocumentUriUsingTree(Uri, String)
    929      */
    930     public static boolean isDocumentUri(Context context, @Nullable Uri uri) {
    931         if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
    932             final List<String> paths = uri.getPathSegments();
    933             if (paths.size() == 2) {
    934                 return PATH_DOCUMENT.equals(paths.get(0));
    935             } else if (paths.size() == 4) {
    936                 return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));
    937             }
    938         }
    939         return false;
    940     }
    941 
    942     /** {@hide} */
    943     public static boolean isRootUri(Context context, @Nullable Uri uri) {
    944         if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
    945             final List<String> paths = uri.getPathSegments();
    946             return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0)));
    947         }
    948         return false;
    949     }
    950 
    951     /** {@hide} */
    952     public static boolean isContentUri(@Nullable Uri uri) {
    953         return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme());
    954     }
    955 
    956     /**
    957      * Test if the given URI represents a {@link Document} tree.
    958      *
    959      * @see #buildTreeDocumentUri(String, String)
    960      * @see #getTreeDocumentId(Uri)
    961      */
    962     public static boolean isTreeUri(Uri uri) {
    963         final List<String> paths = uri.getPathSegments();
    964         return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
    965     }
    966 
    967     private static boolean isDocumentsProvider(Context context, String authority) {
    968         final Intent intent = new Intent(PROVIDER_INTERFACE);
    969         final List<ResolveInfo> infos = context.getPackageManager()
    970                 .queryIntentContentProviders(intent, 0);
    971         for (ResolveInfo info : infos) {
    972             if (authority.equals(info.providerInfo.authority)) {
    973                 return true;
    974             }
    975         }
    976         return false;
    977     }
    978 
    979     /**
    980      * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI.
    981      */
    982     public static String getRootId(Uri rootUri) {
    983         final List<String> paths = rootUri.getPathSegments();
    984         if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
    985             return paths.get(1);
    986         }
    987         throw new IllegalArgumentException("Invalid URI: " + rootUri);
    988     }
    989 
    990     /**
    991      * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
    992      *
    993      * @see #isDocumentUri(Context, Uri)
    994      */
    995     public static String getDocumentId(Uri documentUri) {
    996         final List<String> paths = documentUri.getPathSegments();
    997         if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
    998             return paths.get(1);
    999         }
   1000         if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0))
   1001                 && PATH_DOCUMENT.equals(paths.get(2))) {
   1002             return paths.get(3);
   1003         }
   1004         throw new IllegalArgumentException("Invalid URI: " + documentUri);
   1005     }
   1006 
   1007     /**
   1008      * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
   1009      */
   1010     public static String getTreeDocumentId(Uri documentUri) {
   1011         final List<String> paths = documentUri.getPathSegments();
   1012         if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
   1013             return paths.get(1);
   1014         }
   1015         throw new IllegalArgumentException("Invalid URI: " + documentUri);
   1016     }
   1017 
   1018     /**
   1019      * Extract the search query from a URI built by
   1020      * {@link #buildSearchDocumentsUri(String, String, String)}.
   1021      */
   1022     public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
   1023         return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
   1024     }
   1025 
   1026     /** {@hide} */
   1027     public static Uri setManageMode(Uri uri) {
   1028         return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
   1029     }
   1030 
   1031     /** {@hide} */
   1032     public static boolean isManageMode(Uri uri) {
   1033         return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
   1034     }
   1035 
   1036     /**
   1037      * Return thumbnail representing the document at the given URI. Callers are
   1038      * responsible for their own in-memory caching.
   1039      *
   1040      * @param documentUri document to return thumbnail for, which must have
   1041      *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
   1042      * @param size optimal thumbnail size desired. A provider may return a
   1043      *            thumbnail of a different size, but never more than double the
   1044      *            requested size.
   1045      * @param signal signal used to indicate if caller is no longer interested
   1046      *            in the thumbnail.
   1047      * @return decoded thumbnail, or {@code null} if problem was encountered.
   1048      * @see DocumentsProvider#openDocumentThumbnail(String, Point,
   1049      *      android.os.CancellationSignal)
   1050      */
   1051     public static Bitmap getDocumentThumbnail(
   1052             ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal)
   1053             throws FileNotFoundException {
   1054         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1055                 documentUri.getAuthority());
   1056         try {
   1057             return getDocumentThumbnail(client, documentUri, size, signal);
   1058         } catch (Exception e) {
   1059             if (!(e instanceof OperationCanceledException)) {
   1060                 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
   1061             }
   1062             rethrowIfNecessary(resolver, e);
   1063             return null;
   1064         } finally {
   1065             ContentProviderClient.releaseQuietly(client);
   1066         }
   1067     }
   1068 
   1069     /** {@hide} */
   1070     public static Bitmap getDocumentThumbnail(
   1071             ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
   1072             throws RemoteException, IOException {
   1073         final Bundle openOpts = new Bundle();
   1074         openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size);
   1075 
   1076         AssetFileDescriptor afd = null;
   1077         Bitmap bitmap = null;
   1078         try {
   1079             afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
   1080 
   1081             final FileDescriptor fd = afd.getFileDescriptor();
   1082             final long offset = afd.getStartOffset();
   1083 
   1084             // Try seeking on the returned FD, since it gives us the most
   1085             // optimal decode path; otherwise fall back to buffering.
   1086             BufferedInputStream is = null;
   1087             try {
   1088                 Os.lseek(fd, offset, SEEK_SET);
   1089             } catch (ErrnoException e) {
   1090                 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
   1091                 is.mark(THUMBNAIL_BUFFER_SIZE);
   1092             }
   1093 
   1094             // We requested a rough thumbnail size, but the remote size may have
   1095             // returned something giant, so defensively scale down as needed.
   1096             final BitmapFactory.Options opts = new BitmapFactory.Options();
   1097             opts.inJustDecodeBounds = true;
   1098             if (is != null) {
   1099                 BitmapFactory.decodeStream(is, null, opts);
   1100             } else {
   1101                 BitmapFactory.decodeFileDescriptor(fd, null, opts);
   1102             }
   1103 
   1104             final int widthSample = opts.outWidth / size.x;
   1105             final int heightSample = opts.outHeight / size.y;
   1106 
   1107             opts.inJustDecodeBounds = false;
   1108             opts.inSampleSize = Math.min(widthSample, heightSample);
   1109             if (is != null) {
   1110                 is.reset();
   1111                 bitmap = BitmapFactory.decodeStream(is, null, opts);
   1112             } else {
   1113                 try {
   1114                     Os.lseek(fd, offset, SEEK_SET);
   1115                 } catch (ErrnoException e) {
   1116                     e.rethrowAsIOException();
   1117                 }
   1118                 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
   1119             }
   1120 
   1121             // Transform the bitmap if requested. We use a side-channel to
   1122             // communicate the orientation, since EXIF thumbnails don't contain
   1123             // the rotation flags of the original image.
   1124             final Bundle extras = afd.getExtras();
   1125             final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
   1126             if (orientation != 0) {
   1127                 final int width = bitmap.getWidth();
   1128                 final int height = bitmap.getHeight();
   1129 
   1130                 final Matrix m = new Matrix();
   1131                 m.setRotate(orientation, width / 2, height / 2);
   1132                 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
   1133             }
   1134         } finally {
   1135             IoUtils.closeQuietly(afd);
   1136         }
   1137 
   1138         return bitmap;
   1139     }
   1140 
   1141     /**
   1142      * Create a new document with given MIME type and display name.
   1143      *
   1144      * @param parentDocumentUri directory with {@link Document#FLAG_DIR_SUPPORTS_CREATE}
   1145      * @param mimeType MIME type of new document
   1146      * @param displayName name of new document
   1147      * @return newly created document, or {@code null} if failed
   1148      */
   1149     public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
   1150             String mimeType, String displayName) throws FileNotFoundException {
   1151         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1152                 parentDocumentUri.getAuthority());
   1153         try {
   1154             return createDocument(client, parentDocumentUri, mimeType, displayName);
   1155         } catch (Exception e) {
   1156             Log.w(TAG, "Failed to create document", e);
   1157             rethrowIfNecessary(resolver, e);
   1158             return null;
   1159         } finally {
   1160             ContentProviderClient.releaseQuietly(client);
   1161         }
   1162     }
   1163 
   1164     /** {@hide} */
   1165     public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
   1166             String mimeType, String displayName) throws RemoteException {
   1167         final Bundle in = new Bundle();
   1168         in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
   1169         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
   1170         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
   1171 
   1172         final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
   1173         return out.getParcelable(DocumentsContract.EXTRA_URI);
   1174     }
   1175 
   1176     /** {@hide} */
   1177     public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
   1178             Uri childDocumentUri) throws RemoteException {
   1179 
   1180         final Bundle in = new Bundle();
   1181         in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
   1182         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
   1183 
   1184         final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
   1185         if (out == null) {
   1186             throw new RemoteException("Failed to get a reponse from isChildDocument query.");
   1187         }
   1188         if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
   1189             throw new RemoteException("Response did not include result field..");
   1190         }
   1191         return out.getBoolean(DocumentsContract.EXTRA_RESULT);
   1192     }
   1193 
   1194     /**
   1195      * Change the display name of an existing document.
   1196      * <p>
   1197      * If the underlying provider needs to create a new
   1198      * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display
   1199      * name, that new document is returned and the original document is no
   1200      * longer valid. Otherwise, the original document is returned.
   1201      *
   1202      * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME}
   1203      * @param displayName updated name for document
   1204      * @return the existing or new document after the rename, or {@code null} if
   1205      *         failed.
   1206      */
   1207     public static Uri renameDocument(ContentResolver resolver, Uri documentUri,
   1208             String displayName) throws FileNotFoundException {
   1209         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1210                 documentUri.getAuthority());
   1211         try {
   1212             return renameDocument(client, documentUri, displayName);
   1213         } catch (Exception e) {
   1214             Log.w(TAG, "Failed to rename document", e);
   1215             rethrowIfNecessary(resolver, e);
   1216             return null;
   1217         } finally {
   1218             ContentProviderClient.releaseQuietly(client);
   1219         }
   1220     }
   1221 
   1222     /** {@hide} */
   1223     public static Uri renameDocument(ContentProviderClient client, Uri documentUri,
   1224             String displayName) throws RemoteException {
   1225         final Bundle in = new Bundle();
   1226         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
   1227         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
   1228 
   1229         final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
   1230         final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
   1231         return (outUri != null) ? outUri : documentUri;
   1232     }
   1233 
   1234     /**
   1235      * Delete the given document.
   1236      *
   1237      * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
   1238      * @return if the document was deleted successfully.
   1239      */
   1240     public static boolean deleteDocument(ContentResolver resolver, Uri documentUri)
   1241             throws FileNotFoundException {
   1242         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1243                 documentUri.getAuthority());
   1244         try {
   1245             deleteDocument(client, documentUri);
   1246             return true;
   1247         } catch (Exception e) {
   1248             Log.w(TAG, "Failed to delete document", e);
   1249             rethrowIfNecessary(resolver, e);
   1250             return false;
   1251         } finally {
   1252             ContentProviderClient.releaseQuietly(client);
   1253         }
   1254     }
   1255 
   1256     /** {@hide} */
   1257     public static void deleteDocument(ContentProviderClient client, Uri documentUri)
   1258             throws RemoteException {
   1259         final Bundle in = new Bundle();
   1260         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
   1261 
   1262         client.call(METHOD_DELETE_DOCUMENT, null, in);
   1263     }
   1264 
   1265     /**
   1266      * Copies the given document.
   1267      *
   1268      * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_COPY}
   1269      * @param targetParentDocumentUri document which will become a parent of the source
   1270      *         document's copy.
   1271      * @return the copied document, or {@code null} if failed.
   1272      */
   1273     public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri,
   1274             Uri targetParentDocumentUri) throws FileNotFoundException {
   1275         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1276                 sourceDocumentUri.getAuthority());
   1277         try {
   1278             return copyDocument(client, sourceDocumentUri, targetParentDocumentUri);
   1279         } catch (Exception e) {
   1280             Log.w(TAG, "Failed to copy document", e);
   1281             rethrowIfNecessary(resolver, e);
   1282             return null;
   1283         } finally {
   1284             ContentProviderClient.releaseQuietly(client);
   1285         }
   1286     }
   1287 
   1288     /** {@hide} */
   1289     public static Uri copyDocument(ContentProviderClient client, Uri sourceDocumentUri,
   1290             Uri targetParentDocumentUri) throws RemoteException {
   1291         final Bundle in = new Bundle();
   1292         in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
   1293         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
   1294 
   1295         final Bundle out = client.call(METHOD_COPY_DOCUMENT, null, in);
   1296         return out.getParcelable(DocumentsContract.EXTRA_URI);
   1297     }
   1298 
   1299     /**
   1300      * Moves the given document under a new parent.
   1301      *
   1302      * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_MOVE}
   1303      * @param sourceParentDocumentUri parent document of the document to move.
   1304      * @param targetParentDocumentUri document which will become a new parent of the source
   1305      *         document.
   1306      * @return the moved document, or {@code null} if failed.
   1307      */
   1308     public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri,
   1309             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException {
   1310         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1311                 sourceDocumentUri.getAuthority());
   1312         try {
   1313             return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri,
   1314                     targetParentDocumentUri);
   1315         } catch (Exception e) {
   1316             Log.w(TAG, "Failed to move document", e);
   1317             rethrowIfNecessary(resolver, e);
   1318             return null;
   1319         } finally {
   1320             ContentProviderClient.releaseQuietly(client);
   1321         }
   1322     }
   1323 
   1324     /** {@hide} */
   1325     public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri,
   1326             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws RemoteException {
   1327         final Bundle in = new Bundle();
   1328         in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
   1329         in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri);
   1330         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
   1331 
   1332         final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in);
   1333         return out.getParcelable(DocumentsContract.EXTRA_URI);
   1334     }
   1335 
   1336     /**
   1337      * Removes the given document from a parent directory.
   1338      *
   1339      * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
   1340      * This method is especially useful if the document can be in multiple parents.
   1341      *
   1342      * @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE}
   1343      * @param parentDocumentUri parent document of the document to remove.
   1344      * @return true if the document was removed successfully.
   1345      */
   1346     public static boolean removeDocument(ContentResolver resolver, Uri documentUri,
   1347             Uri parentDocumentUri) throws FileNotFoundException {
   1348         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1349                 documentUri.getAuthority());
   1350         try {
   1351             removeDocument(client, documentUri, parentDocumentUri);
   1352             return true;
   1353         } catch (Exception e) {
   1354             Log.w(TAG, "Failed to remove document", e);
   1355             rethrowIfNecessary(resolver, e);
   1356             return false;
   1357         } finally {
   1358             ContentProviderClient.releaseQuietly(client);
   1359         }
   1360     }
   1361 
   1362     /** {@hide} */
   1363     public static void removeDocument(ContentProviderClient client, Uri documentUri,
   1364             Uri parentDocumentUri) throws RemoteException {
   1365         final Bundle in = new Bundle();
   1366         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
   1367         in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri);
   1368 
   1369         client.call(METHOD_REMOVE_DOCUMENT, null, in);
   1370     }
   1371 
   1372     /**
   1373      * Ejects the given root. It throws {@link IllegalStateException} when ejection failed.
   1374      *
   1375      * @param rootUri root with {@link Root#FLAG_SUPPORTS_EJECT} to be ejected
   1376      */
   1377     public static void ejectRoot(ContentResolver resolver, Uri rootUri) {
   1378         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1379                 rootUri.getAuthority());
   1380         try {
   1381             ejectRoot(client, rootUri);
   1382         } catch (RemoteException e) {
   1383             e.rethrowAsRuntimeException();
   1384         } finally {
   1385             ContentProviderClient.releaseQuietly(client);
   1386         }
   1387     }
   1388 
   1389     /** {@hide} */
   1390     public static void ejectRoot(ContentProviderClient client, Uri rootUri)
   1391             throws RemoteException {
   1392         final Bundle in = new Bundle();
   1393         in.putParcelable(DocumentsContract.EXTRA_URI, rootUri);
   1394 
   1395         client.call(METHOD_EJECT_ROOT, null, in);
   1396     }
   1397 
   1398     /**
   1399      * Returns metadata associated with the document. The type of metadata returned
   1400      * is specific to the document type. For example image files will largely return EXIF
   1401      * metadata.
   1402      *
   1403      * <p>The returned {@link Bundle} will contain zero or more entries.
   1404      * <p>Each entry represents a specific type of metadata.
   1405      *
   1406      * <p>if tags == null, then a list of default tags will be used.
   1407      *
   1408      * @param documentUri a Document URI
   1409      * @param tags an array of keys to choose which data are added to the Bundle. If the Document
   1410      *             is a JPG or ExifInterface compatible, send keys from {@link ExifInterface}.
   1411      *             If tags are null, a set of default tags will be used. If the tags don't
   1412      *             match with any relevant data, they will not be added to the Bundle.
   1413      * @return a Bundle of Bundles. If metadata exists within the Bundle, there will also
   1414      * be a String under DocumentsContract.METADATA_TYPES that will return a String[] of the
   1415      * types of metadata gathered.
   1416      *
   1417      * <pre><code>
   1418      *     Bundle metadata = DocumentsContract.getDocumentMetadata(resolver, imageDocUri, tags);
   1419      *     int imageLength = metadata.getInt(ExifInterface.TAG_IMAGE_LENGTH);
   1420      * </code></pre>
   1421      *
   1422      * {@hide}
   1423      */
   1424     public static Bundle getDocumentMetadata(ContentResolver resolver, Uri documentUri,
   1425             @Nullable String[] tags)
   1426             throws FileNotFoundException {
   1427         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1428                 documentUri.getAuthority());
   1429 
   1430         try {
   1431             return getDocumentMetadata(client, documentUri, tags);
   1432         } catch (Exception e) {
   1433             Log.w(TAG, "Failed to get document metadata");
   1434             rethrowIfNecessary(resolver, e);
   1435             return null;
   1436         } finally {
   1437             ContentProviderClient.releaseQuietly(client);
   1438         }
   1439     }
   1440 
   1441     /**
   1442      * Returns metadata associated with the document. The type of metadata returned
   1443      * is specific to the document type. For example image files will largely return EXIF
   1444      * metadata.
   1445      *
   1446      * <p>The returned {@link Bundle} will contain zero or more entries.
   1447      * <p>Each entry represents a specific type of metadata.
   1448      *
   1449      * <p>if tags == null, then a list of default tags will be used.
   1450      *
   1451      * @param documentUri a Document URI
   1452      * @param tags an array of keys to choose which data are added to the Bundle. If the Document
   1453      *             is a JPG or ExifInterface compatible, send keys from {@link ExifInterface}.
   1454      *             If tags are null, a set of default tags will be used. If the tags don't
   1455      *             match with any relevant data, they will not be added to the Bundle.
   1456      * @return a Bundle of Bundles. If metadata exists within the Bundle, there will also
   1457      * be a String under DocumentsContract.METADATA_TYPES that will return a String[] of the
   1458      * types of metadata gathered.
   1459      *
   1460      * <pre><code>
   1461      *     Bundle metadata = DocumentsContract.getDocumentMetadata(client, imageDocUri, tags);
   1462      *     int imageLength = metadata.getInt(ExifInterface.TAG_IMAGE_LENGTH);
   1463      * </code></pre>
   1464      *
   1465      * {@hide}
   1466      */
   1467     public static Bundle getDocumentMetadata(ContentProviderClient client,
   1468             Uri documentUri, @Nullable String[] tags) throws RemoteException {
   1469         final Bundle in = new Bundle();
   1470         in.putParcelable(EXTRA_URI, documentUri);
   1471         in.putStringArray(EXTRA_METADATA_TAGS, tags);
   1472 
   1473         final Bundle out = client.call(METHOD_GET_DOCUMENT_METADATA, null, in);
   1474 
   1475         if (out == null) {
   1476             throw new RemoteException("Failed to get a response from getDocumentMetadata");
   1477         }
   1478         return out;
   1479     }
   1480 
   1481     /**
   1482      * Finds the canonical path from the top of the document tree.
   1483      *
   1484      * The {@link Path#getPath()} of the return value contains the document ID
   1485      * of all documents along the path from the top the document tree to the
   1486      * requested document, both inclusive.
   1487      *
   1488      * The {@link Path#getRootId()} of the return value returns {@code null}.
   1489      *
   1490      * @param treeUri treeUri of the document which path is requested.
   1491      * @return the path of the document, or {@code null} if failed.
   1492      * @see DocumentsProvider#findDocumentPath(String, String)
   1493      */
   1494     public static Path findDocumentPath(ContentResolver resolver, Uri treeUri)
   1495             throws FileNotFoundException {
   1496         checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
   1497 
   1498         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1499                 treeUri.getAuthority());
   1500         try {
   1501             return findDocumentPath(client, treeUri);
   1502         } catch (Exception e) {
   1503             Log.w(TAG, "Failed to find path", e);
   1504             rethrowIfNecessary(resolver, e);
   1505             return null;
   1506         } finally {
   1507             ContentProviderClient.releaseQuietly(client);
   1508         }
   1509     }
   1510 
   1511     /**
   1512      * Finds the canonical path. If uri is a document uri returns path from a root and
   1513      * its associated root id. If uri is a tree uri returns the path from the top of
   1514      * the tree. The {@link Path#getPath()} of the return value contains document ID
   1515      * starts from the top of the tree or the root document to the requested document,
   1516      * both inclusive.
   1517      *
   1518      * Callers can expect the root ID returned from multiple calls to this method is
   1519      * consistent.
   1520      *
   1521      * @param uri uri of the document which path is requested. It can be either a
   1522      *          plain document uri or a tree uri.
   1523      * @return the path of the document.
   1524      * @see DocumentsProvider#findDocumentPath(String, String)
   1525      *
   1526      * {@hide}
   1527      */
   1528     public static Path findDocumentPath(ContentProviderClient client, Uri uri)
   1529             throws RemoteException {
   1530 
   1531         final Bundle in = new Bundle();
   1532         in.putParcelable(DocumentsContract.EXTRA_URI, uri);
   1533 
   1534         final Bundle out = client.call(METHOD_FIND_DOCUMENT_PATH, null, in);
   1535 
   1536         return out.getParcelable(DocumentsContract.EXTRA_RESULT);
   1537     }
   1538 
   1539     /**
   1540      * Creates an intent for obtaining a web link for the specified document.
   1541      *
   1542      * <p>Note, that due to internal limitations, if there is already a web link
   1543      * intent created for the specified document but with different options,
   1544      * then it may be overriden.
   1545      *
   1546      * <p>Providers are required to show confirmation UI for all new permissions granted
   1547      * for the linked document.
   1548      *
   1549      * <p>If list of recipients is known, then it should be passed in options as
   1550      * {@link Intent#EXTRA_EMAIL} as a list of email addresses. Note, that
   1551      * this is just a hint for the provider, which can ignore the list. In either
   1552      * case the provider is required to show a UI for letting the user confirm
   1553      * any new permission grants.
   1554      *
   1555      * <p>Note, that the entire <code>options</code> bundle will be sent to the provider
   1556      * backing the passed <code>uri</code>. Make sure that you trust the provider
   1557      * before passing any sensitive information.
   1558      *
   1559      * <p>Since this API may show a UI, it cannot be called from background.
   1560      *
   1561      * <p>In order to obtain the Web Link use code like this:
   1562      * <pre><code>
   1563      * void onSomethingHappened() {
   1564      *   IntentSender sender = DocumentsContract.createWebLinkIntent(<i>...</i>);
   1565      *   if (sender != null) {
   1566      *     startIntentSenderForResult(
   1567      *         sender,
   1568      *         WEB_LINK_REQUEST_CODE,
   1569      *         null, 0, 0, 0, null);
   1570      *   }
   1571      * }
   1572      *
   1573      * <i>(...)</i>
   1574      *
   1575      * void onActivityResult(int requestCode, int resultCode, Intent data) {
   1576      *   if (requestCode == WEB_LINK_REQUEST_CODE && resultCode == RESULT_OK) {
   1577      *     Uri weblinkUri = data.getData();
   1578      *     <i>...</i>
   1579      *   }
   1580      * }
   1581      * </code></pre>
   1582      *
   1583      * @param uri uri for the document to create a link to.
   1584      * @param options Extra information for generating the link.
   1585      * @return an intent sender to obtain the web link, or null if the document
   1586      *      is not linkable, or creating the intent sender failed.
   1587      * @see DocumentsProvider#createWebLinkIntent(String, Bundle)
   1588      * @see Intent#EXTRA_EMAIL
   1589      */
   1590     public static IntentSender createWebLinkIntent(ContentResolver resolver, Uri uri,
   1591             Bundle options) throws FileNotFoundException {
   1592         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1593                 uri.getAuthority());
   1594         try {
   1595             return createWebLinkIntent(client, uri, options);
   1596         } catch (Exception e) {
   1597             Log.w(TAG, "Failed to create a web link intent", e);
   1598             rethrowIfNecessary(resolver, e);
   1599             return null;
   1600         } finally {
   1601             ContentProviderClient.releaseQuietly(client);
   1602         }
   1603     }
   1604 
   1605     /**
   1606      * {@hide}
   1607      */
   1608     public static IntentSender createWebLinkIntent(ContentProviderClient client, Uri uri,
   1609             Bundle options) throws RemoteException {
   1610         final Bundle in = new Bundle();
   1611         in.putParcelable(DocumentsContract.EXTRA_URI, uri);
   1612 
   1613         // Options may be provider specific, so put them in a separate bundle to
   1614         // avoid overriding the Uri.
   1615         if (options != null) {
   1616             in.putBundle(EXTRA_OPTIONS, options);
   1617         }
   1618 
   1619         final Bundle out = client.call(METHOD_CREATE_WEB_LINK_INTENT, null, in);
   1620         return out.getParcelable(DocumentsContract.EXTRA_RESULT);
   1621     }
   1622 
   1623     /**
   1624      * Open the given image for thumbnail purposes, using any embedded EXIF
   1625      * thumbnail if available, and providing orientation hints from the parent
   1626      * image.
   1627      *
   1628      * @hide
   1629      */
   1630     public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
   1631         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
   1632                 file, ParcelFileDescriptor.MODE_READ_ONLY);
   1633         Bundle extras = null;
   1634 
   1635         try {
   1636             final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
   1637 
   1638             switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
   1639                 case ExifInterface.ORIENTATION_ROTATE_90:
   1640                     extras = new Bundle(1);
   1641                     extras.putInt(EXTRA_ORIENTATION, 90);
   1642                     break;
   1643                 case ExifInterface.ORIENTATION_ROTATE_180:
   1644                     extras = new Bundle(1);
   1645                     extras.putInt(EXTRA_ORIENTATION, 180);
   1646                     break;
   1647                 case ExifInterface.ORIENTATION_ROTATE_270:
   1648                     extras = new Bundle(1);
   1649                     extras.putInt(EXTRA_ORIENTATION, 270);
   1650                     break;
   1651             }
   1652 
   1653             final long[] thumb = exif.getThumbnailRange();
   1654             if (thumb != null) {
   1655                 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
   1656             }
   1657         } catch (IOException e) {
   1658         }
   1659 
   1660         return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
   1661     }
   1662 
   1663     private static void rethrowIfNecessary(ContentResolver resolver, Exception e)
   1664             throws FileNotFoundException {
   1665         // We only want to throw applications targetting O and above
   1666         if (resolver.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
   1667             if (e instanceof ParcelableException) {
   1668                 ((ParcelableException) e).maybeRethrow(FileNotFoundException.class);
   1669             } else if (e instanceof RemoteException) {
   1670                 ((RemoteException) e).rethrowAsRuntimeException();
   1671             } else if (e instanceof RuntimeException) {
   1672                 throw (RuntimeException) e;
   1673             }
   1674         }
   1675     }
   1676 
   1677     /**
   1678      * Holds a path from a document to a particular document under it. It
   1679      * may also contains the root ID where the path resides.
   1680      */
   1681     public static final class Path implements Parcelable {
   1682 
   1683         private final @Nullable String mRootId;
   1684         private final List<String> mPath;
   1685 
   1686         /**
   1687          * Creates a Path.
   1688          *
   1689          * @param rootId the ID of the root. May be null.
   1690          * @param path the list of document ID from the parent document at
   1691          *          position 0 to the child document.
   1692          */
   1693         public Path(@Nullable String rootId, List<String> path) {
   1694             checkCollectionNotEmpty(path, "path");
   1695             checkCollectionElementsNotNull(path, "path");
   1696 
   1697             mRootId = rootId;
   1698             mPath = path;
   1699         }
   1700 
   1701         /**
   1702          * Returns the root id or null if the calling package doesn't have
   1703          * permission to access root information.
   1704          */
   1705         public @Nullable String getRootId() {
   1706             return mRootId;
   1707         }
   1708 
   1709         /**
   1710          * Returns the path. The path is trimmed to the top of tree if
   1711          * calling package doesn't have permission to access those
   1712          * documents.
   1713          */
   1714         public List<String> getPath() {
   1715             return mPath;
   1716         }
   1717 
   1718         @Override
   1719         public boolean equals(Object o) {
   1720             if (this == o) {
   1721                 return true;
   1722             }
   1723             if (o == null || !(o instanceof Path)) {
   1724                 return false;
   1725             }
   1726             Path path = (Path) o;
   1727             return Objects.equals(mRootId, path.mRootId) &&
   1728                     Objects.equals(mPath, path.mPath);
   1729         }
   1730 
   1731         @Override
   1732         public int hashCode() {
   1733             return Objects.hash(mRootId, mPath);
   1734         }
   1735 
   1736         @Override
   1737         public String toString() {
   1738             return new StringBuilder()
   1739                     .append("DocumentsContract.Path{")
   1740                     .append("rootId=")
   1741                     .append(mRootId)
   1742                     .append(", path=")
   1743                     .append(mPath)
   1744                     .append("}")
   1745                     .toString();
   1746         }
   1747 
   1748         @Override
   1749         public void writeToParcel(Parcel dest, int flags) {
   1750             dest.writeString(mRootId);
   1751             dest.writeStringList(mPath);
   1752         }
   1753 
   1754         @Override
   1755         public int describeContents() {
   1756             return 0;
   1757         }
   1758 
   1759         public static final Creator<Path> CREATOR = new Creator<Path>() {
   1760             @Override
   1761             public Path createFromParcel(Parcel in) {
   1762                 final String rootId = in.readString();
   1763                 final List<String> path = in.createStringArrayList();
   1764                 return new Path(rootId, path);
   1765             }
   1766 
   1767             @Override
   1768             public Path[] newArray(int size) {
   1769                 return new Path[size];
   1770             }
   1771         };
   1772     }
   1773 }
   1774