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