• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DocumentsContract()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 {
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 {
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      */
buildRootsUri(String authority)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      */
buildRootUri(String authority, String rootId)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      */
buildRecentDocumentsUri(String authority, String rootId)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      */
buildTreeDocumentUri(String authority, String documentId)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      */
buildDocumentUri(String authority, String documentId)918     public static Uri buildDocumentUri(String authority, String documentId) {
919         return getBaseDocumentUriBuilder(authority).appendPath(documentId).build();
920     }
921 
922     /** {@hide} */
buildBaseDocumentUri(String authority)923     public static Uri buildBaseDocumentUri(String authority) {
924         return getBaseDocumentUriBuilder(authority).build();
925     }
926 
getBaseDocumentUriBuilder(String authority)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      */
buildDocumentUriUsingTree(Uri treeUri, String documentId)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} */
buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId)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      */
buildChildDocumentsUri(String authority, String parentDocumentId)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      */
buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId)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      */
buildSearchDocumentsUri( String authority, String rootId, String query)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      */
matchSearchQueryArguments(Bundle queryArgs, String displayName, String mimeType, long lastModified, long size)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      */
getHandledQueryArguments(Bundle queryArgs)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      */
isDocumentUri(Context context, @Nullable Uri uri)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      */
isRootsUri(@onNull Context context, @Nullable Uri uri)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      */
isRootUri(@onNull Context context, @Nullable Uri uri)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} */
isContentUri(@ullable Uri uri)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      */
isTreeUri(Uri uri)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 
isRootUri(Context context, @Nullable Uri uri, int pathSize)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 
isDocumentsProvider(Context context, String authority)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      */
getRootId(Uri rootUri)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      */
getDocumentId(Uri documentUri)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      */
getTreeDocumentId(Uri documentUri)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      */
getSearchDocumentsQuery(Uri searchDocumentsUri)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      */
getSearchDocumentsQuery(@onNull Bundle bundle)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
setManageMode(@onNull Uri uri)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
isManageMode(@onNull Uri uri)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      */
getDocumentThumbnail(@onNull ContentResolver content, @NonNull Uri documentUri, @NonNull Point size, @Nullable CancellationSignal signal)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      */
createDocument(@onNull ContentResolver content, @NonNull Uri parentDocumentUri, @NonNull String mimeType, @NonNull String displayName)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      */
isChildDocument(@onNull ContentResolver content, @NonNull Uri parentDocumentUri, @NonNull Uri childDocumentUri)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      */
renameDocument(@onNull ContentResolver content, @NonNull Uri documentUri, @NonNull String displayName)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      */
deleteDocument(@onNull ContentResolver content, @NonNull Uri documentUri)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      */
copyDocument(@onNull ContentResolver content, @NonNull Uri sourceDocumentUri, @NonNull Uri targetParentDocumentUri)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      */
moveDocument(@onNull ContentResolver content, @NonNull Uri sourceDocumentUri, @NonNull Uri sourceParentDocumentUri, @NonNull Uri targetParentDocumentUri)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      */
removeDocument(@onNull ContentResolver content, @NonNull Uri documentUri, @NonNull Uri parentDocumentUri)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      */
ejectRoot(@onNull ContentResolver content, @NonNull Uri rootUri)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      */
getDocumentMetadata(@onNull ContentResolver content, @NonNull Uri documentUri)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      */
findDocumentPath(@onNull ContentResolver content, @NonNull Uri treeUri)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      */
createWebLinkIntent(@onNull ContentResolver content, @NonNull Uri uri, @Nullable Bundle options)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      */
openImageThumbnail(File file)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 
rethrowIfNecessary(Exception e)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          */
Path(@ullable String rootId, List<String> path)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          */
getRootId()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          */
getPath()1764         public List<String> getPath() {
1765             return mPath;
1766         }
1767 
1768         @Override
equals(Object o)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
hashCode()1782         public int hashCode() {
1783             return Objects.hash(mRootId, mPath);
1784         }
1785 
1786         @Override
toString()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
writeToParcel(Parcel dest, int flags)1799         public void writeToParcel(Parcel dest, int flags) {
1800             dest.writeString(mRootId);
1801             dest.writeStringList(mPath);
1802         }
1803 
1804         @Override
describeContents()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