• 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 android.net.TrafficStats.KB_IN_BYTES;
20 import static android.system.OsConstants.SEEK_SET;
21 
22 import android.content.ContentProviderClient;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.ResolveInfo;
27 import android.content.res.AssetFileDescriptor;
28 import android.database.Cursor;
29 import android.graphics.Bitmap;
30 import android.graphics.BitmapFactory;
31 import android.graphics.Matrix;
32 import android.graphics.Point;
33 import android.media.ExifInterface;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.CancellationSignal;
37 import android.os.OperationCanceledException;
38 import android.os.ParcelFileDescriptor;
39 import android.os.ParcelFileDescriptor.OnCloseListener;
40 import android.os.RemoteException;
41 import android.system.ErrnoException;
42 import android.system.Os;
43 import android.util.Log;
44 
45 import libcore.io.IoUtils;
46 
47 import java.io.BufferedInputStream;
48 import java.io.File;
49 import java.io.FileDescriptor;
50 import java.io.FileInputStream;
51 import java.io.FileNotFoundException;
52 import java.io.IOException;
53 import java.util.List;
54 
55 /**
56  * Defines the contract between a documents provider and the platform.
57  * <p>
58  * To create a document provider, extend {@link DocumentsProvider}, which
59  * provides a foundational implementation of this contract.
60  * <p>
61  * All client apps must hold a valid URI permission grant to access documents,
62  * typically issued when a user makes a selection through
63  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
64  * or {@link Intent#ACTION_OPEN_DOCUMENT_TREE}.
65  *
66  * @see DocumentsProvider
67  */
68 public final class DocumentsContract {
69     private static final String TAG = "Documents";
70 
71     // content://com.example/root/
72     // content://com.example/root/sdcard/
73     // content://com.example/root/sdcard/recent/
74     // content://com.example/root/sdcard/search/?query=pony
75     // content://com.example/document/12/
76     // content://com.example/document/12/children/
77     // content://com.example/tree/12/document/24/
78     // content://com.example/tree/12/document/24/children/
79 
DocumentsContract()80     private DocumentsContract() {
81     }
82 
83     /**
84      * Intent action used to identify {@link DocumentsProvider} instances. This
85      * is used in the {@code <intent-filter>} of a {@code <provider>}.
86      */
87     public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
88 
89     /** {@hide} */
90     public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
91 
92     /** {@hide} */
93     public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
94 
95     /**
96      * Set this in a DocumentsUI intent to cause a package's own roots to be
97      * excluded from the roots list.
98      */
99     public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
100 
101     /**
102      * Included in {@link AssetFileDescriptor#getExtras()} when returned
103      * thumbnail should be rotated.
104      *
105      * @see MediaStore.Images.ImageColumns#ORIENTATION
106      * @hide
107      */
108     public static final String EXTRA_ORIENTATION = "android.content.extra.ORIENTATION";
109 
110     /**
111      * Overrides the default prompt text in DocumentsUI when set in an intent.
112      */
113     public static final String EXTRA_PROMPT = "android.provider.extra.PROMPT";
114 
115     /** {@hide} */
116     public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT";
117     /** {@hide} */
118     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
119 
120     /** {@hide} */
121     public static final String
122             ACTION_BROWSE_DOCUMENT_ROOT = "android.provider.action.BROWSE_DOCUMENT_ROOT";
123 
124     /** {@hide} */
125     public static final String
126             ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
127 
128     /**
129      * Buffer is large enough to rewind past any EXIF headers.
130      */
131     private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
132 
133     /** {@hide} */
134     public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui";
135 
136     /**
137      * Constants related to a document, including {@link Cursor} column names
138      * and flags.
139      * <p>
140      * A document can be either an openable stream (with a specific MIME type),
141      * or a directory containing additional documents (with the
142      * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a
143      * subtree containing zero or more documents, which can recursively contain
144      * even more documents and directories.
145      * <p>
146      * All columns are <em>read-only</em> to client applications.
147      */
148     public final static class Document {
Document()149         private Document() {
150         }
151 
152         /**
153          * Unique ID of a document. This ID is both provided by and interpreted
154          * by a {@link DocumentsProvider}, and should be treated as an opaque
155          * value by client applications. This column is required.
156          * <p>
157          * Each document must have a unique ID within a provider, but that
158          * single document may be included as a child of multiple directories.
159          * <p>
160          * A provider must always return durable IDs, since they will be used to
161          * issue long-term URI permission grants when an application interacts
162          * with {@link Intent#ACTION_OPEN_DOCUMENT} and
163          * {@link Intent#ACTION_CREATE_DOCUMENT}.
164          * <p>
165          * Type: STRING
166          */
167         public static final String COLUMN_DOCUMENT_ID = "document_id";
168 
169         /**
170          * Concrete MIME type of a document. For example, "image/png" or
171          * "application/pdf" for openable files. A document can also be a
172          * directory containing additional documents, which is represented with
173          * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
174          * <p>
175          * Type: STRING
176          *
177          * @see #MIME_TYPE_DIR
178          */
179         public static final String COLUMN_MIME_TYPE = "mime_type";
180 
181         /**
182          * Display name of a document, used as the primary title displayed to a
183          * user. This column is required.
184          * <p>
185          * Type: STRING
186          */
187         public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
188 
189         /**
190          * Summary of a document, which may be shown to a user. This column is
191          * optional, and may be {@code null}.
192          * <p>
193          * Type: STRING
194          */
195         public static final String COLUMN_SUMMARY = "summary";
196 
197         /**
198          * Timestamp when a document was last modified, in milliseconds since
199          * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
200          * {@code null} if unknown. A {@link DocumentsProvider} can update this
201          * field using events from {@link OnCloseListener} or other reliable
202          * {@link ParcelFileDescriptor} transports.
203          * <p>
204          * Type: INTEGER (long)
205          *
206          * @see System#currentTimeMillis()
207          */
208         public static final String COLUMN_LAST_MODIFIED = "last_modified";
209 
210         /**
211          * Specific icon resource ID for a document. This column is optional,
212          * and may be {@code null} to use a platform-provided default icon based
213          * on {@link #COLUMN_MIME_TYPE}.
214          * <p>
215          * Type: INTEGER (int)
216          */
217         public static final String COLUMN_ICON = "icon";
218 
219         /**
220          * Flags that apply to a document. This column is required.
221          * <p>
222          * Type: INTEGER (int)
223          *
224          * @see #FLAG_SUPPORTS_WRITE
225          * @see #FLAG_SUPPORTS_DELETE
226          * @see #FLAG_SUPPORTS_THUMBNAIL
227          * @see #FLAG_DIR_PREFERS_GRID
228          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
229          */
230         public static final String COLUMN_FLAGS = "flags";
231 
232         /**
233          * Size of a document, in bytes, or {@code null} if unknown. This column
234          * is required.
235          * <p>
236          * Type: INTEGER (long)
237          */
238         public static final String COLUMN_SIZE = OpenableColumns.SIZE;
239 
240         /**
241          * MIME type of a document which is a directory that may contain
242          * additional documents.
243          *
244          * @see #COLUMN_MIME_TYPE
245          */
246         public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
247 
248         /**
249          * Flag indicating that a document can be represented as a thumbnail.
250          *
251          * @see #COLUMN_FLAGS
252          * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
253          *      Point, CancellationSignal)
254          * @see DocumentsProvider#openDocumentThumbnail(String, Point,
255          *      android.os.CancellationSignal)
256          */
257         public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
258 
259         /**
260          * Flag indicating that a document supports writing.
261          * <p>
262          * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
263          * the calling application is granted both
264          * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
265          * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
266          * writability of a document may change over time, for example due to
267          * remote access changes. This flag indicates that a document client can
268          * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
269          *
270          * @see #COLUMN_FLAGS
271          */
272         public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
273 
274         /**
275          * Flag indicating that a document is deletable.
276          *
277          * @see #COLUMN_FLAGS
278          * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
279          * @see DocumentsProvider#deleteDocument(String)
280          */
281         public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
282 
283         /**
284          * Flag indicating that a document is a directory that supports creation
285          * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
286          * {@link #MIME_TYPE_DIR}.
287          *
288          * @see #COLUMN_FLAGS
289          * @see DocumentsProvider#createDocument(String, String, String)
290          */
291         public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
292 
293         /**
294          * Flag indicating that a directory prefers its contents be shown in a
295          * larger format grid. Usually suitable when a directory contains mostly
296          * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
297          * {@link #MIME_TYPE_DIR}.
298          *
299          * @see #COLUMN_FLAGS
300          */
301         public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
302 
303         /**
304          * Flag indicating that a directory prefers its contents be sorted by
305          * {@link #COLUMN_LAST_MODIFIED}. Only valid when
306          * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
307          *
308          * @see #COLUMN_FLAGS
309          */
310         public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
311 
312         /**
313          * Flag indicating that a document can be renamed.
314          *
315          * @see #COLUMN_FLAGS
316          * @see DocumentsContract#renameDocument(ContentProviderClient, Uri,
317          *      String)
318          * @see DocumentsProvider#renameDocument(String, String)
319          */
320         public static final int FLAG_SUPPORTS_RENAME = 1 << 6;
321 
322         /**
323          * Flag indicating that document titles should be hidden when viewing
324          * this directory in a larger format grid. For example, a directory
325          * containing only images may want the image thumbnails to speak for
326          * themselves. Only valid when {@link #COLUMN_MIME_TYPE} is
327          * {@link #MIME_TYPE_DIR}.
328          *
329          * @see #COLUMN_FLAGS
330          * @see #FLAG_DIR_PREFERS_GRID
331          * @hide
332          */
333         public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 16;
334     }
335 
336     /**
337      * Constants related to a root of documents, including {@link Cursor} column
338      * names and flags. A root is the start of a tree of documents, such as a
339      * physical storage device, or an account. Each root starts at the directory
340      * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively
341      * contain both documents and directories.
342      * <p>
343      * All columns are <em>read-only</em> to client applications.
344      */
345     public final static class Root {
Root()346         private Root() {
347         }
348 
349         /**
350          * Unique ID of a root. This ID is both provided by and interpreted by a
351          * {@link DocumentsProvider}, and should be treated as an opaque value
352          * by client applications. This column is required.
353          * <p>
354          * Type: STRING
355          */
356         public static final String COLUMN_ROOT_ID = "root_id";
357 
358         /**
359          * Flags that apply to a root. This column is required.
360          * <p>
361          * Type: INTEGER (int)
362          *
363          * @see #FLAG_LOCAL_ONLY
364          * @see #FLAG_SUPPORTS_CREATE
365          * @see #FLAG_SUPPORTS_RECENTS
366          * @see #FLAG_SUPPORTS_SEARCH
367          */
368         public static final String COLUMN_FLAGS = "flags";
369 
370         /**
371          * Icon resource ID for a root. This column is required.
372          * <p>
373          * Type: INTEGER (int)
374          */
375         public static final String COLUMN_ICON = "icon";
376 
377         /**
378          * Title for a root, which will be shown to a user. This column is
379          * required. For a single storage service surfacing multiple accounts as
380          * different roots, this title should be the name of the service.
381          * <p>
382          * Type: STRING
383          */
384         public static final String COLUMN_TITLE = "title";
385 
386         /**
387          * Summary for this root, which may be shown to a user. This column is
388          * optional, and may be {@code null}. For a single storage service
389          * surfacing multiple accounts as different roots, this summary should
390          * be the name of the account.
391          * <p>
392          * Type: STRING
393          */
394         public static final String COLUMN_SUMMARY = "summary";
395 
396         /**
397          * Document which is a directory that represents the top directory of
398          * this root. This column is required.
399          * <p>
400          * Type: STRING
401          *
402          * @see Document#COLUMN_DOCUMENT_ID
403          */
404         public static final String COLUMN_DOCUMENT_ID = "document_id";
405 
406         /**
407          * Number of bytes available in this root. This column is optional, and
408          * may be {@code null} if unknown or unbounded.
409          * <p>
410          * Type: INTEGER (long)
411          */
412         public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
413 
414         /**
415          * MIME types supported by this root. This column is optional, and if
416          * {@code null} the root is assumed to support all MIME types. Multiple
417          * MIME types can be separated by a newline. For example, a root
418          * supporting audio might return "audio/*\napplication/x-flac".
419          * <p>
420          * Type: STRING
421          */
422         public static final String COLUMN_MIME_TYPES = "mime_types";
423 
424         /** {@hide} */
425         public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
426 
427         /**
428          * Flag indicating that at least one directory under this root supports
429          * creating content. Roots with this flag will be shown when an
430          * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
431          *
432          * @see #COLUMN_FLAGS
433          */
434         public static final int FLAG_SUPPORTS_CREATE = 1;
435 
436         /**
437          * Flag indicating that this root offers content that is strictly local
438          * on the device. That is, no network requests are made for the content.
439          *
440          * @see #COLUMN_FLAGS
441          * @see Intent#EXTRA_LOCAL_ONLY
442          */
443         public static final int FLAG_LOCAL_ONLY = 1 << 1;
444 
445         /**
446          * Flag indicating that this root can be queried to provide recently
447          * modified documents.
448          *
449          * @see #COLUMN_FLAGS
450          * @see DocumentsContract#buildRecentDocumentsUri(String, String)
451          * @see DocumentsProvider#queryRecentDocuments(String, String[])
452          */
453         public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
454 
455         /**
456          * Flag indicating that this root supports search.
457          *
458          * @see #COLUMN_FLAGS
459          * @see DocumentsContract#buildSearchDocumentsUri(String, String,
460          *      String)
461          * @see DocumentsProvider#querySearchDocuments(String, String,
462          *      String[])
463          */
464         public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
465 
466         /**
467          * Flag indicating that this root supports testing parent child
468          * relationships.
469          *
470          * @see #COLUMN_FLAGS
471          * @see DocumentsProvider#isChildDocument(String, String)
472          */
473         public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
474 
475         /**
476          * Flag indicating that this root is currently empty. This may be used
477          * to hide the root when opening documents, but the root will still be
478          * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
479          * also set. If the value of this flag changes, such as when a root
480          * becomes non-empty, you must send a content changed notification for
481          * {@link DocumentsContract#buildRootsUri(String)}.
482          *
483          * @see #COLUMN_FLAGS
484          * @see ContentResolver#notifyChange(Uri,
485          *      android.database.ContentObserver, boolean)
486          * @hide
487          */
488         public static final int FLAG_EMPTY = 1 << 16;
489 
490         /**
491          * Flag indicating that this root should only be visible to advanced
492          * users.
493          *
494          * @see #COLUMN_FLAGS
495          * @hide
496          */
497         public static final int FLAG_ADVANCED = 1 << 17;
498 
499         /**
500          * Flag indicating that this root has settings.
501          *
502          * @see #COLUMN_FLAGS
503          * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS
504          * @hide
505          */
506         public static final int FLAG_HAS_SETTINGS = 1 << 18;
507     }
508 
509     /**
510      * Optional boolean flag included in a directory {@link Cursor#getExtras()}
511      * indicating that a document provider is still loading data. For example, a
512      * provider has returned some results, but is still waiting on an
513      * outstanding network request. The provider must send a content changed
514      * notification when loading is finished.
515      *
516      * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
517      *      boolean)
518      */
519     public static final String EXTRA_LOADING = "loading";
520 
521     /**
522      * Optional string included in a directory {@link Cursor#getExtras()}
523      * providing an informational message that should be shown to a user. For
524      * example, a provider may wish to indicate that not all documents are
525      * available.
526      */
527     public static final String EXTRA_INFO = "info";
528 
529     /**
530      * Optional string included in a directory {@link Cursor#getExtras()}
531      * providing an error message that should be shown to a user. For example, a
532      * provider may wish to indicate that a network error occurred. The user may
533      * choose to retry, resulting in a new query.
534      */
535     public static final String EXTRA_ERROR = "error";
536 
537     /** {@hide} */
538     public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
539     /** {@hide} */
540     public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
541     /** {@hide} */
542     public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
543 
544     /** {@hide} */
545     public static final String EXTRA_URI = "uri";
546 
547     private static final String PATH_ROOT = "root";
548     private static final String PATH_RECENT = "recent";
549     private static final String PATH_DOCUMENT = "document";
550     private static final String PATH_CHILDREN = "children";
551     private static final String PATH_SEARCH = "search";
552     private static final String PATH_TREE = "tree";
553 
554     private static final String PARAM_QUERY = "query";
555     private static final String PARAM_MANAGE = "manage";
556 
557     /**
558      * Build URI representing the roots of a document provider. When queried, a
559      * provider will return one or more rows with columns defined by
560      * {@link Root}.
561      *
562      * @see DocumentsProvider#queryRoots(String[])
563      */
buildRootsUri(String authority)564     public static Uri buildRootsUri(String authority) {
565         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
566                 .authority(authority).appendPath(PATH_ROOT).build();
567     }
568 
569     /**
570      * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a
571      * document provider.
572      *
573      * @see #getRootId(Uri)
574      */
buildRootUri(String authority, String rootId)575     public static Uri buildRootUri(String authority, String rootId) {
576         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
577                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
578     }
579 
580     /**
581      * Build URI representing the recently modified documents of a specific root
582      * in a document provider. When queried, a provider will return zero or more
583      * rows with columns defined by {@link Document}.
584      *
585      * @see DocumentsProvider#queryRecentDocuments(String, String[])
586      * @see #getRootId(Uri)
587      */
buildRecentDocumentsUri(String authority, String rootId)588     public static Uri buildRecentDocumentsUri(String authority, String rootId) {
589         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
590                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
591                 .appendPath(PATH_RECENT).build();
592     }
593 
594     /**
595      * Build URI representing access to descendant documents of the given
596      * {@link Document#COLUMN_DOCUMENT_ID}.
597      *
598      * @see #getTreeDocumentId(Uri)
599      */
buildTreeDocumentUri(String authority, String documentId)600     public static Uri buildTreeDocumentUri(String authority, String documentId) {
601         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
602                 .appendPath(PATH_TREE).appendPath(documentId).build();
603     }
604 
605     /**
606      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
607      * a document provider. When queried, a provider will return a single row
608      * with columns defined by {@link Document}.
609      *
610      * @see DocumentsProvider#queryDocument(String, String[])
611      * @see #getDocumentId(Uri)
612      */
buildDocumentUri(String authority, String documentId)613     public static Uri buildDocumentUri(String authority, String documentId) {
614         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
615                 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
616     }
617 
618     /**
619      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
620      * a document provider. When queried, a provider will return a single row
621      * with columns defined by {@link Document}.
622      * <p>
623      * However, instead of directly accessing the target document, the returned
624      * URI will leverage access granted through a subtree URI, typically
625      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document
626      * must be a descendant (child, grandchild, etc) of the subtree.
627      * <p>
628      * This is typically used to access documents under a user-selected
629      * directory tree, since it doesn't require the user to separately confirm
630      * each new document access.
631      *
632      * @param treeUri the subtree to leverage to gain access to the target
633      *            document. The target directory must be a descendant of this
634      *            subtree.
635      * @param documentId the target document, which the caller may not have
636      *            direct access to.
637      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
638      * @see DocumentsProvider#isChildDocument(String, String)
639      * @see #buildDocumentUri(String, String)
640      */
buildDocumentUriUsingTree(Uri treeUri, String documentId)641     public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
642         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
643                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
644                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
645                 .appendPath(documentId).build();
646     }
647 
648     /** {@hide} */
buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId)649     public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) {
650         if (isTreeUri(baseUri)) {
651             return buildDocumentUriUsingTree(baseUri, documentId);
652         } else {
653             return buildDocumentUri(baseUri.getAuthority(), documentId);
654         }
655     }
656 
657     /**
658      * Build URI representing the children of the target directory in a document
659      * provider. When queried, a provider will return zero or more rows with
660      * columns defined by {@link Document}.
661      *
662      * @param parentDocumentId the document to return children for, which must
663      *            be a directory with MIME type of
664      *            {@link Document#MIME_TYPE_DIR}.
665      * @see DocumentsProvider#queryChildDocuments(String, String[], String)
666      * @see #getDocumentId(Uri)
667      */
buildChildDocumentsUri(String authority, String parentDocumentId)668     public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
669         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
670                 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
671                 .build();
672     }
673 
674     /**
675      * Build URI representing the children of the target directory in a document
676      * provider. When queried, a provider will return zero or more rows with
677      * columns defined by {@link Document}.
678      * <p>
679      * However, instead of directly accessing the target directory, the returned
680      * URI will leverage access granted through a subtree URI, typically
681      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target
682      * directory must be a descendant (child, grandchild, etc) of the subtree.
683      * <p>
684      * This is typically used to access documents under a user-selected
685      * directory tree, since it doesn't require the user to separately confirm
686      * each new document access.
687      *
688      * @param treeUri the subtree to leverage to gain access to the target
689      *            document. The target directory must be a descendant of this
690      *            subtree.
691      * @param parentDocumentId the document to return children for, which the
692      *            caller may not have direct access to, and which must be a
693      *            directory with MIME type of {@link Document#MIME_TYPE_DIR}.
694      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
695      * @see DocumentsProvider#isChildDocument(String, String)
696      * @see #buildChildDocumentsUri(String, String)
697      */
buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId)698     public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) {
699         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
700                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
701                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
702                 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
703     }
704 
705     /**
706      * Build URI representing a search for matching documents under a specific
707      * root in a document provider. When queried, a provider will return zero or
708      * more rows with columns defined by {@link Document}.
709      *
710      * @see DocumentsProvider#querySearchDocuments(String, String, String[])
711      * @see #getRootId(Uri)
712      * @see #getSearchDocumentsQuery(Uri)
713      */
buildSearchDocumentsUri( String authority, String rootId, String query)714     public static Uri buildSearchDocumentsUri(
715             String authority, String rootId, String query) {
716         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
717                 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
718                 .appendQueryParameter(PARAM_QUERY, query).build();
719     }
720 
721     /**
722      * Test if the given URI represents a {@link Document} backed by a
723      * {@link DocumentsProvider}.
724      *
725      * @see #buildDocumentUri(String, String)
726      * @see #buildDocumentUriUsingTree(Uri, String)
727      */
isDocumentUri(Context context, Uri uri)728     public static boolean isDocumentUri(Context context, Uri uri) {
729         final List<String> paths = uri.getPathSegments();
730         if (paths.size() == 2 && PATH_DOCUMENT.equals(paths.get(0))) {
731             return isDocumentsProvider(context, uri.getAuthority());
732         }
733         if (paths.size() == 4 && PATH_TREE.equals(paths.get(0))
734                 && PATH_DOCUMENT.equals(paths.get(2))) {
735             return isDocumentsProvider(context, uri.getAuthority());
736         }
737         return false;
738     }
739 
740     /** {@hide} */
isTreeUri(Uri uri)741     public static boolean isTreeUri(Uri uri) {
742         final List<String> paths = uri.getPathSegments();
743         return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
744     }
745 
isDocumentsProvider(Context context, String authority)746     private static boolean isDocumentsProvider(Context context, String authority) {
747         final Intent intent = new Intent(PROVIDER_INTERFACE);
748         final List<ResolveInfo> infos = context.getPackageManager()
749                 .queryIntentContentProviders(intent, 0);
750         for (ResolveInfo info : infos) {
751             if (authority.equals(info.providerInfo.authority)) {
752                 return true;
753             }
754         }
755         return false;
756     }
757 
758     /**
759      * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI.
760      */
getRootId(Uri rootUri)761     public static String getRootId(Uri rootUri) {
762         final List<String> paths = rootUri.getPathSegments();
763         if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
764             return paths.get(1);
765         }
766         throw new IllegalArgumentException("Invalid URI: " + rootUri);
767     }
768 
769     /**
770      * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
771      *
772      * @see #isDocumentUri(Context, Uri)
773      */
getDocumentId(Uri documentUri)774     public static String getDocumentId(Uri documentUri) {
775         final List<String> paths = documentUri.getPathSegments();
776         if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
777             return paths.get(1);
778         }
779         if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0))
780                 && PATH_DOCUMENT.equals(paths.get(2))) {
781             return paths.get(3);
782         }
783         throw new IllegalArgumentException("Invalid URI: " + documentUri);
784     }
785 
786     /**
787      * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
788      */
getTreeDocumentId(Uri documentUri)789     public static String getTreeDocumentId(Uri documentUri) {
790         final List<String> paths = documentUri.getPathSegments();
791         if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
792             return paths.get(1);
793         }
794         throw new IllegalArgumentException("Invalid URI: " + documentUri);
795     }
796 
797     /**
798      * Extract the search query from a URI built by
799      * {@link #buildSearchDocumentsUri(String, String, String)}.
800      */
getSearchDocumentsQuery(Uri searchDocumentsUri)801     public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
802         return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
803     }
804 
805     /** {@hide} */
setManageMode(Uri uri)806     public static Uri setManageMode(Uri uri) {
807         return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
808     }
809 
810     /** {@hide} */
isManageMode(Uri uri)811     public static boolean isManageMode(Uri uri) {
812         return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
813     }
814 
815     /**
816      * Return thumbnail representing the document at the given URI. Callers are
817      * responsible for their own in-memory caching.
818      *
819      * @param documentUri document to return thumbnail for, which must have
820      *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
821      * @param size optimal thumbnail size desired. A provider may return a
822      *            thumbnail of a different size, but never more than double the
823      *            requested size.
824      * @param signal signal used to indicate if caller is no longer interested
825      *            in the thumbnail.
826      * @return decoded thumbnail, or {@code null} if problem was encountered.
827      * @see DocumentsProvider#openDocumentThumbnail(String, Point,
828      *      android.os.CancellationSignal)
829      */
getDocumentThumbnail( ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal)830     public static Bitmap getDocumentThumbnail(
831             ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
832         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
833                 documentUri.getAuthority());
834         try {
835             return getDocumentThumbnail(client, documentUri, size, signal);
836         } catch (Exception e) {
837             if (!(e instanceof OperationCanceledException)) {
838                 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
839             }
840             return null;
841         } finally {
842             ContentProviderClient.releaseQuietly(client);
843         }
844     }
845 
846     /** {@hide} */
getDocumentThumbnail( ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)847     public static Bitmap getDocumentThumbnail(
848             ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
849             throws RemoteException, IOException {
850         final Bundle openOpts = new Bundle();
851         openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size);
852 
853         AssetFileDescriptor afd = null;
854         Bitmap bitmap = null;
855         try {
856             afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
857 
858             final FileDescriptor fd = afd.getFileDescriptor();
859             final long offset = afd.getStartOffset();
860 
861             // Try seeking on the returned FD, since it gives us the most
862             // optimal decode path; otherwise fall back to buffering.
863             BufferedInputStream is = null;
864             try {
865                 Os.lseek(fd, offset, SEEK_SET);
866             } catch (ErrnoException e) {
867                 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
868                 is.mark(THUMBNAIL_BUFFER_SIZE);
869             }
870 
871             // We requested a rough thumbnail size, but the remote size may have
872             // returned something giant, so defensively scale down as needed.
873             final BitmapFactory.Options opts = new BitmapFactory.Options();
874             opts.inJustDecodeBounds = true;
875             if (is != null) {
876                 BitmapFactory.decodeStream(is, null, opts);
877             } else {
878                 BitmapFactory.decodeFileDescriptor(fd, null, opts);
879             }
880 
881             final int widthSample = opts.outWidth / size.x;
882             final int heightSample = opts.outHeight / size.y;
883 
884             opts.inJustDecodeBounds = false;
885             opts.inSampleSize = Math.min(widthSample, heightSample);
886             if (is != null) {
887                 is.reset();
888                 bitmap = BitmapFactory.decodeStream(is, null, opts);
889             } else {
890                 try {
891                     Os.lseek(fd, offset, SEEK_SET);
892                 } catch (ErrnoException e) {
893                     e.rethrowAsIOException();
894                 }
895                 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
896             }
897 
898             // Transform the bitmap if requested. We use a side-channel to
899             // communicate the orientation, since EXIF thumbnails don't contain
900             // the rotation flags of the original image.
901             final Bundle extras = afd.getExtras();
902             final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
903             if (orientation != 0) {
904                 final int width = bitmap.getWidth();
905                 final int height = bitmap.getHeight();
906 
907                 final Matrix m = new Matrix();
908                 m.setRotate(orientation, width / 2, height / 2);
909                 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
910             }
911         } finally {
912             IoUtils.closeQuietly(afd);
913         }
914 
915         return bitmap;
916     }
917 
918     /**
919      * Create a new document with given MIME type and display name.
920      *
921      * @param parentDocumentUri directory with
922      *            {@link Document#FLAG_DIR_SUPPORTS_CREATE}
923      * @param mimeType MIME type of new document
924      * @param displayName name of new document
925      * @return newly created document, or {@code null} if failed
926      */
createDocument(ContentResolver resolver, Uri parentDocumentUri, String mimeType, String displayName)927     public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
928             String mimeType, String displayName) {
929         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
930                 parentDocumentUri.getAuthority());
931         try {
932             return createDocument(client, parentDocumentUri, mimeType, displayName);
933         } catch (Exception e) {
934             Log.w(TAG, "Failed to create document", e);
935             return null;
936         } finally {
937             ContentProviderClient.releaseQuietly(client);
938         }
939     }
940 
941     /** {@hide} */
createDocument(ContentProviderClient client, Uri parentDocumentUri, String mimeType, String displayName)942     public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
943             String mimeType, String displayName) throws RemoteException {
944         final Bundle in = new Bundle();
945         in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
946         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
947         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
948 
949         final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
950         return out.getParcelable(DocumentsContract.EXTRA_URI);
951     }
952 
953     /**
954      * Change the display name of an existing document.
955      * <p>
956      * If the underlying provider needs to create a new
957      * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display
958      * name, that new document is returned and the original document is no
959      * longer valid. Otherwise, the original document is returned.
960      *
961      * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME}
962      * @param displayName updated name for document
963      * @return the existing or new document after the rename, or {@code null} if
964      *         failed.
965      */
renameDocument(ContentResolver resolver, Uri documentUri, String displayName)966     public static Uri renameDocument(ContentResolver resolver, Uri documentUri,
967             String displayName) {
968         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
969                 documentUri.getAuthority());
970         try {
971             return renameDocument(client, documentUri, displayName);
972         } catch (Exception e) {
973             Log.w(TAG, "Failed to rename document", e);
974             return null;
975         } finally {
976             ContentProviderClient.releaseQuietly(client);
977         }
978     }
979 
980     /** {@hide} */
renameDocument(ContentProviderClient client, Uri documentUri, String displayName)981     public static Uri renameDocument(ContentProviderClient client, Uri documentUri,
982             String displayName) throws RemoteException {
983         final Bundle in = new Bundle();
984         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
985         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
986 
987         final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
988         final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
989         return (outUri != null) ? outUri : documentUri;
990     }
991 
992     /**
993      * Delete the given document.
994      *
995      * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
996      * @return if the document was deleted successfully.
997      */
deleteDocument(ContentResolver resolver, Uri documentUri)998     public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
999         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1000                 documentUri.getAuthority());
1001         try {
1002             deleteDocument(client, documentUri);
1003             return true;
1004         } catch (Exception e) {
1005             Log.w(TAG, "Failed to delete document", e);
1006             return false;
1007         } finally {
1008             ContentProviderClient.releaseQuietly(client);
1009         }
1010     }
1011 
1012     /** {@hide} */
deleteDocument(ContentProviderClient client, Uri documentUri)1013     public static void deleteDocument(ContentProviderClient client, Uri documentUri)
1014             throws RemoteException {
1015         final Bundle in = new Bundle();
1016         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1017 
1018         client.call(METHOD_DELETE_DOCUMENT, null, in);
1019     }
1020 
1021     /**
1022      * Open the given image for thumbnail purposes, using any embedded EXIF
1023      * thumbnail if available, and providing orientation hints from the parent
1024      * image.
1025      *
1026      * @hide
1027      */
openImageThumbnail(File file)1028     public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
1029         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
1030                 file, ParcelFileDescriptor.MODE_READ_ONLY);
1031         Bundle extras = null;
1032 
1033         try {
1034             final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
1035 
1036             switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
1037                 case ExifInterface.ORIENTATION_ROTATE_90:
1038                     extras = new Bundle(1);
1039                     extras.putInt(EXTRA_ORIENTATION, 90);
1040                     break;
1041                 case ExifInterface.ORIENTATION_ROTATE_180:
1042                     extras = new Bundle(1);
1043                     extras.putInt(EXTRA_ORIENTATION, 180);
1044                     break;
1045                 case ExifInterface.ORIENTATION_ROTATE_270:
1046                     extras = new Bundle(1);
1047                     extras.putInt(EXTRA_ORIENTATION, 270);
1048                     break;
1049             }
1050 
1051             final long[] thumb = exif.getThumbnailRange();
1052             if (thumb != null) {
1053                 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
1054             }
1055         } catch (IOException e) {
1056         }
1057 
1058         return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
1059     }
1060 }
1061