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