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 android.system.OsConstants.SEEK_SET;
     21 
     22 import android.annotation.Nullable;
     23 import android.content.ContentProviderClient;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.pm.ResolveInfo;
     28 import android.content.res.AssetFileDescriptor;
     29 import android.database.Cursor;
     30 import android.graphics.Bitmap;
     31 import android.graphics.BitmapFactory;
     32 import android.graphics.Matrix;
     33 import android.graphics.Point;
     34 import android.media.ExifInterface;
     35 import android.net.Uri;
     36 import android.os.Bundle;
     37 import android.os.CancellationSignal;
     38 import android.os.OperationCanceledException;
     39 import android.os.ParcelFileDescriptor;
     40 import android.os.ParcelFileDescriptor.OnCloseListener;
     41 import android.os.RemoteException;
     42 import android.os.storage.StorageVolume;
     43 import android.system.ErrnoException;
     44 import android.system.Os;
     45 import android.util.Log;
     46 
     47 import libcore.io.IoUtils;
     48 
     49 import java.io.BufferedInputStream;
     50 import java.io.File;
     51 import java.io.FileDescriptor;
     52 import java.io.FileInputStream;
     53 import java.io.FileNotFoundException;
     54 import java.io.IOException;
     55 import java.util.List;
     56 
     57 /**
     58  * Defines the contract between a documents provider and the platform.
     59  * <p>
     60  * To create a document provider, extend {@link DocumentsProvider}, which
     61  * provides a foundational implementation of this contract.
     62  * <p>
     63  * All client apps must hold a valid URI permission grant to access documents,
     64  * typically issued when a user makes a selection through
     65  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
     66  * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
     67  * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
     68  *
     69  * @see DocumentsProvider
     70  */
     71 public final class DocumentsContract {
     72     private static final String TAG = "DocumentsContract";
     73 
     74     // content://com.example/root/
     75     // content://com.example/root/sdcard/
     76     // content://com.example/root/sdcard/recent/
     77     // content://com.example/root/sdcard/search/?query=pony
     78     // content://com.example/document/12/
     79     // content://com.example/document/12/children/
     80     // content://com.example/tree/12/document/24/
     81     // content://com.example/tree/12/document/24/children/
     82 
     83     private DocumentsContract() {
     84     }
     85 
     86     /**
     87      * Intent action used to identify {@link DocumentsProvider} instances. This
     88      * is used in the {@code <intent-filter>} of a {@code <provider>}.
     89      */
     90     public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
     91 
     92     /** {@hide} */
     93     public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
     94 
     95     /** {@hide} */
     96     public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
     97 
     98     /** {@hide} */
     99     public static final String EXTRA_SHOW_FILESIZE = "android.content.extra.SHOW_FILESIZE";
    100 
    101     /** {@hide} */
    102     public static final String EXTRA_FANCY_FEATURES = "android.content.extra.FANCY";
    103 
    104     /** {@hide} */
    105     public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI";
    106 
    107     /**
    108      * Set this in a DocumentsUI intent to cause a package's own roots to be
    109      * excluded from the roots list.
    110      */
    111     public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
    112 
    113     /**
    114      * Included in {@link AssetFileDescriptor#getExtras()} when returned
    115      * thumbnail should be rotated.
    116      *
    117      * @see MediaStore.Images.ImageColumns#ORIENTATION
    118      */
    119     public static final String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION";
    120 
    121     /**
    122      * Overrides the default prompt text in DocumentsUI when set in an intent.
    123      */
    124     public static final String EXTRA_PROMPT = "android.provider.extra.PROMPT";
    125 
    126     /** {@hide} */
    127     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
    128 
    129     /** {@hide} */
    130     public static final String ACTION_BROWSE = "android.provider.action.BROWSE";
    131 
    132     /** {@hide} */
    133     public static final String
    134             ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
    135 
    136     /**
    137      * Buffer is large enough to rewind past any EXIF headers.
    138      */
    139     private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
    140 
    141     /** {@hide} */
    142     public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui";
    143 
    144     /**
    145      * Constants related to a document, including {@link Cursor} column names
    146      * and flags.
    147      * <p>
    148      * A document can be either an openable stream (with a specific MIME type),
    149      * or a directory containing additional documents (with the
    150      * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a
    151      * subtree containing zero or more documents, which can recursively contain
    152      * even more documents and directories.
    153      * <p>
    154      * All columns are <em>read-only</em> to client applications.
    155      */
    156     public final static class Document {
    157         private Document() {
    158         }
    159 
    160         /**
    161          * Unique ID of a document. This ID is both provided by and interpreted
    162          * by a {@link DocumentsProvider}, and should be treated as an opaque
    163          * value by client applications. This column is required.
    164          * <p>
    165          * Each document must have a unique ID within a provider, but that
    166          * single document may be included as a child of multiple directories.
    167          * <p>
    168          * A provider must always return durable IDs, since they will be used to
    169          * issue long-term URI permission grants when an application interacts
    170          * with {@link Intent#ACTION_OPEN_DOCUMENT} and
    171          * {@link Intent#ACTION_CREATE_DOCUMENT}.
    172          * <p>
    173          * Type: STRING
    174          */
    175         public static final String COLUMN_DOCUMENT_ID = "document_id";
    176 
    177         /**
    178          * Concrete MIME type of a document. For example, "image/png" or
    179          * "application/pdf" for openable files. A document can also be a
    180          * directory containing additional documents, which is represented with
    181          * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
    182          * <p>
    183          * Type: STRING
    184          *
    185          * @see #MIME_TYPE_DIR
    186          */
    187         public static final String COLUMN_MIME_TYPE = "mime_type";
    188 
    189         /**
    190          * Display name of a document, used as the primary title displayed to a
    191          * user. This column is required.
    192          * <p>
    193          * Type: STRING
    194          */
    195         public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
    196 
    197         /**
    198          * Summary of a document, which may be shown to a user. This column is
    199          * optional, and may be {@code null}.
    200          * <p>
    201          * Type: STRING
    202          */
    203         public static final String COLUMN_SUMMARY = "summary";
    204 
    205         /**
    206          * Timestamp when a document was last modified, in milliseconds since
    207          * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
    208          * {@code null} if unknown. A {@link DocumentsProvider} can update this
    209          * field using events from {@link OnCloseListener} or other reliable
    210          * {@link ParcelFileDescriptor} transports.
    211          * <p>
    212          * Type: INTEGER (long)
    213          *
    214          * @see System#currentTimeMillis()
    215          */
    216         public static final String COLUMN_LAST_MODIFIED = "last_modified";
    217 
    218         /**
    219          * Specific icon resource ID for a document. This column is optional,
    220          * and may be {@code null} to use a platform-provided default icon based
    221          * on {@link #COLUMN_MIME_TYPE}.
    222          * <p>
    223          * Type: INTEGER (int)
    224          */
    225         public static final String COLUMN_ICON = "icon";
    226 
    227         /**
    228          * Flags that apply to a document. This column is required.
    229          * <p>
    230          * Type: INTEGER (int)
    231          *
    232          * @see #FLAG_SUPPORTS_WRITE
    233          * @see #FLAG_SUPPORTS_DELETE
    234          * @see #FLAG_SUPPORTS_THUMBNAIL
    235          * @see #FLAG_DIR_PREFERS_GRID
    236          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
    237          * @see #FLAG_VIRTUAL_DOCUMENT
    238          * @see #FLAG_SUPPORTS_COPY
    239          * @see #FLAG_SUPPORTS_MOVE
    240          * @see #FLAG_SUPPORTS_REMOVE
    241          */
    242         public static final String COLUMN_FLAGS = "flags";
    243 
    244         /**
    245          * Size of a document, in bytes, or {@code null} if unknown. This column
    246          * is required.
    247          * <p>
    248          * Type: INTEGER (long)
    249          */
    250         public static final String COLUMN_SIZE = OpenableColumns.SIZE;
    251 
    252         /**
    253          * MIME type of a document which is a directory that may contain
    254          * additional documents.
    255          *
    256          * @see #COLUMN_MIME_TYPE
    257          */
    258         public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
    259 
    260         /**
    261          * Flag indicating that a document can be represented as a thumbnail.
    262          *
    263          * @see #COLUMN_FLAGS
    264          * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
    265          *      Point, CancellationSignal)
    266          * @see DocumentsProvider#openDocumentThumbnail(String, Point,
    267          *      android.os.CancellationSignal)
    268          */
    269         public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
    270 
    271         /**
    272          * Flag indicating that a document supports writing.
    273          * <p>
    274          * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
    275          * the calling application is granted both
    276          * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
    277          * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
    278          * writability of a document may change over time, for example due to
    279          * remote access changes. This flag indicates that a document client can
    280          * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
    281          *
    282          * @see #COLUMN_FLAGS
    283          */
    284         public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
    285 
    286         /**
    287          * Flag indicating that a document is deletable.
    288          *
    289          * @see #COLUMN_FLAGS
    290          * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
    291          * @see DocumentsProvider#deleteDocument(String)
    292          */
    293         public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
    294 
    295         /**
    296          * Flag indicating that a document is a directory that supports creation
    297          * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
    298          * {@link #MIME_TYPE_DIR}.
    299          *
    300          * @see #COLUMN_FLAGS
    301          * @see DocumentsProvider#createDocument(String, String, String)
    302          */
    303         public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
    304 
    305         /**
    306          * Flag indicating that a directory prefers its contents be shown in a
    307          * larger format grid. Usually suitable when a directory contains mostly
    308          * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
    309          * {@link #MIME_TYPE_DIR}.
    310          *
    311          * @see #COLUMN_FLAGS
    312          */
    313         public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
    314 
    315         /**
    316          * Flag indicating that a directory prefers its contents be sorted by
    317          * {@link #COLUMN_LAST_MODIFIED}. Only valid when
    318          * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
    319          *
    320          * @see #COLUMN_FLAGS
    321          */
    322         public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
    323 
    324         /**
    325          * Flag indicating that a document can be renamed.
    326          *
    327          * @see #COLUMN_FLAGS
    328          * @see DocumentsContract#renameDocument(ContentResolver, Uri,
    329          *      String)
    330          * @see DocumentsProvider#renameDocument(String, String)
    331          */
    332         public static final int FLAG_SUPPORTS_RENAME = 1 << 6;
    333 
    334         /**
    335          * Flag indicating that a document can be copied to another location
    336          * within the same document provider.
    337          *
    338          * @see #COLUMN_FLAGS
    339          * @see DocumentsContract#copyDocument(ContentResolver, Uri, Uri)
    340          * @see DocumentsProvider#copyDocument(String, String)
    341          */
    342         public static final int FLAG_SUPPORTS_COPY = 1 << 7;
    343 
    344         /**
    345          * Flag indicating that a document can be moved to another location
    346          * within the same document provider.
    347          *
    348          * @see #COLUMN_FLAGS
    349          * @see DocumentsContract#moveDocument(ContentResolver, Uri, Uri, Uri)
    350          * @see DocumentsProvider#moveDocument(String, String, String)
    351          */
    352         public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
    353 
    354         /**
    355          * Flag indicating that a document is virtual, and doesn't have byte
    356          * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
    357          *
    358          * @see #COLUMN_FLAGS
    359          * @see #COLUMN_MIME_TYPE
    360          * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
    361          *      android.os.CancellationSignal)
    362          * @see DocumentsProvider#getDocumentStreamTypes(String, String)
    363          */
    364         public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
    365 
    366         /**
    367          * Flag indicating that a document can be removed from a parent.
    368          *
    369          * @see #COLUMN_FLAGS
    370          * @see DocumentsContract#removeDocument(ContentResolver, Uri, Uri)
    371          * @see DocumentsProvider#removeDocument(String, String)
    372          */
    373         public static final int FLAG_SUPPORTS_REMOVE = 1 << 10;
    374 
    375         /**
    376          * Flag indicating that a document is an archive, and it's contents can be
    377          * obtained via {@link DocumentsProvider#queryChildDocuments}.
    378          * <p>
    379          * The <em>provider</em> support library offers utility classes to add common
    380          * archive support.
    381          *
    382          * @see #COLUMN_FLAGS
    383          * @see DocumentsProvider#queryChildDocuments(String, String[], String)
    384          * @hide
    385          */
    386         public static final int FLAG_ARCHIVE = 1 << 15;
    387 
    388         /**
    389          * Flag indicating that a document is not complete, likely its
    390          * contents are being downloaded. Partial files cannot be opened,
    391          * copied, moved in the UI. But they can be deleted and retried
    392          * if they represent a failed download.
    393          *
    394          * @see #COLUMN_FLAGS
    395          * @hide
    396          */
    397         public static final int FLAG_PARTIAL = 1 << 16;
    398     }
    399 
    400     /**
    401      * Constants related to a root of documents, including {@link Cursor} column
    402      * names and flags. A root is the start of a tree of documents, such as a
    403      * physical storage device, or an account. Each root starts at the directory
    404      * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively
    405      * contain both documents and directories.
    406      * <p>
    407      * All columns are <em>read-only</em> to client applications.
    408      */
    409     public final static class Root {
    410         private Root() {
    411         }
    412 
    413         /**
    414          * Unique ID of a root. This ID is both provided by and interpreted by a
    415          * {@link DocumentsProvider}, and should be treated as an opaque value
    416          * by client applications. This column is required.
    417          * <p>
    418          * Type: STRING
    419          */
    420         public static final String COLUMN_ROOT_ID = "root_id";
    421 
    422         /**
    423          * Flags that apply to a root. This column is required.
    424          * <p>
    425          * Type: INTEGER (int)
    426          *
    427          * @see #FLAG_LOCAL_ONLY
    428          * @see #FLAG_SUPPORTS_CREATE
    429          * @see #FLAG_SUPPORTS_RECENTS
    430          * @see #FLAG_SUPPORTS_SEARCH
    431          */
    432         public static final String COLUMN_FLAGS = "flags";
    433 
    434         /**
    435          * Icon resource ID for a root. This column is required.
    436          * <p>
    437          * Type: INTEGER (int)
    438          */
    439         public static final String COLUMN_ICON = "icon";
    440 
    441         /**
    442          * Title for a root, which will be shown to a user. This column is
    443          * required. For a single storage service surfacing multiple accounts as
    444          * different roots, this title should be the name of the service.
    445          * <p>
    446          * Type: STRING
    447          */
    448         public static final String COLUMN_TITLE = "title";
    449 
    450         /**
    451          * Summary for this root, which may be shown to a user. This column is
    452          * optional, and may be {@code null}. For a single storage service
    453          * surfacing multiple accounts as different roots, this summary should
    454          * be the name of the account.
    455          * <p>
    456          * Type: STRING
    457          */
    458         public static final String COLUMN_SUMMARY = "summary";
    459 
    460         /**
    461          * Document which is a directory that represents the top directory of
    462          * this root. This column is required.
    463          * <p>
    464          * Type: STRING
    465          *
    466          * @see Document#COLUMN_DOCUMENT_ID
    467          */
    468         public static final String COLUMN_DOCUMENT_ID = "document_id";
    469 
    470         /**
    471          * Number of bytes available in this root. This column is optional, and
    472          * may be {@code null} if unknown or unbounded.
    473          * <p>
    474          * Type: INTEGER (long)
    475          */
    476         public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
    477 
    478         /**
    479          * Capacity of a root in bytes. This column is optional, and may be
    480          * {@code null} if unknown or unbounded.
    481          * <p>
    482          * Type: INTEGER (long)
    483          */
    484         public static final String COLUMN_CAPACITY_BYTES = "capacity_bytes";
    485 
    486         /**
    487          * MIME types supported by this root. This column is optional, and if
    488          * {@code null} the root is assumed to support all MIME types. Multiple
    489          * MIME types can be separated by a newline. For example, a root
    490          * supporting audio might return "audio/*\napplication/x-flac".
    491          * <p>
    492          * Type: STRING
    493          */
    494         public static final String COLUMN_MIME_TYPES = "mime_types";
    495 
    496         /** {@hide} */
    497         public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
    498 
    499         /**
    500          * Flag indicating that at least one directory under this root supports
    501          * creating content. Roots with this flag will be shown when an
    502          * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
    503          *
    504          * @see #COLUMN_FLAGS
    505          */
    506         public static final int FLAG_SUPPORTS_CREATE = 1;
    507 
    508         /**
    509          * Flag indicating that this root offers content that is strictly local
    510          * on the device. That is, no network requests are made for the content.
    511          *
    512          * @see #COLUMN_FLAGS
    513          * @see Intent#EXTRA_LOCAL_ONLY
    514          */
    515         public static final int FLAG_LOCAL_ONLY = 1 << 1;
    516 
    517         /**
    518          * Flag indicating that this root can be queried to provide recently
    519          * modified documents.
    520          *
    521          * @see #COLUMN_FLAGS
    522          * @see DocumentsContract#buildRecentDocumentsUri(String, String)
    523          * @see DocumentsProvider#queryRecentDocuments(String, String[])
    524          */
    525         public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
    526 
    527         /**
    528          * Flag indicating that this root supports search.
    529          *
    530          * @see #COLUMN_FLAGS
    531          * @see DocumentsContract#buildSearchDocumentsUri(String, String,
    532          *      String)
    533          * @see DocumentsProvider#querySearchDocuments(String, String,
    534          *      String[])
    535          */
    536         public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
    537 
    538         /**
    539          * Flag indicating that this root supports testing parent child
    540          * relationships.
    541          *
    542          * @see #COLUMN_FLAGS
    543          * @see DocumentsProvider#isChildDocument(String, String)
    544          */
    545         public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
    546 
    547         /**
    548          * Flag indicating that this root is currently empty. This may be used
    549          * to hide the root when opening documents, but the root will still be
    550          * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
    551          * also set. If the value of this flag changes, such as when a root
    552          * becomes non-empty, you must send a content changed notification for
    553          * {@link DocumentsContract#buildRootsUri(String)}.
    554          *
    555          * @see #COLUMN_FLAGS
    556          * @see ContentResolver#notifyChange(Uri,
    557          *      android.database.ContentObserver, boolean)
    558          * @hide
    559          */
    560         public static final int FLAG_EMPTY = 1 << 16;
    561 
    562         /**
    563          * Flag indicating that this root should only be visible to advanced
    564          * users.
    565          *
    566          * @see #COLUMN_FLAGS
    567          * @hide
    568          */
    569         public static final int FLAG_ADVANCED = 1 << 17;
    570 
    571         /**
    572          * Flag indicating that this root has settings.
    573          *
    574          * @see #COLUMN_FLAGS
    575          * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS
    576          * @hide
    577          */
    578         public static final int FLAG_HAS_SETTINGS = 1 << 18;
    579 
    580         /**
    581          * Flag indicating that this root is on removable SD card storage.
    582          *
    583          * @see #COLUMN_FLAGS
    584          * @hide
    585          */
    586         public static final int FLAG_REMOVABLE_SD = 1 << 19;
    587 
    588         /**
    589          * Flag indicating that this root is on removable USB storage.
    590          *
    591          * @see #COLUMN_FLAGS
    592          * @hide
    593          */
    594         public static final int FLAG_REMOVABLE_USB = 1 << 20;
    595     }
    596 
    597     /**
    598      * Optional boolean flag included in a directory {@link Cursor#getExtras()}
    599      * indicating that a document provider is still loading data. For example, a
    600      * provider has returned some results, but is still waiting on an
    601      * outstanding network request. The provider must send a content changed
    602      * notification when loading is finished.
    603      *
    604      * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
    605      *      boolean)
    606      */
    607     public static final String EXTRA_LOADING = "loading";
    608 
    609     /**
    610      * Optional string included in a directory {@link Cursor#getExtras()}
    611      * providing an informational message that should be shown to a user. For
    612      * example, a provider may wish to indicate that not all documents are
    613      * available.
    614      */
    615     public static final String EXTRA_INFO = "info";
    616 
    617     /**
    618      * Optional string included in a directory {@link Cursor#getExtras()}
    619      * providing an error message that should be shown to a user. For example, a
    620      * provider may wish to indicate that a network error occurred. The user may
    621      * choose to retry, resulting in a new query.
    622      */
    623     public static final String EXTRA_ERROR = "error";
    624 
    625     /**
    626      * Optional result (I'm thinking boolean) answer to a question.
    627      * {@hide}
    628      */
    629     public static final String EXTRA_RESULT = "result";
    630 
    631     /** {@hide} */
    632     public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
    633     /** {@hide} */
    634     public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
    635     /** {@hide} */
    636     public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
    637     /** {@hide} */
    638     public static final String METHOD_COPY_DOCUMENT = "android:copyDocument";
    639     /** {@hide} */
    640     public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument";
    641     /** {@hide} */
    642     public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument";
    643     /** {@hide} */
    644     public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument";
    645 
    646     /** {@hide} */
    647     public static final String EXTRA_PARENT_URI = "parentUri";
    648     /** {@hide} */
    649     public static final String EXTRA_URI = "uri";
    650 
    651     private static final String PATH_ROOT = "root";
    652     private static final String PATH_RECENT = "recent";
    653     private static final String PATH_DOCUMENT = "document";
    654     private static final String PATH_CHILDREN = "children";
    655     private static final String PATH_SEARCH = "search";
    656     private static final String PATH_TREE = "tree";
    657 
    658     private static final String PARAM_QUERY = "query";
    659     private static final String PARAM_MANAGE = "manage";
    660 
    661     /**
    662      * Build URI representing the roots of a document provider. When queried, a
    663      * provider will return one or more rows with columns defined by
    664      * {@link Root}.
    665      *
    666      * @see DocumentsProvider#queryRoots(String[])
    667      */
    668     public static Uri buildRootsUri(String authority) {
    669         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    670                 .authority(authority).appendPath(PATH_ROOT).build();
    671     }
    672 
    673     /**
    674      * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a
    675      * document provider.
    676      *
    677      * @see #getRootId(Uri)
    678      */
    679     public static Uri buildRootUri(String authority, String rootId) {
    680         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    681                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
    682     }
    683 
    684     /**
    685      * Builds URI for user home directory on external (local) storage.
    686      * {@hide}
    687      */
    688     public static Uri buildHomeUri() {
    689         // TODO: Avoid this type of interpackage copying. Added here to avoid
    690         // direct coupling, but not ideal.
    691         return DocumentsContract.buildRootUri("com.android.externalstorage.documents", "home");
    692     }
    693 
    694     /**
    695      * Build URI representing the recently modified documents of a specific root
    696      * in a document provider. When queried, a provider will return zero or more
    697      * rows with columns defined by {@link Document}.
    698      *
    699      * @see DocumentsProvider#queryRecentDocuments(String, String[])
    700      * @see #getRootId(Uri)
    701      */
    702     public static Uri buildRecentDocumentsUri(String authority, String rootId) {
    703         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    704                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
    705                 .appendPath(PATH_RECENT).build();
    706     }
    707 
    708     /**
    709      * Build URI representing access to descendant documents of the given
    710      * {@link Document#COLUMN_DOCUMENT_ID}.
    711      *
    712      * @see #getTreeDocumentId(Uri)
    713      */
    714     public static Uri buildTreeDocumentUri(String authority, String documentId) {
    715         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
    716                 .appendPath(PATH_TREE).appendPath(documentId).build();
    717     }
    718 
    719     /**
    720      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
    721      * a document provider. When queried, a provider will return a single row
    722      * with columns defined by {@link Document}.
    723      *
    724      * @see DocumentsProvider#queryDocument(String, String[])
    725      * @see #getDocumentId(Uri)
    726      */
    727     public static Uri buildDocumentUri(String authority, String documentId) {
    728         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    729                 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
    730     }
    731 
    732     /**
    733      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
    734      * a document provider. When queried, a provider will return a single row
    735      * with columns defined by {@link Document}.
    736      * <p>
    737      * However, instead of directly accessing the target document, the returned
    738      * URI will leverage access granted through a subtree URI, typically
    739      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document
    740      * must be a descendant (child, grandchild, etc) of the subtree.
    741      * <p>
    742      * This is typically used to access documents under a user-selected
    743      * directory tree, since it doesn't require the user to separately confirm
    744      * each new document access.
    745      *
    746      * @param treeUri the subtree to leverage to gain access to the target
    747      *            document. The target directory must be a descendant of this
    748      *            subtree.
    749      * @param documentId the target document, which the caller may not have
    750      *            direct access to.
    751      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
    752      * @see DocumentsProvider#isChildDocument(String, String)
    753      * @see #buildDocumentUri(String, String)
    754      */
    755     public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
    756         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    757                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
    758                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
    759                 .appendPath(documentId).build();
    760     }
    761 
    762     /** {@hide} */
    763     public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) {
    764         if (isTreeUri(baseUri)) {
    765             return buildDocumentUriUsingTree(baseUri, documentId);
    766         } else {
    767             return buildDocumentUri(baseUri.getAuthority(), documentId);
    768         }
    769     }
    770 
    771     /**
    772      * Build URI representing the children of the target directory in a document
    773      * provider. When queried, a provider will return zero or more rows with
    774      * columns defined by {@link Document}.
    775      *
    776      * @param parentDocumentId the document to return children for, which must
    777      *            be a directory with MIME type of
    778      *            {@link Document#MIME_TYPE_DIR}.
    779      * @see DocumentsProvider#queryChildDocuments(String, String[], String)
    780      * @see #getDocumentId(Uri)
    781      */
    782     public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
    783         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
    784                 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
    785                 .build();
    786     }
    787 
    788     /**
    789      * Build URI representing the children of the target directory in a document
    790      * provider. When queried, a provider will return zero or more rows with
    791      * columns defined by {@link Document}.
    792      * <p>
    793      * However, instead of directly accessing the target directory, the returned
    794      * URI will leverage access granted through a subtree URI, typically
    795      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target
    796      * directory must be a descendant (child, grandchild, etc) of the subtree.
    797      * <p>
    798      * This is typically used to access documents under a user-selected
    799      * directory tree, since it doesn't require the user to separately confirm
    800      * each new document access.
    801      *
    802      * @param treeUri the subtree to leverage to gain access to the target
    803      *            document. The target directory must be a descendant of this
    804      *            subtree.
    805      * @param parentDocumentId the document to return children for, which the
    806      *            caller may not have direct access to, and which must be a
    807      *            directory with MIME type of {@link Document#MIME_TYPE_DIR}.
    808      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
    809      * @see DocumentsProvider#isChildDocument(String, String)
    810      * @see #buildChildDocumentsUri(String, String)
    811      */
    812     public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) {
    813         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    814                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
    815                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
    816                 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
    817     }
    818 
    819     /**
    820      * Build URI representing a search for matching documents under a specific
    821      * root in a document provider. When queried, a provider will return zero or
    822      * more rows with columns defined by {@link Document}.
    823      *
    824      * @see DocumentsProvider#querySearchDocuments(String, String, String[])
    825      * @see #getRootId(Uri)
    826      * @see #getSearchDocumentsQuery(Uri)
    827      */
    828     public static Uri buildSearchDocumentsUri(
    829             String authority, String rootId, String query) {
    830         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
    831                 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
    832                 .appendQueryParameter(PARAM_QUERY, query).build();
    833     }
    834 
    835     /**
    836      * Test if the given URI represents a {@link Document} backed by a
    837      * {@link DocumentsProvider}.
    838      *
    839      * @see #buildDocumentUri(String, String)
    840      * @see #buildDocumentUriUsingTree(Uri, String)
    841      */
    842     public static boolean isDocumentUri(Context context, @Nullable Uri uri) {
    843         if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
    844             final List<String> paths = uri.getPathSegments();
    845             if (paths.size() == 2) {
    846                 return PATH_DOCUMENT.equals(paths.get(0));
    847             } else if (paths.size() == 4) {
    848                 return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));
    849             }
    850         }
    851         return false;
    852     }
    853 
    854     /** {@hide} */
    855     public static boolean isRootUri(Context context, @Nullable Uri uri) {
    856         if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
    857             final List<String> paths = uri.getPathSegments();
    858             return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0)));
    859         }
    860         return false;
    861     }
    862 
    863     /** {@hide} */
    864     public static boolean isContentUri(@Nullable Uri uri) {
    865         return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme());
    866     }
    867 
    868     /**
    869      * Test if the given URI represents a {@link Document} tree.
    870      *
    871      * @see #buildTreeDocumentUri(String, String)
    872      * @see #getTreeDocumentId(Uri)
    873      */
    874     public static boolean isTreeUri(Uri uri) {
    875         final List<String> paths = uri.getPathSegments();
    876         return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
    877     }
    878 
    879     private static boolean isDocumentsProvider(Context context, String authority) {
    880         final Intent intent = new Intent(PROVIDER_INTERFACE);
    881         final List<ResolveInfo> infos = context.getPackageManager()
    882                 .queryIntentContentProviders(intent, 0);
    883         for (ResolveInfo info : infos) {
    884             if (authority.equals(info.providerInfo.authority)) {
    885                 return true;
    886             }
    887         }
    888         return false;
    889     }
    890 
    891     /**
    892      * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI.
    893      */
    894     public static String getRootId(Uri rootUri) {
    895         final List<String> paths = rootUri.getPathSegments();
    896         if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
    897             return paths.get(1);
    898         }
    899         throw new IllegalArgumentException("Invalid URI: " + rootUri);
    900     }
    901 
    902     /**
    903      * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
    904      *
    905      * @see #isDocumentUri(Context, Uri)
    906      */
    907     public static String getDocumentId(Uri documentUri) {
    908         final List<String> paths = documentUri.getPathSegments();
    909         if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
    910             return paths.get(1);
    911         }
    912         if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0))
    913                 && PATH_DOCUMENT.equals(paths.get(2))) {
    914             return paths.get(3);
    915         }
    916         throw new IllegalArgumentException("Invalid URI: " + documentUri);
    917     }
    918 
    919     /**
    920      * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
    921      */
    922     public static String getTreeDocumentId(Uri documentUri) {
    923         final List<String> paths = documentUri.getPathSegments();
    924         if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
    925             return paths.get(1);
    926         }
    927         throw new IllegalArgumentException("Invalid URI: " + documentUri);
    928     }
    929 
    930     /**
    931      * Extract the search query from a URI built by
    932      * {@link #buildSearchDocumentsUri(String, String, String)}.
    933      */
    934     public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
    935         return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
    936     }
    937 
    938     /** {@hide} */
    939     public static Uri setManageMode(Uri uri) {
    940         return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
    941     }
    942 
    943     /** {@hide} */
    944     public static boolean isManageMode(Uri uri) {
    945         return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
    946     }
    947 
    948     /**
    949      * Return thumbnail representing the document at the given URI. Callers are
    950      * responsible for their own in-memory caching.
    951      *
    952      * @param documentUri document to return thumbnail for, which must have
    953      *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
    954      * @param size optimal thumbnail size desired. A provider may return a
    955      *            thumbnail of a different size, but never more than double the
    956      *            requested size.
    957      * @param signal signal used to indicate if caller is no longer interested
    958      *            in the thumbnail.
    959      * @return decoded thumbnail, or {@code null} if problem was encountered.
    960      * @see DocumentsProvider#openDocumentThumbnail(String, Point,
    961      *      android.os.CancellationSignal)
    962      */
    963     public static Bitmap getDocumentThumbnail(
    964             ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
    965         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
    966                 documentUri.getAuthority());
    967         try {
    968             return getDocumentThumbnail(client, documentUri, size, signal);
    969         } catch (Exception e) {
    970             if (!(e instanceof OperationCanceledException)) {
    971                 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
    972             }
    973             return null;
    974         } finally {
    975             ContentProviderClient.releaseQuietly(client);
    976         }
    977     }
    978 
    979     /** {@hide} */
    980     public static Bitmap getDocumentThumbnail(
    981             ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
    982             throws RemoteException, IOException {
    983         final Bundle openOpts = new Bundle();
    984         openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size);
    985 
    986         AssetFileDescriptor afd = null;
    987         Bitmap bitmap = null;
    988         try {
    989             afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
    990 
    991             final FileDescriptor fd = afd.getFileDescriptor();
    992             final long offset = afd.getStartOffset();
    993 
    994             // Try seeking on the returned FD, since it gives us the most
    995             // optimal decode path; otherwise fall back to buffering.
    996             BufferedInputStream is = null;
    997             try {
    998                 Os.lseek(fd, offset, SEEK_SET);
    999             } catch (ErrnoException e) {
   1000                 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
   1001                 is.mark(THUMBNAIL_BUFFER_SIZE);
   1002             }
   1003 
   1004             // We requested a rough thumbnail size, but the remote size may have
   1005             // returned something giant, so defensively scale down as needed.
   1006             final BitmapFactory.Options opts = new BitmapFactory.Options();
   1007             opts.inJustDecodeBounds = true;
   1008             if (is != null) {
   1009                 BitmapFactory.decodeStream(is, null, opts);
   1010             } else {
   1011                 BitmapFactory.decodeFileDescriptor(fd, null, opts);
   1012             }
   1013 
   1014             final int widthSample = opts.outWidth / size.x;
   1015             final int heightSample = opts.outHeight / size.y;
   1016 
   1017             opts.inJustDecodeBounds = false;
   1018             opts.inSampleSize = Math.min(widthSample, heightSample);
   1019             if (is != null) {
   1020                 is.reset();
   1021                 bitmap = BitmapFactory.decodeStream(is, null, opts);
   1022             } else {
   1023                 try {
   1024                     Os.lseek(fd, offset, SEEK_SET);
   1025                 } catch (ErrnoException e) {
   1026                     e.rethrowAsIOException();
   1027                 }
   1028                 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
   1029             }
   1030 
   1031             // Transform the bitmap if requested. We use a side-channel to
   1032             // communicate the orientation, since EXIF thumbnails don't contain
   1033             // the rotation flags of the original image.
   1034             final Bundle extras = afd.getExtras();
   1035             final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
   1036             if (orientation != 0) {
   1037                 final int width = bitmap.getWidth();
   1038                 final int height = bitmap.getHeight();
   1039 
   1040                 final Matrix m = new Matrix();
   1041                 m.setRotate(orientation, width / 2, height / 2);
   1042                 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
   1043             }
   1044         } finally {
   1045             IoUtils.closeQuietly(afd);
   1046         }
   1047 
   1048         return bitmap;
   1049     }
   1050 
   1051     /**
   1052      * Create a new document with given MIME type and display name.
   1053      *
   1054      * @param parentDocumentUri directory with
   1055      *            {@link Document#FLAG_DIR_SUPPORTS_CREATE}
   1056      * @param mimeType MIME type of new document
   1057      * @param displayName name of new document
   1058      * @return newly created document, or {@code null} if failed
   1059      */
   1060     public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
   1061             String mimeType, String displayName) {
   1062         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1063                 parentDocumentUri.getAuthority());
   1064         try {
   1065             return createDocument(client, parentDocumentUri, mimeType, displayName);
   1066         } catch (Exception e) {
   1067             Log.w(TAG, "Failed to create document", e);
   1068             return null;
   1069         } finally {
   1070             ContentProviderClient.releaseQuietly(client);
   1071         }
   1072     }
   1073 
   1074     /** {@hide} */
   1075     public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
   1076             String mimeType, String displayName) throws RemoteException {
   1077         final Bundle in = new Bundle();
   1078         in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
   1079         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
   1080         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
   1081 
   1082         final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
   1083         return out.getParcelable(DocumentsContract.EXTRA_URI);
   1084     }
   1085 
   1086     /** {@hide} */
   1087     public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
   1088             Uri childDocumentUri) throws RemoteException {
   1089 
   1090         final Bundle in = new Bundle();
   1091         in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
   1092         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
   1093 
   1094         final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
   1095         if (out == null) {
   1096             throw new RemoteException("Failed to get a reponse from isChildDocument query.");
   1097         }
   1098         if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
   1099             throw new RemoteException("Response did not include result field..");
   1100         }
   1101         return out.getBoolean(DocumentsContract.EXTRA_RESULT);
   1102     }
   1103 
   1104     /**
   1105      * Change the display name of an existing document.
   1106      * <p>
   1107      * If the underlying provider needs to create a new
   1108      * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display
   1109      * name, that new document is returned and the original document is no
   1110      * longer valid. Otherwise, the original document is returned.
   1111      *
   1112      * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME}
   1113      * @param displayName updated name for document
   1114      * @return the existing or new document after the rename, or {@code null} if
   1115      *         failed.
   1116      */
   1117     public static Uri renameDocument(ContentResolver resolver, Uri documentUri,
   1118             String displayName) {
   1119         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1120                 documentUri.getAuthority());
   1121         try {
   1122             return renameDocument(client, documentUri, displayName);
   1123         } catch (Exception e) {
   1124             Log.w(TAG, "Failed to rename document", e);
   1125             return null;
   1126         } finally {
   1127             ContentProviderClient.releaseQuietly(client);
   1128         }
   1129     }
   1130 
   1131     /** {@hide} */
   1132     public static Uri renameDocument(ContentProviderClient client, Uri documentUri,
   1133             String displayName) throws RemoteException {
   1134         final Bundle in = new Bundle();
   1135         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
   1136         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
   1137 
   1138         final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
   1139         final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
   1140         return (outUri != null) ? outUri : documentUri;
   1141     }
   1142 
   1143     /**
   1144      * Delete the given document.
   1145      *
   1146      * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
   1147      * @return if the document was deleted successfully.
   1148      */
   1149     public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
   1150         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1151                 documentUri.getAuthority());
   1152         try {
   1153             deleteDocument(client, documentUri);
   1154             return true;
   1155         } catch (Exception e) {
   1156             Log.w(TAG, "Failed to delete document", e);
   1157             return false;
   1158         } finally {
   1159             ContentProviderClient.releaseQuietly(client);
   1160         }
   1161     }
   1162 
   1163     /** {@hide} */
   1164     public static void deleteDocument(ContentProviderClient client, Uri documentUri)
   1165             throws RemoteException {
   1166         final Bundle in = new Bundle();
   1167         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
   1168 
   1169         client.call(METHOD_DELETE_DOCUMENT, null, in);
   1170     }
   1171 
   1172     /**
   1173      * Copies the given document.
   1174      *
   1175      * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_COPY}
   1176      * @param targetParentDocumentUri document which will become a parent of the source
   1177      *         document's copy.
   1178      * @return the copied document, or {@code null} if failed.
   1179      */
   1180     public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri,
   1181             Uri targetParentDocumentUri) {
   1182         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1183                 sourceDocumentUri.getAuthority());
   1184         try {
   1185             return copyDocument(client, sourceDocumentUri, targetParentDocumentUri);
   1186         } catch (Exception e) {
   1187             Log.w(TAG, "Failed to copy document", e);
   1188             return null;
   1189         } finally {
   1190             ContentProviderClient.releaseQuietly(client);
   1191         }
   1192     }
   1193 
   1194     /** {@hide} */
   1195     public static Uri copyDocument(ContentProviderClient client, Uri sourceDocumentUri,
   1196             Uri targetParentDocumentUri) throws RemoteException {
   1197         final Bundle in = new Bundle();
   1198         in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
   1199         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
   1200 
   1201         final Bundle out = client.call(METHOD_COPY_DOCUMENT, null, in);
   1202         return out.getParcelable(DocumentsContract.EXTRA_URI);
   1203     }
   1204 
   1205     /**
   1206      * Moves the given document under a new parent.
   1207      *
   1208      * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_MOVE}
   1209      * @param sourceParentDocumentUri parent document of the document to move.
   1210      * @param targetParentDocumentUri document which will become a new parent of the source
   1211      *         document.
   1212      * @return the moved document, or {@code null} if failed.
   1213      */
   1214     public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri,
   1215             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) {
   1216         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1217                 sourceDocumentUri.getAuthority());
   1218         try {
   1219             return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri,
   1220                     targetParentDocumentUri);
   1221         } catch (Exception e) {
   1222             Log.w(TAG, "Failed to move document", e);
   1223             return null;
   1224         } finally {
   1225             ContentProviderClient.releaseQuietly(client);
   1226         }
   1227     }
   1228 
   1229     /** {@hide} */
   1230     public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri,
   1231             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws RemoteException {
   1232         final Bundle in = new Bundle();
   1233         in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
   1234         in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri);
   1235         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
   1236 
   1237         final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in);
   1238         return out.getParcelable(DocumentsContract.EXTRA_URI);
   1239     }
   1240 
   1241     /**
   1242      * Removes the given document from a parent directory.
   1243      *
   1244      * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
   1245      * This method is especially useful if the document can be in multiple parents.
   1246      *
   1247      * @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE}
   1248      * @param parentDocumentUri parent document of the document to remove.
   1249      * @return true if the document was removed successfully.
   1250      */
   1251     public static boolean removeDocument(ContentResolver resolver, Uri documentUri,
   1252             Uri parentDocumentUri) {
   1253         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   1254                 documentUri.getAuthority());
   1255         try {
   1256             removeDocument(client, documentUri, parentDocumentUri);
   1257             return true;
   1258         } catch (Exception e) {
   1259             Log.w(TAG, "Failed to remove document", e);
   1260             return false;
   1261         } finally {
   1262             ContentProviderClient.releaseQuietly(client);
   1263         }
   1264     }
   1265 
   1266     /** {@hide} */
   1267     public static void removeDocument(ContentProviderClient client, Uri documentUri,
   1268             Uri parentDocumentUri) throws RemoteException {
   1269         final Bundle in = new Bundle();
   1270         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
   1271         in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri);
   1272 
   1273         client.call(METHOD_REMOVE_DOCUMENT, null, in);
   1274     }
   1275 
   1276     /**
   1277      * Open the given image for thumbnail purposes, using any embedded EXIF
   1278      * thumbnail if available, and providing orientation hints from the parent
   1279      * image.
   1280      *
   1281      * @hide
   1282      */
   1283     public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
   1284         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
   1285                 file, ParcelFileDescriptor.MODE_READ_ONLY);
   1286         Bundle extras = null;
   1287 
   1288         try {
   1289             final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
   1290 
   1291             switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
   1292                 case ExifInterface.ORIENTATION_ROTATE_90:
   1293                     extras = new Bundle(1);
   1294                     extras.putInt(EXTRA_ORIENTATION, 90);
   1295                     break;
   1296                 case ExifInterface.ORIENTATION_ROTATE_180:
   1297                     extras = new Bundle(1);
   1298                     extras.putInt(EXTRA_ORIENTATION, 180);
   1299                     break;
   1300                 case ExifInterface.ORIENTATION_ROTATE_270:
   1301                     extras = new Bundle(1);
   1302                     extras.putInt(EXTRA_ORIENTATION, 270);
   1303                     break;
   1304             }
   1305 
   1306             final long[] thumb = exif.getThumbnailRange();
   1307             if (thumb != null) {
   1308                 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
   1309             }
   1310         } catch (IOException e) {
   1311         }
   1312 
   1313         return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
   1314     }
   1315 }
   1316