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