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