DocumentsContract.java revision 66516697a29cf00d93893a1011d3befc7c1ee37f
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 android.content.ContentProvider;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Intent;
23import android.content.pm.ProviderInfo;
24import android.content.res.AssetFileDescriptor;
25import android.database.Cursor;
26import android.graphics.Bitmap;
27import android.graphics.BitmapFactory;
28import android.graphics.Point;
29import android.net.Uri;
30import android.os.Bundle;
31import android.util.Log;
32
33import libcore.io.IoUtils;
34
35import java.io.IOException;
36import java.io.InputStream;
37import java.util.List;
38
39/**
40 * The contract between a storage backend and the platform. Contains definitions
41 * for the supported URIs and columns.
42 */
43public final class DocumentsContract {
44    private static final String TAG = "Documents";
45
46    // content://com.example/roots/
47    // content://com.example/roots/sdcard/
48    // content://com.example/roots/sdcard/docs/0/
49    // content://com.example/roots/sdcard/docs/0/contents/
50    // content://com.example/roots/sdcard/docs/0/search/?query=pony
51
52    /**
53     * MIME type of a document which is a directory that may contain additional
54     * documents.
55     *
56     * @see #buildContentsUri(Uri)
57     */
58    public static final String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc";
59
60    /** {@hide} */
61    public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER";
62
63    /**
64     * {@link DocumentColumns#DOC_ID} value representing the root directory of a
65     * storage root.
66     */
67    public static final String ROOT_DOC_ID = "0";
68
69    /**
70     * Flag indicating that a document is a directory that supports creation of
71     * new files within it.
72     *
73     * @see DocumentColumns#FLAGS
74     * @see #buildContentsUri(Uri)
75     */
76    public static final int FLAG_SUPPORTS_CREATE = 1;
77
78    /**
79     * Flag indicating that a document is renamable.
80     *
81     * @see DocumentColumns#FLAGS
82     * @see #renameDocument(ContentResolver, Uri, String)
83     */
84    public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
85
86    /**
87     * Flag indicating that a document is deletable.
88     *
89     * @see DocumentColumns#FLAGS
90     */
91    public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
92
93    /**
94     * Flag indicating that a document can be represented as a thumbnail.
95     *
96     * @see DocumentColumns#FLAGS
97     * @see #getThumbnail(ContentResolver, Uri, Point)
98     */
99    public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
100
101    /**
102     * Flag indicating that a document is a directory that supports search.
103     *
104     * @see DocumentColumns#FLAGS
105     */
106    public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
107
108    /**
109     * Optimal dimensions for a document thumbnail request, stored as a
110     * {@link Point} object. This is only a hint, and the returned thumbnail may
111     * have different dimensions.
112     *
113     * @see ContentProvider#openTypedAssetFile(Uri, String, Bundle)
114     */
115    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
116
117    /**
118     * Extra boolean flag included in a directory {@link Cursor#getExtras()}
119     * indicating that the backend can provide additional data if requested,
120     * such as additional search results.
121     */
122    public static final String EXTRA_HAS_MORE = "has_more";
123
124    /**
125     * Extra boolean flag included in a {@link Cursor#respond(Bundle)} call to a
126     * directory to request that additional data should be fetched. When
127     * requested data is ready, the provider should send a change notification
128     * to cause a requery.
129     *
130     * @see Cursor#respond(Bundle)
131     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
132     *      boolean)
133     */
134    public static final String EXTRA_REQUEST_MORE = "request_more";
135
136    private static final String PATH_ROOTS = "roots";
137    private static final String PATH_DOCS = "docs";
138    private static final String PATH_CONTENTS = "contents";
139    private static final String PATH_SEARCH = "search";
140
141    public static final String PARAM_QUERY = "query";
142
143    /**
144     * Build URI representing the roots in a storage backend.
145     */
146    public static Uri buildRootsUri(String authority) {
147        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
148                .authority(authority).appendPath(PATH_ROOTS).build();
149    }
150
151    public static Uri buildRootUri(String authority, String rootId) {
152        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
153                .authority(authority).appendPath(PATH_ROOTS).appendPath(rootId).build();
154    }
155
156    /**
157     * Build URI representing the given {@link DocumentColumns#DOC_ID} in a
158     * storage root.
159     */
160    public static Uri buildDocumentUri(String authority, String rootId, String docId) {
161        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
162                .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
163                .build();
164    }
165
166    /**
167     * Build URI representing the contents of the given directory in a storage
168     * backend. The given document must be {@link #MIME_TYPE_DIRECTORY}.
169     */
170    public static Uri buildContentsUri(String authority, String rootId, String docId) {
171        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
172                .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
173                .appendPath(PATH_CONTENTS).build();
174    }
175
176    /**
177     * Build URI representing a search for matching documents under a directory
178     * in a storage backend.
179     */
180    public static Uri buildSearchUri(String authority, String rootId, String docId, String query) {
181        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
182                .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
183                .appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build();
184    }
185
186    public static Uri buildDocumentUri(Uri relatedUri, String docId) {
187        return buildDocumentUri(relatedUri.getAuthority(), getRootId(relatedUri), docId);
188    }
189
190    public static Uri buildContentsUri(Uri relatedUri) {
191        return buildContentsUri(
192                relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri));
193    }
194
195    public static Uri buildSearchUri(Uri relatedUri, String query) {
196        return buildSearchUri(
197                relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri), query);
198    }
199
200    public static String getRootId(Uri documentUri) {
201        final List<String> paths = documentUri.getPathSegments();
202        if (!PATH_ROOTS.equals(paths.get(0))) {
203            throw new IllegalArgumentException();
204        }
205        return paths.get(1);
206    }
207
208    public static String getDocId(Uri documentUri) {
209        final List<String> paths = documentUri.getPathSegments();
210        if (!PATH_ROOTS.equals(paths.get(0))) {
211            throw new IllegalArgumentException();
212        }
213        if (!PATH_DOCS.equals(paths.get(2))) {
214            throw new IllegalArgumentException();
215        }
216        return paths.get(3);
217    }
218
219    public static String getSearchQuery(Uri documentUri) {
220        return documentUri.getQueryParameter(PARAM_QUERY);
221    }
222
223    /**
224     * These are standard columns for document URIs. Storage backend providers
225     * <em>must</em> support at least these columns when queried.
226     *
227     * @see Intent#ACTION_OPEN_DOCUMENT
228     * @see Intent#ACTION_CREATE_DOCUMENT
229     */
230    public interface DocumentColumns extends OpenableColumns {
231        /**
232         * The ID for a document under a storage backend root. Values
233         * <em>must</em> never change once returned. This field is read-only to
234         * document clients.
235         * <p>
236         * Type: STRING
237         */
238        public static final String DOC_ID = "doc_id";
239
240        /**
241         * MIME type of a document, matching the value returned by
242         * {@link ContentResolver#getType(android.net.Uri)}. This field must be
243         * provided when a new document is created, but after that the field is
244         * read-only.
245         * <p>
246         * Type: STRING
247         *
248         * @see DocumentsContract#MIME_TYPE_DIRECTORY
249         */
250        public static final String MIME_TYPE = "mime_type";
251
252        /**
253         * Timestamp when a document was last modified, in milliseconds since
254         * January 1, 1970 00:00:00.0 UTC. This field is read-only to document
255         * clients.
256         * <p>
257         * Type: INTEGER (long)
258         *
259         * @see System#currentTimeMillis()
260         */
261        public static final String LAST_MODIFIED = "last_modified";
262
263        /**
264         * Flags that apply to a specific document. This field is read-only to
265         * document clients.
266         * <p>
267         * Type: INTEGER (int)
268         */
269        public static final String FLAGS = "flags";
270
271        /**
272         * Summary for this document, or {@code null} to omit.
273         * <p>
274         * Type: STRING
275         */
276        public static final String SUMMARY = "summary";
277    }
278
279    /**
280     * Root that represents a cloud-based storage service.
281     *
282     * @see RootColumns#ROOT_TYPE
283     */
284    public static final int ROOT_TYPE_SERVICE = 1;
285
286    /**
287     * Root that represents a shortcut to content that may be available
288     * elsewhere through another storage root.
289     *
290     * @see RootColumns#ROOT_TYPE
291     */
292    public static final int ROOT_TYPE_SHORTCUT = 2;
293
294    /**
295     * Root that represents a physical storage device.
296     *
297     * @see RootColumns#ROOT_TYPE
298     */
299    public static final int ROOT_TYPE_DEVICE = 3;
300
301    /**
302     * Root that represents a physical storage device that should only be
303     * displayed to advanced users.
304     *
305     * @see RootColumns#ROOT_TYPE
306     */
307    public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
308
309    /**
310     * These are standard columns for the roots URI.
311     *
312     * @see DocumentsContract#buildRootsUri(String)
313     */
314    public interface RootColumns {
315        public static final String ROOT_ID = "root_id";
316
317        /**
318         * Storage root type, use for clustering.
319         * <p>
320         * Type: INTEGER (int)
321         *
322         * @see DocumentsContract#ROOT_TYPE_SERVICE
323         * @see DocumentsContract#ROOT_TYPE_DEVICE
324         */
325        public static final String ROOT_TYPE = "root_type";
326
327        /**
328         * Icon resource ID for this storage root, or {@code 0} to use the
329         * default {@link ProviderInfo#icon}.
330         * <p>
331         * Type: INTEGER (int)
332         */
333        public static final String ICON = "icon";
334
335        /**
336         * Title for this storage root, or {@code null} to use the default
337         * {@link ProviderInfo#labelRes}.
338         * <p>
339         * Type: STRING
340         */
341        public static final String TITLE = "title";
342
343        /**
344         * Summary for this storage root, or {@code null} to omit.
345         * <p>
346         * Type: STRING
347         */
348        public static final String SUMMARY = "summary";
349
350        /**
351         * Number of free bytes of available in this storage root, or -1 if
352         * unknown or unbounded.
353         * <p>
354         * Type: INTEGER (long)
355         */
356        public static final String AVAILABLE_BYTES = "available_bytes";
357    }
358
359    /**
360     * Return thumbnail representing the document at the given URI. Callers are
361     * responsible for their own caching. Given document must have
362     * {@link #FLAG_SUPPORTS_THUMBNAIL} set.
363     *
364     * @return decoded thumbnail, or {@code null} if problem was encountered.
365     */
366    public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
367        final Bundle opts = new Bundle();
368        opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size);
369
370        InputStream is = null;
371        try {
372            is = new AssetFileDescriptor.AutoCloseInputStream(
373                    resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts));
374            return BitmapFactory.decodeStream(is);
375        } catch (IOException e) {
376            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
377            return null;
378        } finally {
379            IoUtils.closeQuietly(is);
380        }
381    }
382
383    /**
384     * Rename the document at the given URI. Given document must have
385     * {@link #FLAG_SUPPORTS_RENAME} set.
386     *
387     * @return if rename was successful.
388     */
389    public static boolean renameDocument(
390            ContentResolver resolver, Uri documentUri, String displayName) {
391        final ContentValues values = new ContentValues();
392        values.put(DocumentColumns.DISPLAY_NAME, displayName);
393        return (resolver.update(documentUri, values, null, null) == 1);
394    }
395}
396