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.net.TrafficStats.KB_IN_BYTES;
     20 import static libcore.io.OsConstants.SEEK_SET;
     21 
     22 import android.content.ContentProviderClient;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.pm.ResolveInfo;
     27 import android.content.res.AssetFileDescriptor;
     28 import android.database.Cursor;
     29 import android.graphics.Bitmap;
     30 import android.graphics.BitmapFactory;
     31 import android.graphics.Matrix;
     32 import android.graphics.Point;
     33 import android.media.ExifInterface;
     34 import android.net.Uri;
     35 import android.os.Bundle;
     36 import android.os.CancellationSignal;
     37 import android.os.OperationCanceledException;
     38 import android.os.ParcelFileDescriptor;
     39 import android.os.ParcelFileDescriptor.OnCloseListener;
     40 import android.os.RemoteException;
     41 import android.util.Log;
     42 
     43 import libcore.io.ErrnoException;
     44 import libcore.io.IoUtils;
     45 import libcore.io.Libcore;
     46 
     47 import java.io.BufferedInputStream;
     48 import java.io.File;
     49 import java.io.FileDescriptor;
     50 import java.io.FileInputStream;
     51 import java.io.FileNotFoundException;
     52 import java.io.IOException;
     53 import java.util.List;
     54 
     55 /**
     56  * Defines the contract between a documents provider and the platform.
     57  * <p>
     58  * To create a document provider, extend {@link DocumentsProvider}, which
     59  * provides a foundational implementation of this contract.
     60  *
     61  * @see DocumentsProvider
     62  */
     63 public final class DocumentsContract {
     64     private static final String TAG = "Documents";
     65 
     66     // content://com.example/root/
     67     // content://com.example/root/sdcard/
     68     // content://com.example/root/sdcard/recent/
     69     // content://com.example/root/sdcard/search/?query=pony
     70     // content://com.example/document/12/
     71     // content://com.example/document/12/children/
     72 
     73     private DocumentsContract() {
     74     }
     75 
     76     /**
     77      * Intent action used to identify {@link DocumentsProvider} instances. This
     78      * is used in the {@code <intent-filter>} of a {@code <provider>}.
     79      */
     80     public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
     81 
     82     /** {@hide} */
     83     public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
     84 
     85     /** {@hide} */
     86     public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
     87 
     88     /**
     89      * Included in {@link AssetFileDescriptor#getExtras()} when returned
     90      * thumbnail should be rotated.
     91      *
     92      * @see MediaStore.Images.ImageColumns#ORIENTATION
     93      * @hide
     94      */
     95     public static final String EXTRA_ORIENTATION = "android.content.extra.ORIENTATION";
     96 
     97     /** {@hide} */
     98     public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT";
     99     /** {@hide} */
    100     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
    101 
    102     /**
    103      * Buffer is large enough to rewind past any EXIF headers.
    104      */
    105     private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
    106 
    107     /**
    108      * Constants related to a document, including {@link Cursor} column names
    109      * and flags.
    110      * <p>
    111      * A document can be either an openable stream (with a specific MIME type),
    112      * or a directory containing additional documents (with the
    113      * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a
    114      * subtree containing zero or more documents, which can recursively contain
    115      * even more documents and directories.
    116      * <p>
    117      * All columns are <em>read-only</em> to client applications.
    118      */
    119     public final static class Document {
    120         private Document() {
    121         }
    122 
    123         /**
    124          * Unique ID of a document. This ID is both provided by and interpreted
    125          * by a {@link DocumentsProvider}, and should be treated as an opaque
    126          * value by client applications. This column is required.
    127          * <p>
    128          * Each document must have a unique ID within a provider, but that
    129          * single document may be included as a child of multiple directories.
    130          * <p>
    131          * A provider must always return durable IDs, since they will be used to
    132          * issue long-term URI permission grants when an application interacts
    133          * with {@link Intent#ACTION_OPEN_DOCUMENT} and
    134          * {@link Intent#ACTION_CREATE_DOCUMENT}.
    135          * <p>
    136          * Type: STRING
    137          */
    138         public static final String COLUMN_DOCUMENT_ID = "document_id";
    139 
    140         /**
    141          * Concrete MIME type of a document. For example, "image/png" or
    142          * "application/pdf" for openable files. A document can also be a
    143          * directory containing additional documents, which is represented with
    144          * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
    145          * <p>
    146          * Type: STRING
    147          *
    148          * @see #MIME_TYPE_DIR
    149          */
    150         public static final String COLUMN_MIME_TYPE = "mime_type";
    151 
    152         /**
    153          * Display name of a document, used as the primary title displayed to a
    154          * user. This column is required.
    155          * <p>
    156          * Type: STRING
    157          */
    158         public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
    159 
    160         /**
    161          * Summary of a document, which may be shown to a user. This column is
    162          * optional, and may be {@code null}.
    163          * <p>
    164          * Type: STRING
    165          */
    166         public static final String COLUMN_SUMMARY = "summary";
    167 
    168         /**
    169          * Timestamp when a document was last modified, in milliseconds since
    170          * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
    171          * {@code null} if unknown. A {@link DocumentsProvider} can update this
    172          * field using events from {@link OnCloseListener} or other reliable
    173          * {@link ParcelFileDescriptor} transports.
    174          * <p>
    175          * Type: INTEGER (long)
    176          *
    177          * @see System#currentTimeMillis()
    178          */
    179         public static final String COLUMN_LAST_MODIFIED = "last_modified";
    180 
    181         /**
    182          * Specific icon resource ID for a document. This column is optional,
    183          * and may be {@code null} to use a platform-provided default icon based
    184          * on {@link #COLUMN_MIME_TYPE}.
    185          * <p>
    186          * Type: INTEGER (int)
    187          */
    188         public static final String COLUMN_ICON = "icon";
    189 
    190         /**
    191          * Flags that apply to a document. This column is required.
    192          * <p>
    193          * Type: INTEGER (int)
    194          *
    195          * @see #FLAG_SUPPORTS_WRITE
    196          * @see #FLAG_SUPPORTS_DELETE
    197          * @see #FLAG_SUPPORTS_THUMBNAIL
    198          * @see #FLAG_DIR_PREFERS_GRID
    199          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
    200          */
    201         public static final String COLUMN_FLAGS = "flags";
    202 
    203         /**
    204          * Size of a document, in bytes, or {@code null} if unknown. This column
    205          * is required.
    206          * <p>
    207          * Type: INTEGER (long)
    208          */
    209         public static final String COLUMN_SIZE = OpenableColumns.SIZE;
    210 
    211         /**
    212          * MIME type of a document which is a directory that may contain
    213          * additional documents.
    214          *
    215          * @see #COLUMN_MIME_TYPE
    216          */
    217         public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
    218 
    219         /**
    220          * Flag indicating that a document can be represented as a thumbnail.
    221          *
    222          * @see #COLUMN_FLAGS
    223          * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
    224          *      Point, CancellationSignal)
    225          * @see DocumentsProvider#openDocumentThumbnail(String, Point,
    226          *      android.os.CancellationSignal)
    227          */
    228         public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
    229 
    230         /**
    231          * Flag indicating that a document supports writing.
    232          * <p>
    233          * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
    234          * the calling application is granted both
    235          * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
    236          * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
    237          * writability of a document may change over time, for example due to
    238          * remote access changes. This flag indicates that a document client can
    239          * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
    240          *
    241          * @see #COLUMN_FLAGS
    242          */
    243         public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
    244 
    245         /**
    246          * Flag indicating that a document is deletable.
    247          *
    248          * @see #COLUMN_FLAGS
    249          * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
    250          * @see DocumentsProvider#deleteDocument(String)
    251          */
    252         public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
    253 
    254         /**
    255          * Flag indicating that a document is a directory that supports creation
    256          * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
    257          * {@link #MIME_TYPE_DIR}.
    258          *
    259          * @see #COLUMN_FLAGS
    260          * @see DocumentsContract#createDocument(ContentResolver, Uri, String,
    261          *      String)
    262          * @see DocumentsProvider#createDocument(String, String, String)
    263          */
    264         public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
    265 
    266         /**
    267          * Flag indicating that a directory prefers its contents be shown in a
    268          * larger format grid. Usually suitable when a directory contains mostly
    269          * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
    270          * {@link #MIME_TYPE_DIR}.
    271          *
    272          * @see #COLUMN_FLAGS
    273          */
    274         public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
    275 
    276         /**
    277          * Flag indicating that a directory prefers its contents be sorted by
    278          * {@link #COLUMN_LAST_MODIFIED}. Only valid when
    279          * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
    280          *
    281          * @see #COLUMN_FLAGS
    282          */
    283         public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
    284 
    285         /**
    286          * Flag indicating that document titles should be hidden when viewing
    287          * this directory in a larger format grid. For example, a directory
    288          * containing only images may want the image thumbnails to speak for
    289          * themselves. Only valid when {@link #COLUMN_MIME_TYPE} is
    290          * {@link #MIME_TYPE_DIR}.
    291          *
    292          * @see #COLUMN_FLAGS
    293          * @see #FLAG_DIR_PREFERS_GRID
    294          * @hide
    295          */
    296         public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 16;
    297     }
    298 
    299     /**
    300      * Constants related to a root of documents, including {@link Cursor} column
    301      * names and flags. A root is the start of a tree of documents, such as a
    302      * physical storage device, or an account. Each root starts at the directory
    303      * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively
    304      * contain both documents and directories.
    305      * <p>
    306      * All columns are <em>read-only</em> to client applications.
    307      */
    308     public final static class Root {
    309         private Root() {
    310         }
    311 
    312         /**
    313          * Unique ID of a root. This ID is both provided by and interpreted by a
    314          * {@link DocumentsProvider}, and should be treated as an opaque value
    315          * by client applications. This column is required.
    316          * <p>
    317          * Type: STRING
    318          */
    319         public static final String COLUMN_ROOT_ID = "root_id";
    320 
    321         /**
    322          * Flags that apply to a root. This column is required.
    323          * <p>
    324          * Type: INTEGER (int)
    325          *
    326          * @see #FLAG_LOCAL_ONLY
    327          * @see #FLAG_SUPPORTS_CREATE
    328          * @see #FLAG_SUPPORTS_RECENTS
    329          * @see #FLAG_SUPPORTS_SEARCH
    330          */
    331         public static final String COLUMN_FLAGS = "flags";
    332 
    333         /**
    334          * Icon resource ID for a root. This column is required.
    335          * <p>
    336          * Type: INTEGER (int)
    337          */
    338         public static final String COLUMN_ICON = "icon";
    339 
    340         /**
    341          * Title for a root, which will be shown to a user. This column is
    342          * required. For a single storage service surfacing multiple accounts as
    343          * different roots, this title should be the name of the service.
    344          * <p>
    345          * Type: STRING
    346          */
    347         public static final String COLUMN_TITLE = "title";
    348 
    349         /**
    350          * Summary for this root, which may be shown to a user. This column is
    351          * optional, and may be {@code null}. For a single storage service
    352          * surfacing multiple accounts as different roots, this summary should
    353          * be the name of the account.
    354          * <p>
    355          * Type: STRING
    356          */
    357         public static final String COLUMN_SUMMARY = "summary";
    358 
    359         /**
    360          * Document which is a directory that represents the top directory of
    361          * this root. This column is required.
    362          * <p>
    363          * Type: STRING
    364          *
    365          * @see Document#COLUMN_DOCUMENT_ID
    366          */
    367         public static final String COLUMN_DOCUMENT_ID = "document_id";
    368 
    369         /**
    370          * Number of bytes available in this root. This column is optional, and
    371          * may be {@code null} if unknown or unbounded.
    372          * <p>
    373          * Type: INTEGER (long)
    374          */
    375         public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
    376 
    377         /**
    378          * MIME types supported by this root. This column is optional, and if
    379          * {@code null} the root is assumed to support all MIME types. Multiple
    380          * MIME types can be separated by a newline. For example, a root
    381          * supporting audio might return "audio/*\napplication/x-flac".
    382          * <p>
    383          * Type: STRING
    384          */
    385         public static final String COLUMN_MIME_TYPES = "mime_types";
    386 
    387         /** {@hide} */
    388         public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
    389 
    390         /**
    391          * Flag indicating that at least one directory under this root supports
    392          * creating content. Roots with this flag will be shown when an
    393          * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
    394          *
    395          * @see #COLUMN_FLAGS
    396          */
    397         public static final int FLAG_SUPPORTS_CREATE = 1;
    398 
    399         /**
    400          * Flag indicating that this root offers content that is strictly local
    401          * on the device. That is, no network requests are made for the content.
    402          *
    403          * @see #COLUMN_FLAGS
    404          * @see Intent#EXTRA_LOCAL_ONLY
    405          */
    406         public static final int FLAG_LOCAL_ONLY = 1 << 1;
    407 
    408         /**
    409          * Flag indicating that this root can be queried to provide recently
    410          * modified documents.
    411          *
    412          * @see #COLUMN_FLAGS
    413          * @see DocumentsContract#buildRecentDocumentsUri(String, String)
    414          * @see DocumentsProvider#queryRecentDocuments(String, String[])
    415          */
    416         public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
    417 
    418         /**
    419          * Flag indicating that this root supports search.
    420          *
    421          * @see #COLUMN_FLAGS
    422          * @see DocumentsContract#buildSearchDocumentsUri(String, String,
    423          *      String)
    424          * @see DocumentsProvider#querySearchDocuments(String, String,
    425          *      String[])
    426          */
    427         public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
    428 
    429         /**
    430          * Flag indicating that this root is currently empty. This may be used
    431          * to hide the root when opening documents, but the root will still be
    432          * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
    433          * also set. If the value of this flag changes, such as when a root
    434          * becomes non-empty, you must send a content changed notification for
    435          * {@link DocumentsContract#buildRootsUri(String)}.
    436          *
    437          * @see #COLUMN_FLAGS
    438          * @see ContentResolver#notifyChange(Uri,
    439          *      android.database.ContentObserver, boolean)
    440          * @hide
    441          */
    442         public static final int FLAG_EMPTY = 1 << 16;
    443 
    444         /**
    445          * Flag indicating that this root should only be visible to advanced
    446          * users.
    447          *
    448          * @see #COLUMN_FLAGS
    449          * @hide
    450          */
    451         public static final int FLAG_ADVANCED = 1 << 17;
    452     }
    453 
    454     /**
    455      * Optional boolean flag included in a directory {@link Cursor#getExtras()}
    456      * indicating that a document provider is still loading data. For example, a
    457      * provider has returned some results, but is still waiting on an
    458      * outstanding network request. The provider must send a content changed
    459      * notification when loading is finished.
    460      *
    461      * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
    462      *      boolean)
    463      */
    464     public static final String EXTRA_LOADING = "loading";
    465 
    466     /**
    467      * Optional string included in a directory {@link Cursor#getExtras()}
    468      * providing an informational message that should be shown to a user. For
    469      * example, a provider may wish to indicate that not all documents are
    470      * available.
    471      */
    472     public static final String EXTRA_INFO = "info";
    473 
    474     /**
    475      * Optional string included in a directory {@link Cursor#getExtras()}
    476      * providing an error message that should be shown to a user. For example, a
    477      * provider may wish to indicate that a network error occurred. The user may
    478      * choose to retry, resulting in a new query.
    479      */
    480     public static final String EXTRA_ERROR = "error";
    481 
    482     /** {@hide} */
    483     public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
    484     /** {@hide} */
    485     public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
    486 
    487     /** {@hide} */
    488     public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
    489 
    490     private static final String PATH_ROOT = "root";
    491     private static final String PATH_RECENT = "recent";
    492     private static final String PATH_DOCUMENT = "document";
    493     private static final String PATH_CHILDREN = "children";
    494     private static final String PATH_SEARCH = "search";
    495 
    496     private static final String PARAM_QUERY = "query";
    497     private static final String PARAM_MANAGE = "manage";
    498 
    499     /**
    500      * Build URI representing the roots of a document provider. When queried, a
    501      * provider will return one or more rows with columns defined by
    502      * {@link Root}.
    503      *
    504      * @see DocumentsProvider#queryRoots(String[])
    505      */
    506     public static Uri buildRootsUri(String authority) {
    507         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    508                 .authority(authority).appendPath(PATH_ROOT).build();
    509     }
    510 
    511     /**
    512      * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a
    513      * document provider.
    514      *
    515      * @see #getRootId(Uri)
    516      */
    517     public static Uri buildRootUri(String authority, String rootId) {
    518         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    519                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
    520     }
    521 
    522     /**
    523      * Build URI representing the recently modified documents of a specific root
    524      * in a document provider. When queried, a provider will return zero or more
    525      * rows with columns defined by {@link Document}.
    526      *
    527      * @see DocumentsProvider#queryRecentDocuments(String, String[])
    528      * @see #getRootId(Uri)
    529      */
    530     public static Uri buildRecentDocumentsUri(String authority, String rootId) {
    531         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    532                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
    533                 .appendPath(PATH_RECENT).build();
    534     }
    535 
    536     /**
    537      * Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
    538      * document provider. When queried, a provider will return a single row with
    539      * columns defined by {@link Document}.
    540      *
    541      * @see DocumentsProvider#queryDocument(String, String[])
    542      * @see #getDocumentId(Uri)
    543      */
    544     public static Uri buildDocumentUri(String authority, String documentId) {
    545         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    546                 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
    547     }
    548 
    549     /**
    550      * Build URI representing the children of the given directory in a document
    551      * provider. When queried, a provider will return zero or more rows with
    552      * columns defined by {@link Document}.
    553      *
    554      * @param parentDocumentId the document to return children for, which must
    555      *            be a directory with MIME type of
    556      *            {@link Document#MIME_TYPE_DIR}.
    557      * @see DocumentsProvider#queryChildDocuments(String, String[], String)
    558      * @see #getDocumentId(Uri)
    559      */
    560     public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
    561         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
    562                 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
    563                 .build();
    564     }
    565 
    566     /**
    567      * Build URI representing a search for matching documents under a specific
    568      * root in a document provider. When queried, a provider will return zero or
    569      * more rows with columns defined by {@link Document}.
    570      *
    571      * @see DocumentsProvider#querySearchDocuments(String, String, String[])
    572      * @see #getRootId(Uri)
    573      * @see #getSearchDocumentsQuery(Uri)
    574      */
    575     public static Uri buildSearchDocumentsUri(
    576             String authority, String rootId, String query) {
    577         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
    578                 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
    579                 .appendQueryParameter(PARAM_QUERY, query).build();
    580     }
    581 
    582     /**
    583      * Test if the given URI represents a {@link Document} backed by a
    584      * {@link DocumentsProvider}.
    585      */
    586     public static boolean isDocumentUri(Context context, Uri uri) {
    587         final List<String> paths = uri.getPathSegments();
    588         if (paths.size() < 2) {
    589             return false;
    590         }
    591         if (!PATH_DOCUMENT.equals(paths.get(0))) {
    592             return false;
    593         }
    594 
    595         final Intent intent = new Intent(PROVIDER_INTERFACE);
    596         final List<ResolveInfo> infos = context.getPackageManager()
    597                 .queryIntentContentProviders(intent, 0);
    598         for (ResolveInfo info : infos) {
    599             if (uri.getAuthority().equals(info.providerInfo.authority)) {
    600                 return true;
    601             }
    602         }
    603         return false;
    604     }
    605 
    606     /**
    607      * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI.
    608      */
    609     public static String getRootId(Uri rootUri) {
    610         final List<String> paths = rootUri.getPathSegments();
    611         if (paths.size() < 2) {
    612             throw new IllegalArgumentException("Not a root: " + rootUri);
    613         }
    614         if (!PATH_ROOT.equals(paths.get(0))) {
    615             throw new IllegalArgumentException("Not a root: " + rootUri);
    616         }
    617         return paths.get(1);
    618     }
    619 
    620     /**
    621      * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
    622      */
    623     public static String getDocumentId(Uri documentUri) {
    624         final List<String> paths = documentUri.getPathSegments();
    625         if (paths.size() < 2) {
    626             throw new IllegalArgumentException("Not a document: " + documentUri);
    627         }
    628         if (!PATH_DOCUMENT.equals(paths.get(0))) {
    629             throw new IllegalArgumentException("Not a document: " + documentUri);
    630         }
    631         return paths.get(1);
    632     }
    633 
    634     /**
    635      * Extract the search query from a URI built by
    636      * {@link #buildSearchDocumentsUri(String, String, String)}.
    637      */
    638     public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
    639         return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
    640     }
    641 
    642     /** {@hide} */
    643     public static Uri setManageMode(Uri uri) {
    644         return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
    645     }
    646 
    647     /** {@hide} */
    648     public static boolean isManageMode(Uri uri) {
    649         return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
    650     }
    651 
    652     /**
    653      * Return thumbnail representing the document at the given URI. Callers are
    654      * responsible for their own in-memory caching.
    655      *
    656      * @param documentUri document to return thumbnail for, which must have
    657      *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
    658      * @param size optimal thumbnail size desired. A provider may return a
    659      *            thumbnail of a different size, but never more than double the
    660      *            requested size.
    661      * @param signal signal used to indicate if caller is no longer interested
    662      *            in the thumbnail.
    663      * @return decoded thumbnail, or {@code null} if problem was encountered.
    664      * @see DocumentsProvider#openDocumentThumbnail(String, Point,
    665      *      android.os.CancellationSignal)
    666      */
    667     public static Bitmap getDocumentThumbnail(
    668             ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
    669         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
    670                 documentUri.getAuthority());
    671         try {
    672             return getDocumentThumbnail(client, documentUri, size, signal);
    673         } catch (Exception e) {
    674             if (!(e instanceof OperationCanceledException)) {
    675                 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
    676             }
    677             return null;
    678         } finally {
    679             ContentProviderClient.releaseQuietly(client);
    680         }
    681     }
    682 
    683     /** {@hide} */
    684     public static Bitmap getDocumentThumbnail(
    685             ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
    686             throws RemoteException, IOException {
    687         final Bundle openOpts = new Bundle();
    688         openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
    689 
    690         AssetFileDescriptor afd = null;
    691         Bitmap bitmap = null;
    692         try {
    693             afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
    694 
    695             final FileDescriptor fd = afd.getFileDescriptor();
    696             final long offset = afd.getStartOffset();
    697 
    698             // Try seeking on the returned FD, since it gives us the most
    699             // optimal decode path; otherwise fall back to buffering.
    700             BufferedInputStream is = null;
    701             try {
    702                 Libcore.os.lseek(fd, offset, SEEK_SET);
    703             } catch (ErrnoException e) {
    704                 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
    705                 is.mark(THUMBNAIL_BUFFER_SIZE);
    706             }
    707 
    708             // We requested a rough thumbnail size, but the remote size may have
    709             // returned something giant, so defensively scale down as needed.
    710             final BitmapFactory.Options opts = new BitmapFactory.Options();
    711             opts.inJustDecodeBounds = true;
    712             if (is != null) {
    713                 BitmapFactory.decodeStream(is, null, opts);
    714             } else {
    715                 BitmapFactory.decodeFileDescriptor(fd, null, opts);
    716             }
    717 
    718             final int widthSample = opts.outWidth / size.x;
    719             final int heightSample = opts.outHeight / size.y;
    720 
    721             opts.inJustDecodeBounds = false;
    722             opts.inSampleSize = Math.min(widthSample, heightSample);
    723             if (is != null) {
    724                 is.reset();
    725                 bitmap = BitmapFactory.decodeStream(is, null, opts);
    726             } else {
    727                 try {
    728                     Libcore.os.lseek(fd, offset, SEEK_SET);
    729                 } catch (ErrnoException e) {
    730                     e.rethrowAsIOException();
    731                 }
    732                 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
    733             }
    734 
    735             // Transform the bitmap if requested. We use a side-channel to
    736             // communicate the orientation, since EXIF thumbnails don't contain
    737             // the rotation flags of the original image.
    738             final Bundle extras = afd.getExtras();
    739             final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
    740             if (orientation != 0) {
    741                 final int width = bitmap.getWidth();
    742                 final int height = bitmap.getHeight();
    743 
    744                 final Matrix m = new Matrix();
    745                 m.setRotate(orientation, width / 2, height / 2);
    746                 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
    747             }
    748         } finally {
    749             IoUtils.closeQuietly(afd);
    750         }
    751 
    752         return bitmap;
    753     }
    754 
    755     /**
    756      * Create a new document with given MIME type and display name.
    757      *
    758      * @param parentDocumentUri directory with
    759      *            {@link Document#FLAG_DIR_SUPPORTS_CREATE}
    760      * @param mimeType MIME type of new document
    761      * @param displayName name of new document
    762      * @return newly created document, or {@code null} if failed
    763      * @hide
    764      */
    765     public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
    766             String mimeType, String displayName) {
    767         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
    768                 parentDocumentUri.getAuthority());
    769         try {
    770             return createDocument(client, parentDocumentUri, mimeType, displayName);
    771         } catch (Exception e) {
    772             Log.w(TAG, "Failed to create document", e);
    773             return null;
    774         } finally {
    775             ContentProviderClient.releaseQuietly(client);
    776         }
    777     }
    778 
    779     /** {@hide} */
    780     public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
    781             String mimeType, String displayName) throws RemoteException {
    782         final Bundle in = new Bundle();
    783         in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
    784         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
    785         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
    786 
    787         final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
    788         return buildDocumentUri(
    789                 parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
    790     }
    791 
    792     /**
    793      * Delete the given document.
    794      *
    795      * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
    796      * @return if the document was deleted successfully.
    797      */
    798     public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
    799         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
    800                 documentUri.getAuthority());
    801         try {
    802             deleteDocument(client, documentUri);
    803             return true;
    804         } catch (Exception e) {
    805             Log.w(TAG, "Failed to delete document", e);
    806             return false;
    807         } finally {
    808             ContentProviderClient.releaseQuietly(client);
    809         }
    810     }
    811 
    812     /** {@hide} */
    813     public static void deleteDocument(ContentProviderClient client, Uri documentUri)
    814             throws RemoteException {
    815         final Bundle in = new Bundle();
    816         in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
    817 
    818         client.call(METHOD_DELETE_DOCUMENT, null, in);
    819     }
    820 
    821     /**
    822      * Open the given image for thumbnail purposes, using any embedded EXIF
    823      * thumbnail if available, and providing orientation hints from the parent
    824      * image.
    825      *
    826      * @hide
    827      */
    828     public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
    829         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
    830                 file, ParcelFileDescriptor.MODE_READ_ONLY);
    831         Bundle extras = null;
    832 
    833         try {
    834             final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
    835 
    836             switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
    837                 case ExifInterface.ORIENTATION_ROTATE_90:
    838                     extras = new Bundle(1);
    839                     extras.putInt(EXTRA_ORIENTATION, 90);
    840                     break;
    841                 case ExifInterface.ORIENTATION_ROTATE_180:
    842                     extras = new Bundle(1);
    843                     extras.putInt(EXTRA_ORIENTATION, 180);
    844                     break;
    845                 case ExifInterface.ORIENTATION_ROTATE_270:
    846                     extras = new Bundle(1);
    847                     extras.putInt(EXTRA_ORIENTATION, 270);
    848                     break;
    849             }
    850 
    851             final long[] thumb = exif.getThumbnailRange();
    852             if (thumb != null) {
    853                 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
    854             }
    855         } catch (IOException e) {
    856         }
    857 
    858         return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
    859     }
    860 }
    861