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