• 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.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
20 import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
21 import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
22 import static android.provider.DocumentsContract.getDocumentId;
23 import static android.provider.DocumentsContract.getRootId;
24 import static android.provider.DocumentsContract.getSearchDocumentsQuery;
25 
26 import android.content.ContentProvider;
27 import android.content.ContentResolver;
28 import android.content.ContentValues;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.UriMatcher;
32 import android.content.pm.PackageManager;
33 import android.content.pm.ProviderInfo;
34 import android.content.res.AssetFileDescriptor;
35 import android.database.Cursor;
36 import android.graphics.Point;
37 import android.net.Uri;
38 import android.os.Bundle;
39 import android.os.CancellationSignal;
40 import android.os.ParcelFileDescriptor;
41 import android.os.ParcelFileDescriptor.OnCloseListener;
42 import android.provider.DocumentsContract.Document;
43 import android.provider.DocumentsContract.Root;
44 import android.util.Log;
45 
46 import libcore.io.IoUtils;
47 
48 import java.io.FileNotFoundException;
49 
50 /**
51  * Base class for a document provider. A document provider offers read and write
52  * access to durable files, such as files stored on a local disk, or files in a
53  * cloud storage service. To create a document provider, extend this class,
54  * implement the abstract methods, and add it to your manifest like this:
55  *
56  * <pre class="prettyprint">&lt;manifest&gt;
57  *    ...
58  *    &lt;application&gt;
59  *        ...
60  *        &lt;provider
61  *            android:name="com.example.MyCloudProvider"
62  *            android:authorities="com.example.mycloudprovider"
63  *            android:exported="true"
64  *            android:grantUriPermissions="true"
65  *            android:permission="android.permission.MANAGE_DOCUMENTS"
66  *            android:enabled="@bool/isAtLeastKitKat"&gt;
67  *            &lt;intent-filter&gt;
68  *                &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
69  *            &lt;/intent-filter&gt;
70  *        &lt;/provider&gt;
71  *        ...
72  *    &lt;/application&gt;
73  *&lt;/manifest&gt;</pre>
74  * <p>
75  * When defining your provider, you must protect it with
76  * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission
77  * only the system can obtain. Applications cannot use a documents provider
78  * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or
79  * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively
80  * navigate and select documents. When a user selects documents through that UI,
81  * the system issues narrow URI permission grants to the requesting application.
82  * </p>
83  * <h3>Documents</h3>
84  * <p>
85  * A document can be either an openable stream (with a specific MIME type), or a
86  * directory containing additional documents (with the
87  * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top
88  * of a subtree containing zero or more documents, which can recursively contain
89  * even more documents and directories.
90  * </p>
91  * <p>
92  * Each document can have different capabilities, as described by
93  * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented
94  * as a thumbnail, your provider can set
95  * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement
96  * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return
97  * that thumbnail.
98  * </p>
99  * <p>
100  * Each document under a provider is uniquely referenced by its
101  * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A
102  * single document can be included in multiple directories when responding to
103  * {@link #queryChildDocuments(String, String[], String)}. For example, a
104  * provider might surface a single photo in multiple locations: once in a
105  * directory of geographic locations, and again in a directory of dates.
106  * </p>
107  * <h3>Roots</h3>
108  * <p>
109  * All documents are surfaced through one or more "roots." Each root represents
110  * the top of a document tree that a user can navigate. For example, a root
111  * could represent an account or a physical storage device. Similar to
112  * documents, each root can have capabilities expressed through
113  * {@link Root#COLUMN_FLAGS}.
114  * </p>
115  *
116  * @see Intent#ACTION_OPEN_DOCUMENT
117  * @see Intent#ACTION_CREATE_DOCUMENT
118  */
119 public abstract class DocumentsProvider extends ContentProvider {
120     private static final String TAG = "DocumentsProvider";
121 
122     private static final int MATCH_ROOTS = 1;
123     private static final int MATCH_ROOT = 2;
124     private static final int MATCH_RECENT = 3;
125     private static final int MATCH_SEARCH = 4;
126     private static final int MATCH_DOCUMENT = 5;
127     private static final int MATCH_CHILDREN = 6;
128 
129     private String mAuthority;
130 
131     private UriMatcher mMatcher;
132 
133     /**
134      * Implementation is provided by the parent class.
135      */
136     @Override
attachInfo(Context context, ProviderInfo info)137     public void attachInfo(Context context, ProviderInfo info) {
138         mAuthority = info.authority;
139 
140         mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
141         mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
142         mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
143         mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
144         mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
145         mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
146         mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
147 
148         // Sanity check our setup
149         if (!info.exported) {
150             throw new SecurityException("Provider must be exported");
151         }
152         if (!info.grantUriPermissions) {
153             throw new SecurityException("Provider must grantUriPermissions");
154         }
155         if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission)
156                 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) {
157             throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS");
158         }
159 
160         super.attachInfo(context, info);
161     }
162 
163     /**
164      * Create a new document and return its newly generated
165      * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
166      * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
167      * not change once returned.
168      *
169      * @param parentDocumentId the parent directory to create the new document
170      *            under.
171      * @param mimeType the concrete MIME type associated with the new document.
172      *            If the MIME type is not supported, the provider must throw.
173      * @param displayName the display name of the new document. The provider may
174      *            alter this name to meet any internal constraints, such as
175      *            conflicting names.
176      */
177     @SuppressWarnings("unused")
createDocument(String parentDocumentId, String mimeType, String displayName)178     public String createDocument(String parentDocumentId, String mimeType, String displayName)
179             throws FileNotFoundException {
180         throw new UnsupportedOperationException("Create not supported");
181     }
182 
183     /**
184      * Delete the requested document. Upon returning, any URI permission grants
185      * for the requested document will be revoked. If additional documents were
186      * deleted as a side effect of this call, such as documents inside a
187      * directory, the implementor is responsible for revoking those permissions.
188      *
189      * @param documentId the document to delete.
190      */
191     @SuppressWarnings("unused")
deleteDocument(String documentId)192     public void deleteDocument(String documentId) throws FileNotFoundException {
193         throw new UnsupportedOperationException("Delete not supported");
194     }
195 
196     /**
197      * Return all roots currently provided. To display to users, you must define
198      * at least one root. You should avoid making network requests to keep this
199      * request fast.
200      * <p>
201      * Each root is defined by the metadata columns described in {@link Root},
202      * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory
203      * representing a tree of documents to display under that root.
204      * <p>
205      * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri,
206      * android.database.ContentObserver, boolean)} with
207      * {@link DocumentsContract#buildRootsUri(String)} to notify the system.
208      *
209      * @param projection list of {@link Root} columns to put into the cursor. If
210      *            {@code null} all supported columns should be included.
211      */
queryRoots(String[] projection)212     public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
213 
214     /**
215      * Return recently modified documents under the requested root. This will
216      * only be called for roots that advertise
217      * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
218      * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
219      * limited to only return the 64 most recently modified documents.
220      * <p>
221      * Recent documents do not support change notifications.
222      *
223      * @param projection list of {@link Document} columns to put into the
224      *            cursor. If {@code null} all supported columns should be
225      *            included.
226      * @see DocumentsContract#EXTRA_LOADING
227      */
228     @SuppressWarnings("unused")
queryRecentDocuments(String rootId, String[] projection)229     public Cursor queryRecentDocuments(String rootId, String[] projection)
230             throws FileNotFoundException {
231         throw new UnsupportedOperationException("Recent not supported");
232     }
233 
234     /**
235      * Return metadata for the single requested document. You should avoid
236      * making network requests to keep this request fast.
237      *
238      * @param documentId the document to return.
239      * @param projection list of {@link Document} columns to put into the
240      *            cursor. If {@code null} all supported columns should be
241      *            included.
242      */
queryDocument(String documentId, String[] projection)243     public abstract Cursor queryDocument(String documentId, String[] projection)
244             throws FileNotFoundException;
245 
246     /**
247      * Return the children documents contained in the requested directory. This
248      * must only return immediate descendants, as additional queries will be
249      * issued to recursively explore the tree.
250      * <p>
251      * If your provider is cloud-based, and you have some data cached or pinned
252      * locally, you may return the local data immediately, setting
253      * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
254      * you are still fetching additional data. Then, when the network data is
255      * available, you can send a change notification to trigger a requery and
256      * return the complete contents. To return a Cursor with extras, you need to
257      * extend and override {@link Cursor#getExtras()}.
258      * <p>
259      * To support change notifications, you must
260      * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
261      * Uri, such as
262      * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
263      * you can call {@link ContentResolver#notifyChange(Uri,
264      * android.database.ContentObserver, boolean)} with that Uri to send change
265      * notifications.
266      *
267      * @param parentDocumentId the directory to return children for.
268      * @param projection list of {@link Document} columns to put into the
269      *            cursor. If {@code null} all supported columns should be
270      *            included.
271      * @param sortOrder how to order the rows, formatted as an SQL
272      *            {@code ORDER BY} clause (excluding the ORDER BY itself).
273      *            Passing {@code null} will use the default sort order, which
274      *            may be unordered. This ordering is a hint that can be used to
275      *            prioritize how data is fetched from the network, but UI may
276      *            always enforce a specific ordering.
277      * @see DocumentsContract#EXTRA_LOADING
278      * @see DocumentsContract#EXTRA_INFO
279      * @see DocumentsContract#EXTRA_ERROR
280      */
queryChildDocuments( String parentDocumentId, String[] projection, String sortOrder)281     public abstract Cursor queryChildDocuments(
282             String parentDocumentId, String[] projection, String sortOrder)
283             throws FileNotFoundException;
284 
285     /** {@hide} */
286     @SuppressWarnings("unused")
queryChildDocumentsForManage( String parentDocumentId, String[] projection, String sortOrder)287     public Cursor queryChildDocumentsForManage(
288             String parentDocumentId, String[] projection, String sortOrder)
289             throws FileNotFoundException {
290         throw new UnsupportedOperationException("Manage not supported");
291     }
292 
293     /**
294      * Return documents that that match the given query under the requested
295      * root. The returned documents should be sorted by relevance in descending
296      * order. How documents are matched against the query string is an
297      * implementation detail left to each provider, but it's suggested that at
298      * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
299      * case-insensitive fashion.
300      * <p>
301      * Only documents may be returned; directories are not supported in search
302      * results.
303      * <p>
304      * If your provider is cloud-based, and you have some data cached or pinned
305      * locally, you may return the local data immediately, setting
306      * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
307      * you are still fetching additional data. Then, when the network data is
308      * available, you can send a change notification to trigger a requery and
309      * return the complete contents.
310      * <p>
311      * To support change notifications, you must
312      * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
313      * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
314      * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
315      * android.database.ContentObserver, boolean)} with that Uri to send change
316      * notifications.
317      *
318      * @param rootId the root to search under.
319      * @param query string to match documents against.
320      * @param projection list of {@link Document} columns to put into the
321      *            cursor. If {@code null} all supported columns should be
322      *            included.
323      * @see DocumentsContract#EXTRA_LOADING
324      * @see DocumentsContract#EXTRA_INFO
325      * @see DocumentsContract#EXTRA_ERROR
326      */
327     @SuppressWarnings("unused")
querySearchDocuments(String rootId, String query, String[] projection)328     public Cursor querySearchDocuments(String rootId, String query, String[] projection)
329             throws FileNotFoundException {
330         throw new UnsupportedOperationException("Search not supported");
331     }
332 
333     /**
334      * Return concrete MIME type of the requested document. Must match the value
335      * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
336      * implementation queries {@link #queryDocument(String, String[])}, so
337      * providers may choose to override this as an optimization.
338      */
getDocumentType(String documentId)339     public String getDocumentType(String documentId) throws FileNotFoundException {
340         final Cursor cursor = queryDocument(documentId, null);
341         try {
342             if (cursor.moveToFirst()) {
343                 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
344             } else {
345                 return null;
346             }
347         } finally {
348             IoUtils.closeQuietly(cursor);
349         }
350     }
351 
352     /**
353      * Open and return the requested document.
354      * <p>
355      * Your provider should return a reliable {@link ParcelFileDescriptor} to
356      * detect when the remote caller has finished reading or writing the
357      * document. You may return a pipe or socket pair if the mode is exclusively
358      * "r" or "w", but complex modes like "rw" imply a normal file on disk that
359      * supports seeking.
360      * <p>
361      * If you block while downloading content, you should periodically check
362      * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
363      *
364      * @param documentId the document to return.
365      * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
366      * @param signal used by the caller to signal if the request should be
367      *            cancelled. May be null.
368      * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
369      *      OnCloseListener)
370      * @see ParcelFileDescriptor#createReliablePipe()
371      * @see ParcelFileDescriptor#createReliableSocketPair()
372      * @see ParcelFileDescriptor#parseMode(String)
373      */
openDocument( String documentId, String mode, CancellationSignal signal)374     public abstract ParcelFileDescriptor openDocument(
375             String documentId, String mode, CancellationSignal signal) throws FileNotFoundException;
376 
377     /**
378      * Open and return a thumbnail of the requested document.
379      * <p>
380      * A provider should return a thumbnail closely matching the hinted size,
381      * attempting to serve from a local cache if possible. A provider should
382      * never return images more than double the hinted size.
383      * <p>
384      * If you perform expensive operations to download or generate a thumbnail,
385      * you should periodically check {@link CancellationSignal#isCanceled()} to
386      * abort abandoned thumbnail requests.
387      *
388      * @param documentId the document to return.
389      * @param sizeHint hint of the optimal thumbnail dimensions.
390      * @param signal used by the caller to signal if the request should be
391      *            cancelled. May be null.
392      * @see Document#FLAG_SUPPORTS_THUMBNAIL
393      */
394     @SuppressWarnings("unused")
openDocumentThumbnail( String documentId, Point sizeHint, CancellationSignal signal)395     public AssetFileDescriptor openDocumentThumbnail(
396             String documentId, Point sizeHint, CancellationSignal signal)
397             throws FileNotFoundException {
398         throw new UnsupportedOperationException("Thumbnails not supported");
399     }
400 
401     /**
402      * Implementation is provided by the parent class. Cannot be overriden.
403      *
404      * @see #queryRoots(String[])
405      * @see #queryRecentDocuments(String, String[])
406      * @see #queryDocument(String, String[])
407      * @see #queryChildDocuments(String, String[], String)
408      * @see #querySearchDocuments(String, String, String[])
409      */
410     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)411     public final Cursor query(Uri uri, String[] projection, String selection,
412             String[] selectionArgs, String sortOrder) {
413         try {
414             switch (mMatcher.match(uri)) {
415                 case MATCH_ROOTS:
416                     return queryRoots(projection);
417                 case MATCH_RECENT:
418                     return queryRecentDocuments(getRootId(uri), projection);
419                 case MATCH_SEARCH:
420                     return querySearchDocuments(
421                             getRootId(uri), getSearchDocumentsQuery(uri), projection);
422                 case MATCH_DOCUMENT:
423                     return queryDocument(getDocumentId(uri), projection);
424                 case MATCH_CHILDREN:
425                     if (DocumentsContract.isManageMode(uri)) {
426                         return queryChildDocumentsForManage(
427                                 getDocumentId(uri), projection, sortOrder);
428                     } else {
429                         return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
430                     }
431                 default:
432                     throw new UnsupportedOperationException("Unsupported Uri " + uri);
433             }
434         } catch (FileNotFoundException e) {
435             Log.w(TAG, "Failed during query", e);
436             return null;
437         }
438     }
439 
440     /**
441      * Implementation is provided by the parent class. Cannot be overriden.
442      *
443      * @see #getDocumentType(String)
444      */
445     @Override
getType(Uri uri)446     public final String getType(Uri uri) {
447         try {
448             switch (mMatcher.match(uri)) {
449                 case MATCH_ROOT:
450                     return DocumentsContract.Root.MIME_TYPE_ITEM;
451                 case MATCH_DOCUMENT:
452                     return getDocumentType(getDocumentId(uri));
453                 default:
454                     return null;
455             }
456         } catch (FileNotFoundException e) {
457             Log.w(TAG, "Failed during getType", e);
458             return null;
459         }
460     }
461 
462     /**
463      * Implementation is provided by the parent class. Throws by default, and
464      * cannot be overriden.
465      *
466      * @see #createDocument(String, String, String)
467      */
468     @Override
insert(Uri uri, ContentValues values)469     public final Uri insert(Uri uri, ContentValues values) {
470         throw new UnsupportedOperationException("Insert not supported");
471     }
472 
473     /**
474      * Implementation is provided by the parent class. Throws by default, and
475      * cannot be overriden.
476      *
477      * @see #deleteDocument(String)
478      */
479     @Override
delete(Uri uri, String selection, String[] selectionArgs)480     public final int delete(Uri uri, String selection, String[] selectionArgs) {
481         throw new UnsupportedOperationException("Delete not supported");
482     }
483 
484     /**
485      * Implementation is provided by the parent class. Throws by default, and
486      * cannot be overriden.
487      */
488     @Override
update( Uri uri, ContentValues values, String selection, String[] selectionArgs)489     public final int update(
490             Uri uri, ContentValues values, String selection, String[] selectionArgs) {
491         throw new UnsupportedOperationException("Update not supported");
492     }
493 
494     /**
495      * Implementation is provided by the parent class. Can be overridden to
496      * provide additional functionality, but subclasses <em>must</em> always
497      * call the superclass. If the superclass returns {@code null}, the subclass
498      * may implement custom behavior.
499      *
500      * @see #openDocument(String, String, CancellationSignal)
501      * @see #deleteDocument(String)
502      */
503     @Override
call(String method, String arg, Bundle extras)504     public Bundle call(String method, String arg, Bundle extras) {
505         final Context context = getContext();
506 
507         if (!method.startsWith("android:")) {
508             // Let non-platform methods pass through
509             return super.call(method, arg, extras);
510         }
511 
512         final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
513         final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
514 
515         // Require that caller can manage requested document
516         final boolean callerHasManage =
517                 context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS)
518                 == PackageManager.PERMISSION_GRANTED;
519         enforceWritePermissionInner(documentUri);
520 
521         final Bundle out = new Bundle();
522         try {
523             if (METHOD_CREATE_DOCUMENT.equals(method)) {
524                 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
525                 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
526 
527                 final String newDocumentId = createDocument(documentId, mimeType, displayName);
528                 out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
529 
530                 // Extend permission grant towards caller if needed
531                 if (!callerHasManage) {
532                     final Uri newDocumentUri = DocumentsContract.buildDocumentUri(
533                             mAuthority, newDocumentId);
534                     context.grantUriPermission(getCallingPackage(), newDocumentUri,
535                             Intent.FLAG_GRANT_READ_URI_PERMISSION
536                             | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
537                             | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
538                 }
539 
540             } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
541                 deleteDocument(documentId);
542 
543                 // Document no longer exists, clean up any grants
544                 context.revokeUriPermission(documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
545                         | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
546                         | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
547 
548             } else {
549                 throw new UnsupportedOperationException("Method not supported " + method);
550             }
551         } catch (FileNotFoundException e) {
552             throw new IllegalStateException("Failed call " + method, e);
553         }
554         return out;
555     }
556 
557     /**
558      * Implementation is provided by the parent class. Cannot be overriden.
559      *
560      * @see #openDocument(String, String, CancellationSignal)
561      */
562     @Override
openFile(Uri uri, String mode)563     public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
564         return openDocument(getDocumentId(uri), mode, null);
565     }
566 
567     /**
568      * Implementation is provided by the parent class. Cannot be overriden.
569      *
570      * @see #openDocument(String, String, CancellationSignal)
571      */
572     @Override
openFile(Uri uri, String mode, CancellationSignal signal)573     public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
574             throws FileNotFoundException {
575         return openDocument(getDocumentId(uri), mode, signal);
576     }
577 
578     /**
579      * Implementation is provided by the parent class. Cannot be overriden.
580      *
581      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
582      */
583     @Override
openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)584     public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
585             throws FileNotFoundException {
586         if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
587             final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
588             return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
589         } else {
590             return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
591         }
592     }
593 
594     /**
595      * Implementation is provided by the parent class. Cannot be overriden.
596      *
597      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
598      */
599     @Override
openTypedAssetFile( Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)600     public final AssetFileDescriptor openTypedAssetFile(
601             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
602             throws FileNotFoundException {
603         if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
604             final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
605             return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
606         } else {
607             return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
608         }
609     }
610 }
611