DocumentsContract.java revision aeb16e2435f9975b9fa1fc4b747796647a21292e
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.graphics.drawable.Drawable;
34import android.net.Uri;
35import android.os.Bundle;
36import android.os.Parcel;
37import android.os.ParcelFileDescriptor;
38import android.os.ParcelFileDescriptor.OnCloseListener;
39import android.os.Parcelable;
40import android.util.Log;
41
42import com.android.internal.util.Preconditions;
43import com.google.android.collect.Lists;
44
45import libcore.io.ErrnoException;
46import libcore.io.IoBridge;
47import libcore.io.IoUtils;
48import libcore.io.Libcore;
49
50import java.io.FileDescriptor;
51import java.io.IOException;
52import java.util.List;
53
54/**
55 * Defines the contract between a documents provider and the platform.
56 * <p>
57 * To create a document provider, extend {@link DocumentsProvider}, which
58 * provides a foundational implementation of this contract.
59 *
60 * @see DocumentsProvider
61 */
62public final class DocumentsContract {
63    private static final String TAG = "Documents";
64
65    // content://com.example/docs/12/
66    // content://com.example/docs/12/children/
67    // content://com.example/docs/12/search/?query=pony
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_DOCUMENTS = "android.provider.action.MANAGE_DOCUMENTS";
77
78    /** {@hide} */
79    public static final String
80            ACTION_DOCUMENT_ROOT_CHANGED = "android.provider.action.DOCUMENT_ROOT_CHANGED";
81
82    /**
83     * Constants for individual documents.
84     */
85    public final static class Documents {
86        private Documents() {
87        }
88
89        /**
90         * MIME type of a document which is a directory that may contain additional
91         * documents.
92         */
93        public static final String MIME_TYPE_DIR = "vnd.android.doc/dir";
94
95        /**
96         * Flag indicating that a document is a directory that supports creation of
97         * new files within it.
98         *
99         * @see DocumentColumns#FLAGS
100         */
101        public static final int FLAG_SUPPORTS_CREATE = 1;
102
103        /**
104         * Flag indicating that a document is renamable.
105         *
106         * @see DocumentColumns#FLAGS
107         */
108        public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
109
110        /**
111         * Flag indicating that a document is deletable.
112         *
113         * @see DocumentColumns#FLAGS
114         */
115        public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
116
117        /**
118         * Flag indicating that a document can be represented as a thumbnail.
119         *
120         * @see DocumentColumns#FLAGS
121         */
122        public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
123
124        /**
125         * Flag indicating that a document is a directory that supports search.
126         *
127         * @see DocumentColumns#FLAGS
128         */
129        public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
130
131        /**
132         * Flag indicating that a document supports writing.
133         *
134         * @see DocumentColumns#FLAGS
135         */
136        public static final int FLAG_SUPPORTS_WRITE = 1 << 5;
137
138        /**
139         * Flag indicating that a document is a directory that prefers its contents
140         * be shown in a larger format grid. Usually suitable when a directory
141         * contains mostly pictures.
142         *
143         * @see DocumentColumns#FLAGS
144         */
145        public static final int FLAG_PREFERS_GRID = 1 << 6;
146    }
147
148    /**
149     * Extra boolean flag included in a directory {@link Cursor#getExtras()}
150     * indicating that a document provider is still loading data. For example, a
151     * provider has returned some results, but is still waiting on an
152     * outstanding network request.
153     *
154     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
155     *      boolean)
156     */
157    public static final String EXTRA_LOADING = "loading";
158
159    /**
160     * Extra string included in a directory {@link Cursor#getExtras()}
161     * providing an informational message that should be shown to a user. For
162     * example, a provider may wish to indicate that not all documents are
163     * available.
164     */
165    public static final String EXTRA_INFO = "info";
166
167    /**
168     * Extra string included in a directory {@link Cursor#getExtras()} providing
169     * an error message that should be shown to a user. For example, a provider
170     * may wish to indicate that a network error occurred. The user may choose
171     * to retry, resulting in a new query.
172     */
173    public static final String EXTRA_ERROR = "error";
174
175    /** {@hide} */
176    public static final String METHOD_GET_ROOTS = "android:getRoots";
177    /** {@hide} */
178    public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
179    /** {@hide} */
180    public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
181    /** {@hide} */
182    public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
183
184    /** {@hide} */
185    public static final String EXTRA_AUTHORITY = "authority";
186    /** {@hide} */
187    public static final String EXTRA_PACKAGE_NAME = "packageName";
188    /** {@hide} */
189    public static final String EXTRA_URI = "uri";
190    /** {@hide} */
191    public static final String EXTRA_ROOTS = "roots";
192    /** {@hide} */
193    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
194
195    private static final String PATH_DOCS = "docs";
196    private static final String PATH_CHILDREN = "children";
197    private static final String PATH_SEARCH = "search";
198
199    private static final String PARAM_QUERY = "query";
200
201    /**
202     * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a
203     * document provider.
204     */
205    public static Uri buildDocumentUri(String authority, String docId) {
206        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
207                .authority(authority).appendPath(PATH_DOCS).appendPath(docId).build();
208    }
209
210    /**
211     * Build Uri representing the contents of the given directory in a document
212     * provider. The given document must be {@link Documents#MIME_TYPE_DIR}.
213     *
214     * @hide
215     */
216    public static Uri buildChildrenUri(String authority, String docId) {
217        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
218                .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_CHILDREN).build();
219    }
220
221    /**
222     * Build Uri representing a search for matching documents under a specific
223     * directory in a document provider. The given document must have
224     * {@link Documents#FLAG_SUPPORTS_SEARCH}.
225     *
226     * @hide
227     */
228    public static Uri buildSearchUri(String authority, String docId, String query) {
229        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
230                .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_SEARCH)
231                .appendQueryParameter(PARAM_QUERY, query).build();
232    }
233
234    /**
235     * Extract the {@link DocumentColumns#DOC_ID} from the given Uri.
236     */
237    public static String getDocId(Uri documentUri) {
238        final List<String> paths = documentUri.getPathSegments();
239        if (paths.size() < 2) {
240            throw new IllegalArgumentException("Not a document: " + documentUri);
241        }
242        if (!PATH_DOCS.equals(paths.get(0))) {
243            throw new IllegalArgumentException("Not a document: " + documentUri);
244        }
245        return paths.get(1);
246    }
247
248    /** {@hide} */
249    public static String getSearchQuery(Uri documentUri) {
250        return documentUri.getQueryParameter(PARAM_QUERY);
251    }
252
253    /**
254     * Standard columns for document queries. Document providers <em>must</em>
255     * support at least these columns when queried.
256     */
257    public interface DocumentColumns extends OpenableColumns {
258        /**
259         * Unique ID for a document. Values <em>must</em> never change once
260         * returned, since they may used for long-term Uri permission grants.
261         * <p>
262         * Type: STRING
263         */
264        public static final String DOC_ID = "doc_id";
265
266        /**
267         * MIME type of a document.
268         * <p>
269         * Type: STRING
270         *
271         * @see Documents#MIME_TYPE_DIR
272         */
273        public static final String MIME_TYPE = "mime_type";
274
275        /**
276         * Timestamp when a document was last modified, in milliseconds since
277         * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. Document
278         * providers can update this field using events from
279         * {@link OnCloseListener} or other reliable
280         * {@link ParcelFileDescriptor} transports.
281         * <p>
282         * Type: INTEGER (long)
283         *
284         * @see System#currentTimeMillis()
285         */
286        public static final String LAST_MODIFIED = "last_modified";
287
288        /**
289         * Specific icon resource for a document, or {@code null} to resolve
290         * default using {@link #MIME_TYPE}.
291         * <p>
292         * Type: INTEGER (int)
293         */
294        public static final String ICON = "icon";
295
296        /**
297         * Summary for a document, or {@code null} to omit.
298         * <p>
299         * Type: STRING
300         */
301        public static final String SUMMARY = "summary";
302
303        /**
304         * Flags that apply to a specific document.
305         * <p>
306         * Type: INTEGER (int)
307         */
308        public static final String FLAGS = "flags";
309    }
310
311    /**
312     * Metadata about a specific root of documents.
313     */
314    public final static class DocumentRoot implements Parcelable {
315        /**
316         * Root that represents a storage service, such as a cloud-based
317         * service.
318         *
319         * @see #rootType
320         */
321        public static final int ROOT_TYPE_SERVICE = 1;
322
323        /**
324         * Root that represents a shortcut to content that may be available
325         * elsewhere through another storage root.
326         *
327         * @see #rootType
328         */
329        public static final int ROOT_TYPE_SHORTCUT = 2;
330
331        /**
332         * Root that represents a physical storage device.
333         *
334         * @see #rootType
335         */
336        public static final int ROOT_TYPE_DEVICE = 3;
337
338        /**
339         * Root that represents a physical storage device that should only be
340         * displayed to advanced users.
341         *
342         * @see #rootType
343         */
344        public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
345
346        /**
347         * Flag indicating that at least one directory under this root supports
348         * creating content.
349         *
350         * @see #flags
351         */
352        public static final int FLAG_SUPPORTS_CREATE = 1;
353
354        /**
355         * Flag indicating that this root offers content that is strictly local
356         * on the device. That is, no network requests are made for the content.
357         *
358         * @see #flags
359         */
360        public static final int FLAG_LOCAL_ONLY = 1 << 1;
361
362        /** {@hide} */
363        public String authority;
364
365        /**
366         * Root type, use for clustering.
367         *
368         * @see #ROOT_TYPE_SERVICE
369         * @see #ROOT_TYPE_DEVICE
370         */
371        public int rootType;
372
373        /**
374         * Flags for this root.
375         *
376         * @see #FLAG_LOCAL_ONLY
377         */
378        public int flags;
379
380        /**
381         * Icon resource ID for this root.
382         */
383        public int icon;
384
385        /**
386         * Title for this root.
387         */
388        public String title;
389
390        /**
391         * Summary for this root. May be {@code null}.
392         */
393        public String summary;
394
395        /**
396         * Document which is a directory that represents the top of this root.
397         * Must not be {@code null}.
398         *
399         * @see DocumentColumns#DOC_ID
400         */
401        public String docId;
402
403        /**
404         * Document which is a directory representing recently modified
405         * documents under this root. This directory should return at most two
406         * dozen documents modified within the last 90 days. May be {@code null}
407         * if this root doesn't support recents.
408         *
409         * @see DocumentColumns#DOC_ID
410         */
411        public String recentDocId;
412
413        /**
414         * Number of free bytes of available in this root, or -1 if unknown or
415         * unbounded.
416         */
417        public long availableBytes;
418
419        /**
420         * Set of MIME type filters describing the content offered by this root,
421         * or {@code null} to indicate that all MIME types are supported. For
422         * example, a provider only supporting audio and video might set this to
423         * {@code ["audio/*", "video/*"]}.
424         */
425        public String[] mimeTypes;
426
427        public DocumentRoot() {
428        }
429
430        /** {@hide} */
431        public DocumentRoot(Parcel in) {
432            rootType = in.readInt();
433            flags = in.readInt();
434            icon = in.readInt();
435            title = in.readString();
436            summary = in.readString();
437            docId = in.readString();
438            recentDocId = in.readString();
439            availableBytes = in.readLong();
440            mimeTypes = in.readStringArray();
441        }
442
443        /** {@hide} */
444        public Drawable loadIcon(Context context) {
445            if (icon != 0) {
446                if (authority != null) {
447                    final PackageManager pm = context.getPackageManager();
448                    final ProviderInfo info = pm.resolveContentProvider(authority, 0);
449                    if (info != null) {
450                        return pm.getDrawable(info.packageName, icon, info.applicationInfo);
451                    }
452                } else {
453                    return context.getResources().getDrawable(icon);
454                }
455            }
456            return null;
457        }
458
459        @Override
460        public int describeContents() {
461            return 0;
462        }
463
464        @Override
465        public void writeToParcel(Parcel dest, int flags) {
466            Preconditions.checkNotNull(docId);
467
468            dest.writeInt(rootType);
469            dest.writeInt(flags);
470            dest.writeInt(icon);
471            dest.writeString(title);
472            dest.writeString(summary);
473            dest.writeString(docId);
474            dest.writeString(recentDocId);
475            dest.writeLong(availableBytes);
476            dest.writeStringArray(mimeTypes);
477        }
478
479        public static final Creator<DocumentRoot> CREATOR = new Creator<DocumentRoot>() {
480            @Override
481            public DocumentRoot createFromParcel(Parcel in) {
482                return new DocumentRoot(in);
483            }
484
485            @Override
486            public DocumentRoot[] newArray(int size) {
487                return new DocumentRoot[size];
488            }
489        };
490    }
491
492    /**
493     * Return list of all documents that the calling package has "open." These
494     * are Uris matching {@link DocumentsContract} to which persistent
495     * read/write access has been granted, usually through
496     * {@link Intent#ACTION_OPEN_DOCUMENT} or
497     * {@link Intent#ACTION_CREATE_DOCUMENT}.
498     *
499     * @see Context#grantUriPermission(String, Uri, int)
500     * @see ContentResolver#getIncomingUriPermissionGrants(int, int)
501     */
502    public static Uri[] getOpenDocuments(Context context) {
503        final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
504                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION;
505        final Uri[] uris = context.getContentResolver()
506                .getIncomingUriPermissionGrants(openedFlags, openedFlags);
507
508        // Filter to only include document providers
509        final PackageManager pm = context.getPackageManager();
510        final List<Uri> result = Lists.newArrayList();
511        for (Uri uri : uris) {
512            final ProviderInfo info = pm.resolveContentProvider(
513                    uri.getAuthority(), PackageManager.GET_META_DATA);
514            if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) {
515                result.add(uri);
516            }
517        }
518
519        return result.toArray(new Uri[result.size()]);
520    }
521
522    /**
523     * Return thumbnail representing the document at the given URI. Callers are
524     * responsible for their own in-memory caching. Given document must have
525     * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set.
526     *
527     * @return decoded thumbnail, or {@code null} if problem was encountered.
528     * @hide
529     */
530    public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
531        final Bundle openOpts = new Bundle();
532        openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
533
534        AssetFileDescriptor afd = null;
535        try {
536            afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts);
537
538            final FileDescriptor fd = afd.getFileDescriptor();
539            final long offset = afd.getStartOffset();
540            final long length = afd.getDeclaredLength();
541
542            // Some thumbnails might be a region inside a larger file, such as
543            // an EXIF thumbnail. Since BitmapFactory aggressively seeks around
544            // the entire file, we read the region manually.
545            byte[] region = null;
546            if (offset > 0 && length <= 64 * KB_IN_BYTES) {
547                region = new byte[(int) length];
548                Libcore.os.lseek(fd, offset, SEEK_SET);
549                if (IoBridge.read(fd, region, 0, region.length) != region.length) {
550                    region = null;
551                }
552            }
553
554            // We requested a rough thumbnail size, but the remote size may have
555            // returned something giant, so defensively scale down as needed.
556            final BitmapFactory.Options opts = new BitmapFactory.Options();
557            opts.inJustDecodeBounds = true;
558            if (region != null) {
559                BitmapFactory.decodeByteArray(region, 0, region.length, opts);
560            } else {
561                BitmapFactory.decodeFileDescriptor(fd, null, opts);
562            }
563
564            final int widthSample = opts.outWidth / size.x;
565            final int heightSample = opts.outHeight / size.y;
566
567            opts.inJustDecodeBounds = false;
568            opts.inSampleSize = Math.min(widthSample, heightSample);
569            Log.d(TAG, "Decoding with sample size " + opts.inSampleSize);
570            if (region != null) {
571                return BitmapFactory.decodeByteArray(region, 0, region.length, opts);
572            } else {
573                return BitmapFactory.decodeFileDescriptor(fd, null, opts);
574            }
575        } catch (ErrnoException e) {
576            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
577            return null;
578        } catch (IOException e) {
579            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
580            return null;
581        } finally {
582            IoUtils.closeQuietly(afd);
583        }
584    }
585
586    /** {@hide} */
587    public static List<DocumentRoot> getDocumentRoots(ContentProviderClient client) {
588        try {
589            final Bundle out = client.call(METHOD_GET_ROOTS, null, null);
590            final List<DocumentRoot> roots = out.getParcelableArrayList(EXTRA_ROOTS);
591            return roots;
592        } catch (Exception e) {
593            Log.w(TAG, "Failed to get roots", e);
594            return null;
595        }
596    }
597
598    /**
599     * Create a new document under the given parent document with MIME type and
600     * display name.
601     *
602     * @param docId document with {@link Documents#FLAG_SUPPORTS_CREATE}
603     * @param mimeType MIME type of new document
604     * @param displayName name of new document
605     * @return newly created document, or {@code null} if failed
606     * @hide
607     */
608    public static String createDocument(
609            ContentProviderClient client, String docId, String mimeType, String displayName) {
610        final Bundle in = new Bundle();
611        in.putString(DocumentColumns.DOC_ID, docId);
612        in.putString(DocumentColumns.MIME_TYPE, mimeType);
613        in.putString(DocumentColumns.DISPLAY_NAME, displayName);
614
615        try {
616            final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
617            return out.getString(DocumentColumns.DOC_ID);
618        } catch (Exception e) {
619            Log.w(TAG, "Failed to create document", e);
620            return null;
621        }
622    }
623
624    /**
625     * Rename the given document.
626     *
627     * @param docId document with {@link Documents#FLAG_SUPPORTS_RENAME}
628     * @return document which may have changed due to rename, or {@code null} if
629     *         rename failed.
630     * @hide
631     */
632    public static String renameDocument(
633            ContentProviderClient client, String docId, String displayName) {
634        final Bundle in = new Bundle();
635        in.putString(DocumentColumns.DOC_ID, docId);
636        in.putString(DocumentColumns.DISPLAY_NAME, displayName);
637
638        try {
639            final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
640            return out.getString(DocumentColumns.DOC_ID);
641        } catch (Exception e) {
642            Log.w(TAG, "Failed to rename document", e);
643            return null;
644        }
645    }
646
647    /**
648     * Delete the given document.
649     *
650     * @param docId document with {@link Documents#FLAG_SUPPORTS_DELETE}
651     * @hide
652     */
653    public static boolean deleteDocument(ContentProviderClient client, String docId) {
654        final Bundle in = new Bundle();
655        in.putString(DocumentColumns.DOC_ID, docId);
656
657        try {
658            client.call(METHOD_DELETE_DOCUMENT, null, in);
659            return true;
660        } catch (Exception e) {
661            Log.w(TAG, "Failed to delete document", e);
662            return false;
663        }
664    }
665}
666