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