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