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