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