DocumentsContract.java revision 3e1189b3590aefb65a2af720ae2ba959bbd4188d
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 com.google.android.collect.Lists;
42
43import libcore.io.ErrnoException;
44import libcore.io.IoUtils;
45import libcore.io.Libcore;
46
47import java.io.BufferedInputStream;
48import java.io.FileDescriptor;
49import java.io.FileInputStream;
50import java.io.IOException;
51import java.util.List;
52
53/**
54 * Defines the contract between a documents provider and the platform.
55 * <p>
56 * To create a document provider, extend {@link DocumentsProvider}, which
57 * provides a foundational implementation of this contract.
58 *
59 * @see DocumentsProvider
60 */
61public final class DocumentsContract {
62    private static final String TAG = "Documents";
63
64    // content://com.example/root/
65    // content://com.example/root/sdcard/
66    // content://com.example/root/sdcard/recent/
67    // content://com.example/root/sdcard/search/?query=pony
68    // content://com.example/document/12/
69    // content://com.example/document/12/children/
70
71    private DocumentsContract() {
72    }
73
74    /** {@hide} */
75    public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER";
76
77    /** {@hide} */
78    public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT";
79    /** {@hide} */
80    public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
81
82    /**
83     * Buffer is large enough to rewind past any EXIF headers.
84     */
85    private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
86
87    /**
88     * Constants related to a document, including {@link Cursor} columns names
89     * and flags.
90     * <p>
91     * A document can be either an openable file (with a specific MIME type), or
92     * a directory containing additional documents (with the
93     * {@link #MIME_TYPE_DIR} MIME type).
94     * <p>
95     * All columns are <em>read-only</em> to client applications.
96     */
97    public final static class Document {
98        private Document() {
99        }
100
101        /**
102         * Unique ID of a document. This ID is both provided by and interpreted
103         * by a {@link DocumentsProvider}, and should be treated as an opaque
104         * value by client applications.
105         * <p>
106         * Each document must have a unique ID within a provider, but that
107         * single document may be included as a child of multiple directories.
108         * <p>
109         * A provider must always return durable IDs, since they will be used to
110         * issue long-term Uri permission grants when an application interacts
111         * with {@link Intent#ACTION_OPEN_DOCUMENT} and
112         * {@link Intent#ACTION_CREATE_DOCUMENT}.
113         * <p>
114         * Type: STRING
115         */
116        public static final String COLUMN_DOCUMENT_ID = "document_id";
117
118        /**
119         * Concrete MIME type of a document. For example, "image/png" or
120         * "application/pdf" for openable files. A document can also be a
121         * directory containing additional documents, which is represented with
122         * the {@link #MIME_TYPE_DIR} MIME type.
123         * <p>
124         * Type: STRING
125         *
126         * @see #MIME_TYPE_DIR
127         */
128        public static final String COLUMN_MIME_TYPE = "mime_type";
129
130        /**
131         * Display name of a document, used as the primary title displayed to a
132         * user.
133         * <p>
134         * Type: STRING
135         */
136        public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
137
138        /**
139         * Summary of a document, which may be shown to a user. The summary may
140         * be {@code null}.
141         * <p>
142         * Type: STRING
143         */
144        public static final String COLUMN_SUMMARY = "summary";
145
146        /**
147         * Timestamp when a document was last modified, in milliseconds since
148         * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
149         * {@link DocumentsProvider} can update this field using events from
150         * {@link OnCloseListener} or other reliable
151         * {@link ParcelFileDescriptor} transports.
152         * <p>
153         * Type: INTEGER (long)
154         *
155         * @see System#currentTimeMillis()
156         */
157        public static final String COLUMN_LAST_MODIFIED = "last_modified";
158
159        /**
160         * Specific icon resource ID for a document, or {@code null} to use
161         * platform default icon based on {@link #COLUMN_MIME_TYPE}.
162         * <p>
163         * Type: INTEGER (int)
164         */
165        public static final String COLUMN_ICON = "icon";
166
167        /**
168         * Flags that apply to a document.
169         * <p>
170         * Type: INTEGER (int)
171         *
172         * @see #FLAG_SUPPORTS_WRITE
173         * @see #FLAG_SUPPORTS_DELETE
174         * @see #FLAG_SUPPORTS_THUMBNAIL
175         * @see #FLAG_DIR_PREFERS_GRID
176         * @see #FLAG_DIR_SUPPORTS_CREATE
177         */
178        public static final String COLUMN_FLAGS = "flags";
179
180        /**
181         * Size of a document, in bytes, or {@code null} if unknown.
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    /**
263     * Constants related to a root of documents, including {@link Cursor}
264     * columns names and flags.
265     * <p>
266     * All columns are <em>read-only</em> to client applications.
267     */
268    public final static class Root {
269        private Root() {
270        }
271
272        /**
273         * Unique ID of a root. This ID is both provided by and interpreted by a
274         * {@link DocumentsProvider}, and should be treated as an opaque value
275         * by client applications.
276         * <p>
277         * Type: STRING
278         */
279        public static final String COLUMN_ROOT_ID = "root_id";
280
281        /**
282         * Type of a root, used for clustering when presenting multiple roots to
283         * a user.
284         * <p>
285         * Type: INTEGER (int)
286         *
287         * @see #ROOT_TYPE_SERVICE
288         * @see #ROOT_TYPE_SHORTCUT
289         * @see #ROOT_TYPE_DEVICE
290         */
291        public static final String COLUMN_ROOT_TYPE = "root_type";
292
293        /**
294         * Flags that apply to a root.
295         * <p>
296         * Type: INTEGER (int)
297         *
298         * @see #FLAG_ADVANCED
299         * @see #FLAG_EMPTY
300         * @see #FLAG_LOCAL_ONLY
301         * @see #FLAG_SUPPORTS_CREATE
302         * @see #FLAG_SUPPORTS_RECENTS
303         * @see #FLAG_SUPPORTS_SEARCH
304         */
305        public static final String COLUMN_FLAGS = "flags";
306
307        /**
308         * Icon resource ID for a root.
309         * <p>
310         * Type: INTEGER (int)
311         */
312        public static final String COLUMN_ICON = "icon";
313
314        /**
315         * Title for a root, which will be shown to a user.
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. The summary may
323         * 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.
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, or {@code null} if unknown or
341         * 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, or {@code null} if the root
349         * supports all MIME types. Multiple MIME types can be separated by a
350         * newline. For example, a root supporting audio might use
351         * "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         * Type of root that represents a storage service, such as a cloud-based
362         * service.
363         *
364         * @see #COLUMN_ROOT_TYPE
365         */
366        public static final int ROOT_TYPE_SERVICE = 1;
367
368        /**
369         * Type of root that represents a shortcut to content that may be
370         * available elsewhere through another storage root.
371         *
372         * @see #COLUMN_ROOT_TYPE
373         */
374        public static final int ROOT_TYPE_SHORTCUT = 2;
375
376        /**
377         * Type of root that represents a physical storage device.
378         *
379         * @see #COLUMN_ROOT_TYPE
380         */
381        public static final int ROOT_TYPE_DEVICE = 3;
382
383        /**
384         * Flag indicating that at least one directory under this root supports
385         * creating content. Roots with this flag will be shown when an
386         * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
387         *
388         * @see #COLUMN_FLAGS
389         */
390        public static final int FLAG_SUPPORTS_CREATE = 1;
391
392        /**
393         * Flag indicating that this root offers content that is strictly local
394         * on the device. That is, no network requests are made for the content.
395         *
396         * @see #COLUMN_FLAGS
397         * @see Intent#EXTRA_LOCAL_ONLY
398         */
399        public static final int FLAG_LOCAL_ONLY = 1 << 1;
400
401        /**
402         * Flag indicating that this root should only be visible to advanced
403         * users.
404         *
405         * @see #COLUMN_FLAGS
406         */
407        public static final int FLAG_ADVANCED = 1 << 2;
408
409        /**
410         * Flag indicating that this root can report recently modified
411         * documents.
412         *
413         * @see #COLUMN_FLAGS
414         * @see DocumentsContract#buildRecentDocumentsUri(String, String)
415         */
416        public static final int FLAG_SUPPORTS_RECENTS = 1 << 3;
417
418        /**
419         * Flag indicating that this root supports search.
420         *
421         * @see #COLUMN_FLAGS
422         * @see DocumentsProvider#querySearchDocuments(String, String,
423         *      String[])
424         */
425        public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
426
427        /**
428         * Flag indicating that this root is currently empty. This may be used
429         * to hide the root when opening documents, but the root will still be
430         * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
431         * also set.
432         *
433         * @see #COLUMN_FLAGS
434         * @see DocumentsProvider#querySearchDocuments(String, String,
435         *      String[])
436         */
437        public static final int FLAG_EMPTY = 1 << 5;
438    }
439
440    /**
441     * Optional boolean flag included in a directory {@link Cursor#getExtras()}
442     * indicating that a document provider is still loading data. For example, a
443     * provider has returned some results, but is still waiting on an
444     * outstanding network request. The provider must send a content changed
445     * notification when loading is finished.
446     *
447     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
448     *      boolean)
449     */
450    public static final String EXTRA_LOADING = "loading";
451
452    /**
453     * Optional string included in a directory {@link Cursor#getExtras()}
454     * providing an informational message that should be shown to a user. For
455     * example, a provider may wish to indicate that not all documents are
456     * available.
457     */
458    public static final String EXTRA_INFO = "info";
459
460    /**
461     * Optional string included in a directory {@link Cursor#getExtras()}
462     * providing an error message that should be shown to a user. For example, a
463     * provider may wish to indicate that a network error occurred. The user may
464     * choose to retry, resulting in a new query.
465     */
466    public static final String EXTRA_ERROR = "error";
467
468    /** {@hide} */
469    public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
470    /** {@hide} */
471    public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
472
473    /** {@hide} */
474    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
475
476    private static final String PATH_ROOT = "root";
477    private static final String PATH_RECENT = "recent";
478    private static final String PATH_DOCUMENT = "document";
479    private static final String PATH_CHILDREN = "children";
480    private static final String PATH_SEARCH = "search";
481
482    private static final String PARAM_QUERY = "query";
483    private static final String PARAM_MANAGE = "manage";
484
485    /**
486     * Build Uri representing the roots of a document provider. When queried, a
487     * provider will return one or more rows with columns defined by
488     * {@link Root}.
489     *
490     * @see DocumentsProvider#queryRoots(String[])
491     */
492    public static Uri buildRootsUri(String authority) {
493        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
494                .authority(authority).appendPath(PATH_ROOT).build();
495    }
496
497    /**
498     * Build Uri representing the given {@link Root#COLUMN_ROOT_ID} in a
499     * document provider.
500     *
501     * @see #getRootId(Uri)
502     */
503    public static Uri buildRootUri(String authority, String rootId) {
504        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
505                .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
506    }
507
508    /**
509     * Build Uri representing the recently modified documents of a specific root
510     * in a document provider. When queried, a provider will return zero or more
511     * rows with columns defined by {@link Document}.
512     *
513     * @see DocumentsProvider#queryRecentDocuments(String, String[])
514     * @see #getRootId(Uri)
515     */
516    public static Uri buildRecentDocumentsUri(String authority, String rootId) {
517        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
518                .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
519                .appendPath(PATH_RECENT).build();
520    }
521
522    /**
523     * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
524     * document provider. When queried, a provider will return a single row with
525     * columns defined by {@link Document}.
526     *
527     * @see DocumentsProvider#queryDocument(String, String[])
528     * @see #getDocumentId(Uri)
529     */
530    public static Uri buildDocumentUri(String authority, String documentId) {
531        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
532                .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
533    }
534
535    /**
536     * Build Uri representing the children of the given directory in a document
537     * provider. When queried, a provider will return zero or more rows with
538     * columns defined by {@link Document}.
539     *
540     * @param parentDocumentId the document to return children for, which must
541     *            be a directory with MIME type of
542     *            {@link Document#MIME_TYPE_DIR}.
543     * @see DocumentsProvider#queryChildDocuments(String, String[], String)
544     * @see #getDocumentId(Uri)
545     */
546    public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
547        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
548                .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
549                .build();
550    }
551
552    /**
553     * Build Uri representing a search for matching documents under a specific
554     * root in a document provider. When queried, a provider will return zero or
555     * more rows with columns defined by {@link Document}.
556     *
557     * @see DocumentsProvider#querySearchDocuments(String, String, String[])
558     * @see #getRootId(Uri)
559     * @see #getSearchDocumentsQuery(Uri)
560     */
561    public static Uri buildSearchDocumentsUri(
562            String authority, String rootId, String query) {
563        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
564                .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
565                .appendQueryParameter(PARAM_QUERY, query).build();
566    }
567
568    /**
569     * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri.
570     */
571    public static String getRootId(Uri rootUri) {
572        final List<String> paths = rootUri.getPathSegments();
573        if (paths.size() < 2) {
574            throw new IllegalArgumentException("Not a root: " + rootUri);
575        }
576        if (!PATH_ROOT.equals(paths.get(0))) {
577            throw new IllegalArgumentException("Not a root: " + rootUri);
578        }
579        return paths.get(1);
580    }
581
582    /**
583     * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
584     */
585    public static String getDocumentId(Uri documentUri) {
586        final List<String> paths = documentUri.getPathSegments();
587        if (paths.size() < 2) {
588            throw new IllegalArgumentException("Not a document: " + documentUri);
589        }
590        if (!PATH_DOCUMENT.equals(paths.get(0))) {
591            throw new IllegalArgumentException("Not a document: " + documentUri);
592        }
593        return paths.get(1);
594    }
595
596    /**
597     * Extract the search query from a Uri built by
598     * {@link #buildSearchDocumentsUri(String, String, String)}.
599     */
600    public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
601        return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
602    }
603
604    /** {@hide} */
605    public static Uri setManageMode(Uri uri) {
606        return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
607    }
608
609    /** {@hide} */
610    public static boolean isManageMode(Uri uri) {
611        return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
612    }
613
614    /**
615     * Return list of all documents that the calling package has "open." These
616     * are Uris matching {@link DocumentsContract} to which persistent
617     * read/write access has been granted, usually through
618     * {@link Intent#ACTION_OPEN_DOCUMENT} or
619     * {@link Intent#ACTION_CREATE_DOCUMENT}.
620     *
621     * @see Context#grantUriPermission(String, Uri, int)
622     * @see Context#revokeUriPermission(Uri, int)
623     * @see ContentResolver#getIncomingUriPermissionGrants(int, int)
624     */
625    public static Uri[] getOpenDocuments(Context context) {
626        final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
627                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION;
628        final Uri[] uris = context.getContentResolver()
629                .getIncomingUriPermissionGrants(openedFlags, openedFlags);
630
631        // Filter to only include document providers
632        final PackageManager pm = context.getPackageManager();
633        final List<Uri> result = Lists.newArrayList();
634        for (Uri uri : uris) {
635            final ProviderInfo info = pm.resolveContentProvider(
636                    uri.getAuthority(), PackageManager.GET_META_DATA);
637            if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) {
638                result.add(uri);
639            }
640        }
641
642        return result.toArray(new Uri[result.size()]);
643    }
644
645    /**
646     * Return thumbnail representing the document at the given Uri. Callers are
647     * responsible for their own in-memory caching.
648     *
649     * @param documentUri document to return thumbnail for, which must have
650     *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
651     * @param size optimal thumbnail size desired. A provider may return a
652     *            thumbnail of a different size, but never more than double the
653     *            requested size.
654     * @param signal signal used to indicate that caller is no longer interested
655     *            in the thumbnail.
656     * @return decoded thumbnail, or {@code null} if problem was encountered.
657     * @see DocumentsProvider#openDocumentThumbnail(String, Point,
658     *      android.os.CancellationSignal)
659     */
660    public static Bitmap getDocumentThumbnail(
661            ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
662        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
663                documentUri.getAuthority());
664        try {
665            return getDocumentThumbnail(client, documentUri, size, signal);
666        } catch (RemoteException e) {
667            return null;
668        } finally {
669            ContentProviderClient.closeQuietly(client);
670        }
671    }
672
673    /** {@hide} */
674    public static Bitmap getDocumentThumbnail(
675            ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
676            throws RemoteException {
677        final Bundle openOpts = new Bundle();
678        openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
679
680        AssetFileDescriptor afd = null;
681        try {
682            afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
683
684            final FileDescriptor fd = afd.getFileDescriptor();
685            final long offset = afd.getStartOffset();
686
687            // Try seeking on the returned FD, since it gives us the most
688            // optimal decode path; otherwise fall back to buffering.
689            BufferedInputStream is = null;
690            try {
691                Libcore.os.lseek(fd, offset, SEEK_SET);
692            } catch (ErrnoException e) {
693                is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
694                is.mark(THUMBNAIL_BUFFER_SIZE);
695            }
696
697            // We requested a rough thumbnail size, but the remote size may have
698            // returned something giant, so defensively scale down as needed.
699            final BitmapFactory.Options opts = new BitmapFactory.Options();
700            opts.inJustDecodeBounds = true;
701            if (is != null) {
702                BitmapFactory.decodeStream(is, null, opts);
703            } else {
704                BitmapFactory.decodeFileDescriptor(fd, null, opts);
705            }
706
707            final int widthSample = opts.outWidth / size.x;
708            final int heightSample = opts.outHeight / size.y;
709
710            opts.inJustDecodeBounds = false;
711            opts.inSampleSize = Math.min(widthSample, heightSample);
712            Log.d(TAG, "Decoding with sample size " + opts.inSampleSize);
713            if (is != null) {
714                is.reset();
715                return BitmapFactory.decodeStream(is, null, opts);
716            } else {
717                try {
718                    Libcore.os.lseek(fd, offset, SEEK_SET);
719                } catch (ErrnoException e) {
720                    e.rethrowAsIOException();
721                }
722                return BitmapFactory.decodeFileDescriptor(fd, null, opts);
723            }
724        } catch (IOException e) {
725            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
726            return null;
727        } finally {
728            IoUtils.closeQuietly(afd);
729        }
730    }
731
732    /**
733     * Create a new document with given MIME type and display name.
734     *
735     * @param parentDocumentUri directory with
736     *            {@link Document#FLAG_DIR_SUPPORTS_CREATE}
737     * @param mimeType MIME type of new document
738     * @param displayName name of new document
739     * @return newly created document, or {@code null} if failed
740     * @hide
741     */
742    public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
743            String mimeType, String displayName) {
744        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
745                parentDocumentUri.getAuthority());
746        try {
747            return createDocument(client, parentDocumentUri, mimeType, displayName);
748        } finally {
749            ContentProviderClient.closeQuietly(client);
750        }
751    }
752
753    /** {@hide} */
754    public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
755            String mimeType, String displayName) {
756        final Bundle in = new Bundle();
757        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
758        in.putString(Document.COLUMN_MIME_TYPE, mimeType);
759        in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
760
761        try {
762            final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
763            return buildDocumentUri(
764                    parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
765        } catch (Exception e) {
766            Log.w(TAG, "Failed to create document", e);
767            return null;
768        }
769    }
770
771    /**
772     * Delete the given document.
773     *
774     * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
775     */
776    public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
777        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
778                documentUri.getAuthority());
779        try {
780            return deleteDocument(client, documentUri);
781        } finally {
782            ContentProviderClient.closeQuietly(client);
783        }
784    }
785
786    /** {@hide} */
787    public static boolean deleteDocument(ContentProviderClient client, Uri documentUri) {
788        final Bundle in = new Bundle();
789        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
790
791        try {
792            final Bundle out = client.call(METHOD_DELETE_DOCUMENT, null, in);
793            return true;
794        } catch (Exception e) {
795            Log.w(TAG, "Failed to delete document", e);
796            return false;
797        }
798    }
799}
800