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