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