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