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