• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.internal.util.Preconditions.checkArgument;
23 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
24 import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
25 
26 import android.annotation.Nullable;
27 import android.content.ContentProviderClient;
28 import android.content.ContentResolver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentSender;
32 import android.content.pm.ResolveInfo;
33 import android.content.res.AssetFileDescriptor;
34 import android.database.Cursor;
35 import android.graphics.Bitmap;
36 import android.graphics.BitmapFactory;
37 import android.graphics.Matrix;
38 import android.graphics.Point;
39 import android.media.ExifInterface;
40 import android.net.Uri;
41 import android.os.Build;
42 import android.os.Bundle;
43 import android.os.CancellationSignal;
44 import android.os.OperationCanceledException;
45 import android.os.Parcel;
46 import android.os.ParcelFileDescriptor;
47 import android.os.ParcelFileDescriptor.OnCloseListener;
48 import android.os.Parcelable;
49 import android.os.ParcelableException;
50 import android.os.RemoteException;
51 import android.os.storage.StorageVolume;
52 import android.system.ErrnoException;
53 import android.system.Os;
54 import android.util.Log;
55 
56 import libcore.io.IoUtils;
57 
58 import java.io.BufferedInputStream;
59 import java.io.File;
60 import java.io.FileDescriptor;
61 import java.io.FileInputStream;
62 import java.io.FileNotFoundException;
63 import java.io.IOException;
64 import java.util.List;
65 import java.util.Objects;
66 
67 /**
68  * Defines the contract between a documents provider and the platform.
69  * <p>
70  * To create a document provider, extend {@link DocumentsProvider}, which
71  * provides a foundational implementation of this contract.
72  * <p>
73  * All client apps must hold a valid URI permission grant to access documents,
74  * typically issued when a user makes a selection through
75  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
76  * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
77  * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
78  *
79  * @see DocumentsProvider
80  */
81 public final class DocumentsContract {
82     private static final String TAG = "DocumentsContract";
83 
84     // content://com.example/root/
85     // content://com.example/root/sdcard/
86     // content://com.example/root/sdcard/recent/
87     // content://com.example/root/sdcard/search/?query=pony
88     // content://com.example/document/12/
89     // content://com.example/document/12/children/
90     // content://com.example/tree/12/document/24/
91     // content://com.example/tree/12/document/24/children/
92 
DocumentsContract()93     private DocumentsContract() {
94     }
95 
96     /**
97      * Intent action used to identify {@link DocumentsProvider} instances. This
98      * is used in the {@code <intent-filter>} of a {@code <provider>}.
99      */
100     public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
101 
102     /** {@hide} */
103     public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
104 
105     /** {@hide} */
106     public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
107 
108     /** {@hide} */
109     public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI";
110 
111     /**
112      * Sets the desired initial location visible to user when file chooser is shown.
113      *
114      * <p>Applicable to {@link Intent} with actions:
115      * <ul>
116      *      <li>{@link Intent#ACTION_OPEN_DOCUMENT}</li>
117      *      <li>{@link Intent#ACTION_CREATE_DOCUMENT}</li>
118      *      <li>{@link Intent#ACTION_OPEN_DOCUMENT_TREE}</li>
119      * </ul>
120      *
121      * <p>Location should specify a document URI or a tree URI with document ID. If
122      * this URI identifies a non-directory, document navigator will attempt to use the parent
123      * of the document as the initial location.
124      *
125      * <p>The initial location is system specific if this extra is missing or document navigator
126      * failed to locate the desired initial location.
127      */
128     public static final String EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI";
129 
130     /**
131      * Set this in a DocumentsUI intent to cause a package's own roots to be
132      * excluded from the roots list.
133      */
134     public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
135 
136     /**
137      * Included in {@link AssetFileDescriptor#getExtras()} when returned
138      * thumbnail should be rotated.
139      *
140      * @see MediaStore.Images.ImageColumns#ORIENTATION
141      */
142     public static final String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION";
143 
144     /**
145      * Overrides the default prompt text in DocumentsUI when set in an intent.
146      */
147     public static final String EXTRA_PROMPT = "android.provider.extra.PROMPT";
148 
149     /**
150      * Action of intent issued by DocumentsUI when user wishes to open/configure/manage a particular
151      * document in the provider application.
152      *
153      * <p>When issued, the intent will include the URI of the document as the intent data.
154      *
155      * <p>A provider wishing to provide support for this action should do two things.
156      * <li>Add an {@code <intent-filter>} matching this action.
157      * <li>When supplying information in {@link DocumentsProvider#queryChildDocuments}, include
158      * {@link Document#FLAG_SUPPORTS_SETTINGS} in the flags for each document that supports
159      * settings.
160      *
161      * @see DocumentsContact#Document#FLAG_SUPPORTS_SETTINGS
162      */
163     public static final String
164             ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS";
165 
166     /** {@hide} */
167     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
168 
169     /** {@hide} */
170     public static final String
171             ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
172 
173     /**
174      * Buffer is large enough to rewind past any EXIF headers.
175      */
176     private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
177 
178     /** {@hide} */
179     public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
180             "com.android.externalstorage.documents";
181 
182     /** {@hide} */
183     public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui";
184 
185     /** {@hide} */
186     public static final String METADATA_TYPES = "android:documentMetadataType";
187 
188     /** {@hide} */
189     public static final String METADATA_EXIF = "android:documentExif";
190 
191     /** {@hide} */
192     public static final String EXTRA_METADATA_TAGS = "android:documentMetadataTags";
193 
194     /**
195      * Constants related to a document, including {@link Cursor} column names
196      * and flags.
197      * <p>
198      * A document can be either an openable stream (with a specific MIME type),
199      * or a directory containing additional documents (with the
200      * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a
201      * subtree containing zero or more documents, which can recursively contain
202      * even more documents and directories.
203      * <p>
204      * All columns are <em>read-only</em> to client applications.
205      */
206     public final static class Document {
Document()207         private Document() {
208         }
209 
210         /**
211          * Unique ID of a document. This ID is both provided by and interpreted
212          * by a {@link DocumentsProvider}, and should be treated as an opaque
213          * value by client applications. This column is required.
214          * <p>
215          * Each document must have a unique ID within a provider, but that
216          * single document may be included as a child of multiple directories.
217          * <p>
218          * A provider must always return durable IDs, since they will be used to
219          * issue long-term URI permission grants when an application interacts
220          * with {@link Intent#ACTION_OPEN_DOCUMENT} and
221          * {@link Intent#ACTION_CREATE_DOCUMENT}.
222          * <p>
223          * Type: STRING
224          */
225         public static final String COLUMN_DOCUMENT_ID = "document_id";
226 
227         /**
228          * Concrete MIME type of a document. For example, "image/png" or
229          * "application/pdf" for openable files. A document can also be a
230          * directory containing additional documents, which is represented with
231          * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
232          * <p>
233          * Type: STRING
234          *
235          * @see #MIME_TYPE_DIR
236          */
237         public static final String COLUMN_MIME_TYPE = "mime_type";
238 
239         /**
240          * Display name of a document, used as the primary title displayed to a
241          * user. This column is required.
242          * <p>
243          * Type: STRING
244          */
245         public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
246 
247         /**
248          * Summary of a document, which may be shown to a user. This column is
249          * optional, and may be {@code null}.
250          * <p>
251          * Type: STRING
252          */
253         public static final String COLUMN_SUMMARY = "summary";
254 
255         /**
256          * Timestamp when a document was last modified, in milliseconds since
257          * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
258          * {@code null} if unknown. A {@link DocumentsProvider} can update this
259          * field using events from {@link OnCloseListener} or other reliable
260          * {@link ParcelFileDescriptor} transports.
261          * <p>
262          * Type: INTEGER (long)
263          *
264          * @see System#currentTimeMillis()
265          */
266         public static final String COLUMN_LAST_MODIFIED = "last_modified";
267 
268         /**
269          * Specific icon resource ID for a document. This column is optional,
270          * and may be {@code null} to use a platform-provided default icon based
271          * on {@link #COLUMN_MIME_TYPE}.
272          * <p>
273          * Type: INTEGER (int)
274          */
275         public static final String COLUMN_ICON = "icon";
276 
277         /**
278          * Flags that apply to a document. This column is required.
279          * <p>
280          * Type: INTEGER (int)
281          *
282          * @see #FLAG_SUPPORTS_WRITE
283          * @see #FLAG_SUPPORTS_DELETE
284          * @see #FLAG_SUPPORTS_THUMBNAIL
285          * @see #FLAG_DIR_PREFERS_GRID
286          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
287          * @see #FLAG_VIRTUAL_DOCUMENT
288          * @see #FLAG_SUPPORTS_COPY
289          * @see #FLAG_SUPPORTS_MOVE
290          * @see #FLAG_SUPPORTS_REMOVE
291          */
292         public static final String COLUMN_FLAGS = "flags";
293 
294         /**
295          * Size of a document, in bytes, or {@code null} if unknown. This column
296          * is required.
297          * <p>
298          * Type: INTEGER (long)
299          */
300         public static final String COLUMN_SIZE = OpenableColumns.SIZE;
301 
302         /**
303          * MIME type of a document which is a directory that may contain
304          * additional documents.
305          *
306          * @see #COLUMN_MIME_TYPE
307          */
308         public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
309 
310         /**
311          * Flag indicating that a document can be represented as a thumbnail.
312          *
313          * @see #COLUMN_FLAGS
314          * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
315          *      Point, CancellationSignal)
316          * @see DocumentsProvider#openDocumentThumbnail(String, Point,
317          *      android.os.CancellationSignal)
318          */
319         public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
320 
321         /**
322          * Flag indicating that a document supports writing.
323          * <p>
324          * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
325          * the calling application is granted both
326          * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
327          * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
328          * writability of a document may change over time, for example due to
329          * remote access changes. This flag indicates that a document client can
330          * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
331          *
332          * @see #COLUMN_FLAGS
333          */
334         public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
335 
336         /**
337          * Flag indicating that a document is deletable.
338          *
339          * @see #COLUMN_FLAGS
340          * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
341          * @see DocumentsProvider#deleteDocument(String)
342          */
343         public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
344 
345         /**
346          * Flag indicating that a document is a directory that supports creation
347          * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
348          * {@link #MIME_TYPE_DIR}.
349          *
350          * @see #COLUMN_FLAGS
351          * @see DocumentsProvider#createDocument(String, String, String)
352          */
353         public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
354 
355         /**
356          * Flag indicating that a directory prefers its contents be shown in a
357          * larger format grid. Usually suitable when a directory contains mostly
358          * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
359          * {@link #MIME_TYPE_DIR}.
360          *
361          * @see #COLUMN_FLAGS
362          */
363         public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
364 
365         /**
366          * Flag indicating that a directory prefers its contents be sorted by
367          * {@link #COLUMN_LAST_MODIFIED}. Only valid when
368          * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
369          *
370          * @see #COLUMN_FLAGS
371          */
372         public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
373 
374         /**
375          * Flag indicating that a document can be renamed.
376          *
377          * @see #COLUMN_FLAGS
378          * @see DocumentsContract#renameDocument(ContentResolver, Uri,
379          *      String)
380          * @see DocumentsProvider#renameDocument(String, String)
381          */
382         public static final int FLAG_SUPPORTS_RENAME = 1 << 6;
383 
384         /**
385          * Flag indicating that a document can be copied to another location
386          * within the same document provider.
387          *
388          * @see #COLUMN_FLAGS
389          * @see DocumentsContract#copyDocument(ContentResolver, Uri, Uri)
390          * @see DocumentsProvider#copyDocument(String, String)
391          */
392         public static final int FLAG_SUPPORTS_COPY = 1 << 7;
393 
394         /**
395          * Flag indicating that a document can be moved to another location
396          * within the same document provider.
397          *
398          * @see #COLUMN_FLAGS
399          * @see DocumentsContract#moveDocument(ContentResolver, Uri, Uri, Uri)
400          * @see DocumentsProvider#moveDocument(String, String, String)
401          */
402         public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
403 
404         /**
405          * Flag indicating that a document is virtual, and doesn't have byte
406          * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
407          *
408          * <p><em>Virtual documents must have at least one alternative streamable
409          * format via {@link DocumentsProvider#openTypedDocument}</em>
410          *
411          * @see #COLUMN_FLAGS
412          * @see #COLUMN_MIME_TYPE
413          * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
414          *      android.os.CancellationSignal)
415          * @see DocumentsProvider#getDocumentStreamTypes(String, String)
416          */
417         public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
418 
419         /**
420          * Flag indicating that a document can be removed from a parent.
421          *
422          * @see #COLUMN_FLAGS
423          * @see DocumentsContract#removeDocument(ContentResolver, Uri, Uri)
424          * @see DocumentsProvider#removeDocument(String, String)
425          */
426         public static final int FLAG_SUPPORTS_REMOVE = 1 << 10;
427 
428         /**
429          * Flag indicating that a document has settings that can be configured by user.
430          *
431          * @see #COLUMN_FLAGS
432          * @see #ACTION_DOCUMENT_SETTINGS
433          */
434         public static final int FLAG_SUPPORTS_SETTINGS = 1 << 11;
435 
436         /**
437          * Flag indicating that a Web link can be obtained for the document.
438          *
439          * @see #COLUMN_FLAGS
440          * @see DocumentsContract#createWebLinkIntent(PackageManager, Uri, Bundle)
441          */
442         public static final int FLAG_WEB_LINKABLE = 1 << 12;
443 
444         /**
445          * Flag indicating that a document is not complete, likely its
446          * contents are being downloaded. Partial files cannot be opened,
447          * copied, moved in the UI. But they can be deleted and retried
448          * if they represent a failed download.
449          *
450          * @see #COLUMN_FLAGS
451          * @hide
452          */
453         public static final int FLAG_PARTIAL = 1 << 16;
454 
455         /**
456          * Flag indicating that a document has available metadata that can be read
457          * using DocumentsContract#getDocumentMetadata
458          * @hide
459          */
460         public static final int FLAG_SUPPORTS_METADATA = 1 << 17;
461     }
462 
463     /**
464      * Constants related to a root of documents, including {@link Cursor} column
465      * names and flags. A root is the start of a tree of documents, such as a
466      * physical storage device, or an account. Each root starts at the directory
467      * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively
468      * contain both documents and directories.
469      * <p>
470      * All columns are <em>read-only</em> to client applications.
471      */
472     public final static class Root {
Root()473         private Root() {
474         }
475 
476         /**
477          * Unique ID of a root. This ID is both provided by and interpreted by a
478          * {@link DocumentsProvider}, and should be treated as an opaque value
479          * by client applications. This column is required.
480          * <p>
481          * Type: STRING
482          */
483         public static final String COLUMN_ROOT_ID = "root_id";
484 
485         /**
486          * Flags that apply to a root. This column is required.
487          * <p>
488          * Type: INTEGER (int)
489          *
490          * @see #FLAG_LOCAL_ONLY
491          * @see #FLAG_SUPPORTS_CREATE
492          * @see #FLAG_SUPPORTS_RECENTS
493          * @see #FLAG_SUPPORTS_SEARCH
494          */
495         public static final String COLUMN_FLAGS = "flags";
496 
497         /**
498          * Icon resource ID for a root. This column is required.
499          * <p>
500          * Type: INTEGER (int)
501          */
502         public static final String COLUMN_ICON = "icon";
503 
504         /**
505          * Title for a root, which will be shown to a user. This column is
506          * required. For a single storage service surfacing multiple accounts as
507          * different roots, this title should be the name of the service.
508          * <p>
509          * Type: STRING
510          */
511         public static final String COLUMN_TITLE = "title";
512 
513         /**
514          * Summary for this root, which may be shown to a user. This column is
515          * optional, and may be {@code null}. For a single storage service
516          * surfacing multiple accounts as different roots, this summary should
517          * be the name of the account.
518          * <p>
519          * Type: STRING
520          */
521         public static final String COLUMN_SUMMARY = "summary";
522 
523         /**
524          * Document which is a directory that represents the top directory of
525          * this root. This column is required.
526          * <p>
527          * Type: STRING
528          *
529          * @see Document#COLUMN_DOCUMENT_ID
530          */
531         public static final String COLUMN_DOCUMENT_ID = "document_id";
532 
533         /**
534          * Number of bytes available in this root. This column is optional, and
535          * may be {@code null} if unknown or unbounded.
536          * <p>
537          * Type: INTEGER (long)
538          */
539         public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
540 
541         /**
542          * Capacity of a root in bytes. This column is optional, and may be
543          * {@code null} if unknown or unbounded.
544          * <p>
545          * Type: INTEGER (long)
546          */
547         public static final String COLUMN_CAPACITY_BYTES = "capacity_bytes";
548 
549         /**
550          * MIME types supported by this root. This column is optional, and if
551          * {@code null} the root is assumed to support all MIME types. Multiple
552          * MIME types can be separated by a newline. For example, a root
553          * supporting audio might return "audio/*\napplication/x-flac".
554          * <p>
555          * Type: STRING
556          */
557         public static final String COLUMN_MIME_TYPES = "mime_types";
558 
559         /**
560          * MIME type for a root.
561          */
562         public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
563 
564         /**
565          * Flag indicating that at least one directory under this root supports
566          * creating content. Roots with this flag will be shown when an
567          * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
568          *
569          * @see #COLUMN_FLAGS
570          */
571         public static final int FLAG_SUPPORTS_CREATE = 1;
572 
573         /**
574          * Flag indicating that this root offers content that is strictly local
575          * on the device. That is, no network requests are made for the content.
576          *
577          * @see #COLUMN_FLAGS
578          * @see Intent#EXTRA_LOCAL_ONLY
579          */
580         public static final int FLAG_LOCAL_ONLY = 1 << 1;
581 
582         /**
583          * Flag indicating that this root can be queried to provide recently
584          * modified documents.
585          *
586          * @see #COLUMN_FLAGS
587          * @see DocumentsContract#buildRecentDocumentsUri(String, String)
588          * @see DocumentsProvider#queryRecentDocuments(String, String[])
589          */
590         public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
591 
592         /**
593          * Flag indicating that this root supports search.
594          *
595          * @see #COLUMN_FLAGS
596          * @see DocumentsContract#buildSearchDocumentsUri(String, String,
597          *      String)
598          * @see DocumentsProvider#querySearchDocuments(String, String,
599          *      String[])
600          */
601         public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
602 
603         /**
604          * Flag indicating that this root supports testing parent child
605          * relationships.
606          *
607          * @see #COLUMN_FLAGS
608          * @see DocumentsProvider#isChildDocument(String, String)
609          */
610         public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
611 
612         /**
613          * Flag indicating that this root can be ejected.
614          *
615          * @see #COLUMN_FLAGS
616          * @see DocumentsContract#ejectRoot(ContentResolver, Uri)
617          * @see DocumentsProvider#ejectRoot(String)
618          */
619         public static final int FLAG_SUPPORTS_EJECT = 1 << 5;
620 
621         /**
622          * Flag indicating that this root is currently empty. This may be used
623          * to hide the root when opening documents, but the root will still be
624          * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
625          * also set. If the value of this flag changes, such as when a root
626          * becomes non-empty, you must send a content changed notification for
627          * {@link DocumentsContract#buildRootsUri(String)}.
628          *
629          * @see #COLUMN_FLAGS
630          * @see ContentResolver#notifyChange(Uri,
631          *      android.database.ContentObserver, boolean)
632          * @hide
633          */
634         public static final int FLAG_EMPTY = 1 << 16;
635 
636         /**
637          * Flag indicating that this root should only be visible to advanced
638          * users.
639          *
640          * @see #COLUMN_FLAGS
641          * @hide
642          */
643         public static final int FLAG_ADVANCED = 1 << 17;
644 
645         /**
646          * Flag indicating that this root has settings.
647          *
648          * @see #COLUMN_FLAGS
649          * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS
650          * @hide
651          */
652         public static final int FLAG_HAS_SETTINGS = 1 << 18;
653 
654         /**
655          * Flag indicating that this root is on removable SD card storage.
656          *
657          * @see #COLUMN_FLAGS
658          * @hide
659          */
660         public static final int FLAG_REMOVABLE_SD = 1 << 19;
661 
662         /**
663          * Flag indicating that this root is on removable USB storage.
664          *
665          * @see #COLUMN_FLAGS
666          * @hide
667          */
668         public static final int FLAG_REMOVABLE_USB = 1 << 20;
669     }
670 
671     /**
672      * Optional boolean flag included in a directory {@link Cursor#getExtras()}
673      * indicating that a document provider is still loading data. For example, a
674      * provider has returned some results, but is still waiting on an
675      * outstanding network request. The provider must send a content changed
676      * notification when loading is finished.
677      *
678      * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
679      *      boolean)
680      */
681     public static final String EXTRA_LOADING = "loading";
682 
683     /**
684      * Optional string included in a directory {@link Cursor#getExtras()}
685      * providing an informational message that should be shown to a user. For
686      * example, a provider may wish to indicate that not all documents are
687      * available.
688      */
689     public static final String EXTRA_INFO = "info";
690 
691     /**
692      * Optional string included in a directory {@link Cursor#getExtras()}
693      * providing an error message that should be shown to a user. For example, a
694      * provider may wish to indicate that a network error occurred. The user may
695      * choose to retry, resulting in a new query.
696      */
697     public static final String EXTRA_ERROR = "error";
698 
699     /**
700      * Optional result (I'm thinking boolean) answer to a question.
701      * {@hide}
702      */
703     public static final String EXTRA_RESULT = "result";
704 
705     /** {@hide} */
706     public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
707     /** {@hide} */
708     public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
709     /** {@hide} */
710     public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
711     /** {@hide} */
712     public static final String METHOD_COPY_DOCUMENT = "android:copyDocument";
713     /** {@hide} */
714     public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument";
715     /** {@hide} */
716     public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument";
717     /** {@hide} */
718     public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument";
719     /** {@hide} */
720     public static final String METHOD_EJECT_ROOT = "android:ejectRoot";
721     /** {@hide} */
722     public static final String METHOD_FIND_DOCUMENT_PATH = "android:findDocumentPath";
723     /** {@hide} */
724     public static final String METHOD_CREATE_WEB_LINK_INTENT = "android:createWebLinkIntent";
725     /** {@hide} */
726     public static final String METHOD_GET_DOCUMENT_METADATA = "android:getDocumentMetadata";
727 
728     /** {@hide} */
729     public static final String EXTRA_PARENT_URI = "parentUri";
730     /** {@hide} */
731     public static final String EXTRA_URI = "uri";
732 
733     /**
734      * @see #createWebLinkIntent(ContentResolver, Uri, Bundle)
735      * {@hide}
736      */
737     public static final String EXTRA_OPTIONS = "options";
738 
739     private static final String PATH_ROOT = "root";
740     private static final String PATH_RECENT = "recent";
741     private static final String PATH_DOCUMENT = "document";
742     private static final String PATH_CHILDREN = "children";
743     private static final String PATH_SEARCH = "search";
744     private static final String PATH_TREE = "tree";
745 
746     private static final String PARAM_QUERY = "query";
747     private static final String PARAM_MANAGE = "manage";
748 
749     /**
750      * Build URI representing the roots of a document provider. When queried, a
751      * provider will return one or more rows with columns defined by
752      * {@link Root}.
753      *
754      * @see DocumentsProvider#queryRoots(String[])
755      */
buildRootsUri(String authority)756     public static Uri buildRootsUri(String authority) {
757         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
758                 .authority(authority).appendPath(PATH_ROOT).build();
759     }
760 
761     /**
762      * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a
763      * document provider.
764      *
765      * @see #getRootId(Uri)
766      */
buildRootUri(String authority, String rootId)767     public static Uri buildRootUri(String authority, String rootId) {
768         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
769                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
770     }
771 
772     /**
773      * Builds URI for user home directory on external (local) storage.
774      * {@hide}
775      */
buildHomeUri()776     public static Uri buildHomeUri() {
777         // TODO: Avoid this type of interpackage copying. Added here to avoid
778         // direct coupling, but not ideal.
779         return DocumentsContract.buildRootUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "home");
780     }
781 
782     /**
783      * Build URI representing the recently modified documents of a specific root
784      * in a document provider. When queried, a provider will return zero or more
785      * rows with columns defined by {@link Document}.
786      *
787      * @see DocumentsProvider#queryRecentDocuments(String, String[])
788      * @see #getRootId(Uri)
789      */
buildRecentDocumentsUri(String authority, String rootId)790     public static Uri buildRecentDocumentsUri(String authority, String rootId) {
791         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
792                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
793                 .appendPath(PATH_RECENT).build();
794     }
795 
796     /**
797      * Build URI representing access to descendant documents of the given
798      * {@link Document#COLUMN_DOCUMENT_ID}.
799      *
800      * @see #getTreeDocumentId(Uri)
801      */
buildTreeDocumentUri(String authority, String documentId)802     public static Uri buildTreeDocumentUri(String authority, String documentId) {
803         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
804                 .appendPath(PATH_TREE).appendPath(documentId).build();
805     }
806 
807     /**
808      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
809      * a document provider. When queried, a provider will return a single row
810      * with columns defined by {@link Document}.
811      *
812      * @see DocumentsProvider#queryDocument(String, String[])
813      * @see #getDocumentId(Uri)
814      */
buildDocumentUri(String authority, String documentId)815     public static Uri buildDocumentUri(String authority, String documentId) {
816         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
817                 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
818     }
819 
820     /**
821      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
822      * a document provider. When queried, a provider will return a single row
823      * with columns defined by {@link Document}.
824      * <p>
825      * However, instead of directly accessing the target document, the returned
826      * URI will leverage access granted through a subtree URI, typically
827      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document
828      * must be a descendant (child, grandchild, etc) of the subtree.
829      * <p>
830      * This is typically used to access documents under a user-selected
831      * directory tree, since it doesn't require the user to separately confirm
832      * each new document access.
833      *
834      * @param treeUri the subtree to leverage to gain access to the target
835      *            document. The target directory must be a descendant of this
836      *            subtree.
837      * @param documentId the target document, which the caller may not have
838      *            direct access to.
839      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
840      * @see DocumentsProvider#isChildDocument(String, String)
841      * @see #buildDocumentUri(String, String)
842      */
buildDocumentUriUsingTree(Uri treeUri, String documentId)843     public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
844         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
845                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
846                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
847                 .appendPath(documentId).build();
848     }
849 
850     /** {@hide} */
buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId)851     public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) {
852         if (isTreeUri(baseUri)) {
853             return buildDocumentUriUsingTree(baseUri, documentId);
854         } else {
855             return buildDocumentUri(baseUri.getAuthority(), documentId);
856         }
857     }
858 
859     /**
860      * Build URI representing the children of the target directory in a document
861      * provider. When queried, a provider will return zero or more rows with
862      * columns defined by {@link Document}.
863      *
864      * @param parentDocumentId the document to return children for, which must
865      *            be a directory with MIME type of
866      *            {@link Document#MIME_TYPE_DIR}.
867      * @see DocumentsProvider#queryChildDocuments(String, String[], String)
868      * @see #getDocumentId(Uri)
869      */
buildChildDocumentsUri(String authority, String parentDocumentId)870     public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
871         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
872                 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
873                 .build();
874     }
875 
876     /**
877      * Build URI representing the children of the target directory in a document
878      * provider. When queried, a provider will return zero or more rows with
879      * columns defined by {@link Document}.
880      * <p>
881      * However, instead of directly accessing the target directory, the returned
882      * URI will leverage access granted through a subtree URI, typically
883      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target
884      * directory must be a descendant (child, grandchild, etc) of the subtree.
885      * <p>
886      * This is typically used to access documents under a user-selected
887      * directory tree, since it doesn't require the user to separately confirm
888      * each new document access.
889      *
890      * @param treeUri the subtree to leverage to gain access to the target
891      *            document. The target directory must be a descendant of this
892      *            subtree.
893      * @param parentDocumentId the document to return children for, which the
894      *            caller may not have direct access to, and which must be a
895      *            directory with MIME type of {@link Document#MIME_TYPE_DIR}.
896      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
897      * @see DocumentsProvider#isChildDocument(String, String)
898      * @see #buildChildDocumentsUri(String, String)
899      */
buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId)900     public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) {
901         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
902                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
903                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
904                 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
905     }
906 
907     /**
908      * Build URI representing a search for matching documents under a specific
909      * root in a document provider. When queried, a provider will return zero or
910      * more rows with columns defined by {@link Document}.
911      *
912      * @see DocumentsProvider#querySearchDocuments(String, String, String[])
913      * @see #getRootId(Uri)
914      * @see #getSearchDocumentsQuery(Uri)
915      */
buildSearchDocumentsUri( String authority, String rootId, String query)916     public static Uri buildSearchDocumentsUri(
917             String authority, String rootId, String query) {
918         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
919                 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
920                 .appendQueryParameter(PARAM_QUERY, query).build();
921     }
922 
923     /**
924      * Test if the given URI represents a {@link Document} backed by a
925      * {@link DocumentsProvider}.
926      *
927      * @see #buildDocumentUri(String, String)
928      * @see #buildDocumentUriUsingTree(Uri, String)
929      */
isDocumentUri(Context context, @Nullable Uri uri)930     public static boolean isDocumentUri(Context context, @Nullable Uri uri) {
931         if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
932             final List<String> paths = uri.getPathSegments();
933             if (paths.size() == 2) {
934                 return PATH_DOCUMENT.equals(paths.get(0));
935             } else if (paths.size() == 4) {
936                 return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));
937             }
938         }
939         return false;
940     }
941 
942     /** {@hide} */
isRootUri(Context context, @Nullable Uri uri)943     public static boolean isRootUri(Context context, @Nullable Uri uri) {
944         if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
945             final List<String> paths = uri.getPathSegments();
946             return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0)));
947         }
948         return false;
949     }
950 
951     /** {@hide} */
isContentUri(@ullable Uri uri)952     public static boolean isContentUri(@Nullable Uri uri) {
953         return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme());
954     }
955 
956     /**
957      * Test if the given URI represents a {@link Document} tree.
958      *
959      * @see #buildTreeDocumentUri(String, String)
960      * @see #getTreeDocumentId(Uri)
961      */
isTreeUri(Uri uri)962     public static boolean isTreeUri(Uri uri) {
963         final List<String> paths = uri.getPathSegments();
964         return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
965     }
966 
isDocumentsProvider(Context context, String authority)967     private static boolean isDocumentsProvider(Context context, String authority) {
968         final Intent intent = new Intent(PROVIDER_INTERFACE);
969         final List<ResolveInfo> infos = context.getPackageManager()
970                 .queryIntentContentProviders(intent, 0);
971         for (ResolveInfo info : infos) {
972             if (authority.equals(info.providerInfo.authority)) {
973                 return true;
974             }
975         }
976         return false;
977     }
978 
979     /**
980      * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI.
981      */
getRootId(Uri rootUri)982     public static String getRootId(Uri rootUri) {
983         final List<String> paths = rootUri.getPathSegments();
984         if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
985             return paths.get(1);
986         }
987         throw new IllegalArgumentException("Invalid URI: " + rootUri);
988     }
989 
990     /**
991      * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
992      *
993      * @see #isDocumentUri(Context, Uri)
994      */
getDocumentId(Uri documentUri)995     public static String getDocumentId(Uri documentUri) {
996         final List<String> paths = documentUri.getPathSegments();
997         if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
998             return paths.get(1);
999         }
1000         if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0))
1001                 && PATH_DOCUMENT.equals(paths.get(2))) {
1002             return paths.get(3);
1003         }
1004         throw new IllegalArgumentException("Invalid URI: " + documentUri);
1005     }
1006 
1007     /**
1008      * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
1009      */
getTreeDocumentId(Uri documentUri)1010     public static String getTreeDocumentId(Uri documentUri) {
1011         final List<String> paths = documentUri.getPathSegments();
1012         if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
1013             return paths.get(1);
1014         }
1015         throw new IllegalArgumentException("Invalid URI: " + documentUri);
1016     }
1017 
1018     /**
1019      * Extract the search query from a URI built by
1020      * {@link #buildSearchDocumentsUri(String, String, String)}.
1021      */
getSearchDocumentsQuery(Uri searchDocumentsUri)1022     public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
1023         return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
1024     }
1025 
1026     /** {@hide} */
setManageMode(Uri uri)1027     public static Uri setManageMode(Uri uri) {
1028         return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
1029     }
1030 
1031     /** {@hide} */
isManageMode(Uri uri)1032     public static boolean isManageMode(Uri uri) {
1033         return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
1034     }
1035 
1036     /**
1037      * Return thumbnail representing the document at the given URI. Callers are
1038      * responsible for their own in-memory caching.
1039      *
1040      * @param documentUri document to return thumbnail for, which must have
1041      *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
1042      * @param size optimal thumbnail size desired. A provider may return a
1043      *            thumbnail of a different size, but never more than double the
1044      *            requested size.
1045      * @param signal signal used to indicate if caller is no longer interested
1046      *            in the thumbnail.
1047      * @return decoded thumbnail, or {@code null} if problem was encountered.
1048      * @see DocumentsProvider#openDocumentThumbnail(String, Point,
1049      *      android.os.CancellationSignal)
1050      */
getDocumentThumbnail( ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal)1051     public static Bitmap getDocumentThumbnail(
1052             ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal)
1053             throws FileNotFoundException {
1054         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1055                 documentUri.getAuthority());
1056         try {
1057             return getDocumentThumbnail(client, documentUri, size, signal);
1058         } catch (Exception e) {
1059             if (!(e instanceof OperationCanceledException)) {
1060                 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
1061             }
1062             rethrowIfNecessary(resolver, e);
1063             return null;
1064         } finally {
1065             ContentProviderClient.releaseQuietly(client);
1066         }
1067     }
1068 
1069     /** {@hide} */
getDocumentThumbnail( ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)1070     public static Bitmap getDocumentThumbnail(
1071             ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
1072             throws RemoteException, IOException {
1073         final Bundle openOpts = new Bundle();
1074         openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size);
1075 
1076         AssetFileDescriptor afd = null;
1077         Bitmap bitmap = null;
1078         try {
1079             afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
1080 
1081             final FileDescriptor fd = afd.getFileDescriptor();
1082             final long offset = afd.getStartOffset();
1083 
1084             // Try seeking on the returned FD, since it gives us the most
1085             // optimal decode path; otherwise fall back to buffering.
1086             BufferedInputStream is = null;
1087             try {
1088                 Os.lseek(fd, offset, SEEK_SET);
1089             } catch (ErrnoException e) {
1090                 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
1091                 is.mark(THUMBNAIL_BUFFER_SIZE);
1092             }
1093 
1094             // We requested a rough thumbnail size, but the remote size may have
1095             // returned something giant, so defensively scale down as needed.
1096             final BitmapFactory.Options opts = new BitmapFactory.Options();
1097             opts.inJustDecodeBounds = true;
1098             if (is != null) {
1099                 BitmapFactory.decodeStream(is, null, opts);
1100             } else {
1101                 BitmapFactory.decodeFileDescriptor(fd, null, opts);
1102             }
1103 
1104             final int widthSample = opts.outWidth / size.x;
1105             final int heightSample = opts.outHeight / size.y;
1106 
1107             opts.inJustDecodeBounds = false;
1108             opts.inSampleSize = Math.min(widthSample, heightSample);
1109             if (is != null) {
1110                 is.reset();
1111                 bitmap = BitmapFactory.decodeStream(is, null, opts);
1112             } else {
1113                 try {
1114                     Os.lseek(fd, offset, SEEK_SET);
1115                 } catch (ErrnoException e) {
1116                     e.rethrowAsIOException();
1117                 }
1118                 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
1119             }
1120 
1121             // Transform the bitmap if requested. We use a side-channel to
1122             // communicate the orientation, since EXIF thumbnails don't contain
1123             // the rotation flags of the original image.
1124             final Bundle extras = afd.getExtras();
1125             final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
1126             if (orientation != 0) {
1127                 final int width = bitmap.getWidth();
1128                 final int height = bitmap.getHeight();
1129 
1130                 final Matrix m = new Matrix();
1131                 m.setRotate(orientation, width / 2, height / 2);
1132                 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
1133             }
1134         } finally {
1135             IoUtils.closeQuietly(afd);
1136         }
1137 
1138         return bitmap;
1139     }
1140 
1141     /**
1142      * Create a new document with given MIME type and display name.
1143      *
1144      * @param parentDocumentUri directory with {@link Document#FLAG_DIR_SUPPORTS_CREATE}
1145      * @param mimeType MIME type of new document
1146      * @param displayName name of new document
1147      * @return newly created document, or {@code null} if failed
1148      */
createDocument(ContentResolver resolver, Uri parentDocumentUri, String mimeType, String displayName)1149     public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
1150             String mimeType, String displayName) throws FileNotFoundException {
1151         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1152                 parentDocumentUri.getAuthority());
1153         try {
1154             return createDocument(client, parentDocumentUri, mimeType, displayName);
1155         } catch (Exception e) {
1156             Log.w(TAG, "Failed to create document", e);
1157             rethrowIfNecessary(resolver, e);
1158             return null;
1159         } finally {
1160             ContentProviderClient.releaseQuietly(client);
1161         }
1162     }
1163 
1164     /** {@hide} */
createDocument(ContentProviderClient client, Uri parentDocumentUri, String mimeType, String displayName)1165     public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
1166             String mimeType, String displayName) throws RemoteException {
1167         final Bundle in = new Bundle();
1168         in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
1169         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
1170         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
1171 
1172         final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
1173         return out.getParcelable(DocumentsContract.EXTRA_URI);
1174     }
1175 
1176     /** {@hide} */
isChildDocument(ContentProviderClient client, Uri parentDocumentUri, Uri childDocumentUri)1177     public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
1178             Uri childDocumentUri) throws RemoteException {
1179 
1180         final Bundle in = new Bundle();
1181         in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
1182         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
1183 
1184         final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
1185         if (out == null) {
1186             throw new RemoteException("Failed to get a reponse from isChildDocument query.");
1187         }
1188         if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
1189             throw new RemoteException("Response did not include result field..");
1190         }
1191         return out.getBoolean(DocumentsContract.EXTRA_RESULT);
1192     }
1193 
1194     /**
1195      * Change the display name of an existing document.
1196      * <p>
1197      * If the underlying provider needs to create a new
1198      * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display
1199      * name, that new document is returned and the original document is no
1200      * longer valid. Otherwise, the original document is returned.
1201      *
1202      * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME}
1203      * @param displayName updated name for document
1204      * @return the existing or new document after the rename, or {@code null} if
1205      *         failed.
1206      */
renameDocument(ContentResolver resolver, Uri documentUri, String displayName)1207     public static Uri renameDocument(ContentResolver resolver, Uri documentUri,
1208             String displayName) throws FileNotFoundException {
1209         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1210                 documentUri.getAuthority());
1211         try {
1212             return renameDocument(client, documentUri, displayName);
1213         } catch (Exception e) {
1214             Log.w(TAG, "Failed to rename document", e);
1215             rethrowIfNecessary(resolver, e);
1216             return null;
1217         } finally {
1218             ContentProviderClient.releaseQuietly(client);
1219         }
1220     }
1221 
1222     /** {@hide} */
renameDocument(ContentProviderClient client, Uri documentUri, String displayName)1223     public static Uri renameDocument(ContentProviderClient client, Uri documentUri,
1224             String displayName) throws RemoteException {
1225         final Bundle in = new Bundle();
1226         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1227         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
1228 
1229         final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
1230         final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
1231         return (outUri != null) ? outUri : documentUri;
1232     }
1233 
1234     /**
1235      * Delete the given document.
1236      *
1237      * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
1238      * @return if the document was deleted successfully.
1239      */
deleteDocument(ContentResolver resolver, Uri documentUri)1240     public static boolean deleteDocument(ContentResolver resolver, Uri documentUri)
1241             throws FileNotFoundException {
1242         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1243                 documentUri.getAuthority());
1244         try {
1245             deleteDocument(client, documentUri);
1246             return true;
1247         } catch (Exception e) {
1248             Log.w(TAG, "Failed to delete document", e);
1249             rethrowIfNecessary(resolver, e);
1250             return false;
1251         } finally {
1252             ContentProviderClient.releaseQuietly(client);
1253         }
1254     }
1255 
1256     /** {@hide} */
deleteDocument(ContentProviderClient client, Uri documentUri)1257     public static void deleteDocument(ContentProviderClient client, Uri documentUri)
1258             throws RemoteException {
1259         final Bundle in = new Bundle();
1260         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1261 
1262         client.call(METHOD_DELETE_DOCUMENT, null, in);
1263     }
1264 
1265     /**
1266      * Copies the given document.
1267      *
1268      * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_COPY}
1269      * @param targetParentDocumentUri document which will become a parent of the source
1270      *         document's copy.
1271      * @return the copied document, or {@code null} if failed.
1272      */
copyDocument(ContentResolver resolver, Uri sourceDocumentUri, Uri targetParentDocumentUri)1273     public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri,
1274             Uri targetParentDocumentUri) throws FileNotFoundException {
1275         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1276                 sourceDocumentUri.getAuthority());
1277         try {
1278             return copyDocument(client, sourceDocumentUri, targetParentDocumentUri);
1279         } catch (Exception e) {
1280             Log.w(TAG, "Failed to copy document", e);
1281             rethrowIfNecessary(resolver, e);
1282             return null;
1283         } finally {
1284             ContentProviderClient.releaseQuietly(client);
1285         }
1286     }
1287 
1288     /** {@hide} */
copyDocument(ContentProviderClient client, Uri sourceDocumentUri, Uri targetParentDocumentUri)1289     public static Uri copyDocument(ContentProviderClient client, Uri sourceDocumentUri,
1290             Uri targetParentDocumentUri) throws RemoteException {
1291         final Bundle in = new Bundle();
1292         in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
1293         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
1294 
1295         final Bundle out = client.call(METHOD_COPY_DOCUMENT, null, in);
1296         return out.getParcelable(DocumentsContract.EXTRA_URI);
1297     }
1298 
1299     /**
1300      * Moves the given document under a new parent.
1301      *
1302      * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_MOVE}
1303      * @param sourceParentDocumentUri parent document of the document to move.
1304      * @param targetParentDocumentUri document which will become a new parent of the source
1305      *         document.
1306      * @return the moved document, or {@code null} if failed.
1307      */
moveDocument(ContentResolver resolver, Uri sourceDocumentUri, Uri sourceParentDocumentUri, Uri targetParentDocumentUri)1308     public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri,
1309             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException {
1310         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1311                 sourceDocumentUri.getAuthority());
1312         try {
1313             return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri,
1314                     targetParentDocumentUri);
1315         } catch (Exception e) {
1316             Log.w(TAG, "Failed to move document", e);
1317             rethrowIfNecessary(resolver, e);
1318             return null;
1319         } finally {
1320             ContentProviderClient.releaseQuietly(client);
1321         }
1322     }
1323 
1324     /** {@hide} */
moveDocument(ContentProviderClient client, Uri sourceDocumentUri, Uri sourceParentDocumentUri, Uri targetParentDocumentUri)1325     public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri,
1326             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws RemoteException {
1327         final Bundle in = new Bundle();
1328         in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
1329         in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri);
1330         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
1331 
1332         final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in);
1333         return out.getParcelable(DocumentsContract.EXTRA_URI);
1334     }
1335 
1336     /**
1337      * Removes the given document from a parent directory.
1338      *
1339      * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
1340      * This method is especially useful if the document can be in multiple parents.
1341      *
1342      * @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE}
1343      * @param parentDocumentUri parent document of the document to remove.
1344      * @return true if the document was removed successfully.
1345      */
removeDocument(ContentResolver resolver, Uri documentUri, Uri parentDocumentUri)1346     public static boolean removeDocument(ContentResolver resolver, Uri documentUri,
1347             Uri parentDocumentUri) throws FileNotFoundException {
1348         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1349                 documentUri.getAuthority());
1350         try {
1351             removeDocument(client, documentUri, parentDocumentUri);
1352             return true;
1353         } catch (Exception e) {
1354             Log.w(TAG, "Failed to remove document", e);
1355             rethrowIfNecessary(resolver, e);
1356             return false;
1357         } finally {
1358             ContentProviderClient.releaseQuietly(client);
1359         }
1360     }
1361 
1362     /** {@hide} */
removeDocument(ContentProviderClient client, Uri documentUri, Uri parentDocumentUri)1363     public static void removeDocument(ContentProviderClient client, Uri documentUri,
1364             Uri parentDocumentUri) throws RemoteException {
1365         final Bundle in = new Bundle();
1366         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1367         in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri);
1368 
1369         client.call(METHOD_REMOVE_DOCUMENT, null, in);
1370     }
1371 
1372     /**
1373      * Ejects the given root. It throws {@link IllegalStateException} when ejection failed.
1374      *
1375      * @param rootUri root with {@link Root#FLAG_SUPPORTS_EJECT} to be ejected
1376      */
ejectRoot(ContentResolver resolver, Uri rootUri)1377     public static void ejectRoot(ContentResolver resolver, Uri rootUri) {
1378         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1379                 rootUri.getAuthority());
1380         try {
1381             ejectRoot(client, rootUri);
1382         } catch (RemoteException e) {
1383             e.rethrowAsRuntimeException();
1384         } finally {
1385             ContentProviderClient.releaseQuietly(client);
1386         }
1387     }
1388 
1389     /** {@hide} */
ejectRoot(ContentProviderClient client, Uri rootUri)1390     public static void ejectRoot(ContentProviderClient client, Uri rootUri)
1391             throws RemoteException {
1392         final Bundle in = new Bundle();
1393         in.putParcelable(DocumentsContract.EXTRA_URI, rootUri);
1394 
1395         client.call(METHOD_EJECT_ROOT, null, in);
1396     }
1397 
1398     /**
1399      * Returns metadata associated with the document. The type of metadata returned
1400      * is specific to the document type. For example image files will largely return EXIF
1401      * metadata.
1402      *
1403      * <p>The returned {@link Bundle} will contain zero or more entries.
1404      * <p>Each entry represents a specific type of metadata.
1405      *
1406      * <p>if tags == null, then a list of default tags will be used.
1407      *
1408      * @param documentUri a Document URI
1409      * @param tags an array of keys to choose which data are added to the Bundle. If the Document
1410      *             is a JPG or ExifInterface compatible, send keys from {@link ExifInterface}.
1411      *             If tags are null, a set of default tags will be used. If the tags don't
1412      *             match with any relevant data, they will not be added to the Bundle.
1413      * @return a Bundle of Bundles. If metadata exists within the Bundle, there will also
1414      * be a String under DocumentsContract.METADATA_TYPES that will return a String[] of the
1415      * types of metadata gathered.
1416      *
1417      * <pre><code>
1418      *     Bundle metadata = DocumentsContract.getDocumentMetadata(resolver, imageDocUri, tags);
1419      *     int imageLength = metadata.getInt(ExifInterface.TAG_IMAGE_LENGTH);
1420      * </code></pre>
1421      *
1422      * {@hide}
1423      */
getDocumentMetadata(ContentResolver resolver, Uri documentUri, @Nullable String[] tags)1424     public static Bundle getDocumentMetadata(ContentResolver resolver, Uri documentUri,
1425             @Nullable String[] tags)
1426             throws FileNotFoundException {
1427         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1428                 documentUri.getAuthority());
1429 
1430         try {
1431             return getDocumentMetadata(client, documentUri, tags);
1432         } catch (Exception e) {
1433             Log.w(TAG, "Failed to get document metadata");
1434             rethrowIfNecessary(resolver, e);
1435             return null;
1436         } finally {
1437             ContentProviderClient.releaseQuietly(client);
1438         }
1439     }
1440 
1441     /**
1442      * Returns metadata associated with the document. The type of metadata returned
1443      * is specific to the document type. For example image files will largely return EXIF
1444      * metadata.
1445      *
1446      * <p>The returned {@link Bundle} will contain zero or more entries.
1447      * <p>Each entry represents a specific type of metadata.
1448      *
1449      * <p>if tags == null, then a list of default tags will be used.
1450      *
1451      * @param documentUri a Document URI
1452      * @param tags an array of keys to choose which data are added to the Bundle. If the Document
1453      *             is a JPG or ExifInterface compatible, send keys from {@link ExifInterface}.
1454      *             If tags are null, a set of default tags will be used. If the tags don't
1455      *             match with any relevant data, they will not be added to the Bundle.
1456      * @return a Bundle of Bundles. If metadata exists within the Bundle, there will also
1457      * be a String under DocumentsContract.METADATA_TYPES that will return a String[] of the
1458      * types of metadata gathered.
1459      *
1460      * <pre><code>
1461      *     Bundle metadata = DocumentsContract.getDocumentMetadata(client, imageDocUri, tags);
1462      *     int imageLength = metadata.getInt(ExifInterface.TAG_IMAGE_LENGTH);
1463      * </code></pre>
1464      *
1465      * {@hide}
1466      */
getDocumentMetadata(ContentProviderClient client, Uri documentUri, @Nullable String[] tags)1467     public static Bundle getDocumentMetadata(ContentProviderClient client,
1468             Uri documentUri, @Nullable String[] tags) throws RemoteException {
1469         final Bundle in = new Bundle();
1470         in.putParcelable(EXTRA_URI, documentUri);
1471         in.putStringArray(EXTRA_METADATA_TAGS, tags);
1472 
1473         final Bundle out = client.call(METHOD_GET_DOCUMENT_METADATA, null, in);
1474 
1475         if (out == null) {
1476             throw new RemoteException("Failed to get a response from getDocumentMetadata");
1477         }
1478         return out;
1479     }
1480 
1481     /**
1482      * Finds the canonical path from the top of the document tree.
1483      *
1484      * The {@link Path#getPath()} of the return value contains the document ID
1485      * of all documents along the path from the top the document tree to the
1486      * requested document, both inclusive.
1487      *
1488      * The {@link Path#getRootId()} of the return value returns {@code null}.
1489      *
1490      * @param treeUri treeUri of the document which path is requested.
1491      * @return the path of the document, or {@code null} if failed.
1492      * @see DocumentsProvider#findDocumentPath(String, String)
1493      */
findDocumentPath(ContentResolver resolver, Uri treeUri)1494     public static Path findDocumentPath(ContentResolver resolver, Uri treeUri)
1495             throws FileNotFoundException {
1496         checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
1497 
1498         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1499                 treeUri.getAuthority());
1500         try {
1501             return findDocumentPath(client, treeUri);
1502         } catch (Exception e) {
1503             Log.w(TAG, "Failed to find path", e);
1504             rethrowIfNecessary(resolver, e);
1505             return null;
1506         } finally {
1507             ContentProviderClient.releaseQuietly(client);
1508         }
1509     }
1510 
1511     /**
1512      * Finds the canonical path. If uri is a document uri returns path from a root and
1513      * its associated root id. If uri is a tree uri returns the path from the top of
1514      * the tree. The {@link Path#getPath()} of the return value contains document ID
1515      * starts from the top of the tree or the root document to the requested document,
1516      * both inclusive.
1517      *
1518      * Callers can expect the root ID returned from multiple calls to this method is
1519      * consistent.
1520      *
1521      * @param uri uri of the document which path is requested. It can be either a
1522      *          plain document uri or a tree uri.
1523      * @return the path of the document.
1524      * @see DocumentsProvider#findDocumentPath(String, String)
1525      *
1526      * {@hide}
1527      */
findDocumentPath(ContentProviderClient client, Uri uri)1528     public static Path findDocumentPath(ContentProviderClient client, Uri uri)
1529             throws RemoteException {
1530 
1531         final Bundle in = new Bundle();
1532         in.putParcelable(DocumentsContract.EXTRA_URI, uri);
1533 
1534         final Bundle out = client.call(METHOD_FIND_DOCUMENT_PATH, null, in);
1535 
1536         return out.getParcelable(DocumentsContract.EXTRA_RESULT);
1537     }
1538 
1539     /**
1540      * Creates an intent for obtaining a web link for the specified document.
1541      *
1542      * <p>Note, that due to internal limitations, if there is already a web link
1543      * intent created for the specified document but with different options,
1544      * then it may be overriden.
1545      *
1546      * <p>Providers are required to show confirmation UI for all new permissions granted
1547      * for the linked document.
1548      *
1549      * <p>If list of recipients is known, then it should be passed in options as
1550      * {@link Intent#EXTRA_EMAIL} as a list of email addresses. Note, that
1551      * this is just a hint for the provider, which can ignore the list. In either
1552      * case the provider is required to show a UI for letting the user confirm
1553      * any new permission grants.
1554      *
1555      * <p>Note, that the entire <code>options</code> bundle will be sent to the provider
1556      * backing the passed <code>uri</code>. Make sure that you trust the provider
1557      * before passing any sensitive information.
1558      *
1559      * <p>Since this API may show a UI, it cannot be called from background.
1560      *
1561      * <p>In order to obtain the Web Link use code like this:
1562      * <pre><code>
1563      * void onSomethingHappened() {
1564      *   IntentSender sender = DocumentsContract.createWebLinkIntent(<i>...</i>);
1565      *   if (sender != null) {
1566      *     startIntentSenderForResult(
1567      *         sender,
1568      *         WEB_LINK_REQUEST_CODE,
1569      *         null, 0, 0, 0, null);
1570      *   }
1571      * }
1572      *
1573      * <i>(...)</i>
1574      *
1575      * void onActivityResult(int requestCode, int resultCode, Intent data) {
1576      *   if (requestCode == WEB_LINK_REQUEST_CODE && resultCode == RESULT_OK) {
1577      *     Uri weblinkUri = data.getData();
1578      *     <i>...</i>
1579      *   }
1580      * }
1581      * </code></pre>
1582      *
1583      * @param uri uri for the document to create a link to.
1584      * @param options Extra information for generating the link.
1585      * @return an intent sender to obtain the web link, or null if the document
1586      *      is not linkable, or creating the intent sender failed.
1587      * @see DocumentsProvider#createWebLinkIntent(String, Bundle)
1588      * @see Intent#EXTRA_EMAIL
1589      */
createWebLinkIntent(ContentResolver resolver, Uri uri, Bundle options)1590     public static IntentSender createWebLinkIntent(ContentResolver resolver, Uri uri,
1591             Bundle options) throws FileNotFoundException {
1592         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1593                 uri.getAuthority());
1594         try {
1595             return createWebLinkIntent(client, uri, options);
1596         } catch (Exception e) {
1597             Log.w(TAG, "Failed to create a web link intent", e);
1598             rethrowIfNecessary(resolver, e);
1599             return null;
1600         } finally {
1601             ContentProviderClient.releaseQuietly(client);
1602         }
1603     }
1604 
1605     /**
1606      * {@hide}
1607      */
createWebLinkIntent(ContentProviderClient client, Uri uri, Bundle options)1608     public static IntentSender createWebLinkIntent(ContentProviderClient client, Uri uri,
1609             Bundle options) throws RemoteException {
1610         final Bundle in = new Bundle();
1611         in.putParcelable(DocumentsContract.EXTRA_URI, uri);
1612 
1613         // Options may be provider specific, so put them in a separate bundle to
1614         // avoid overriding the Uri.
1615         if (options != null) {
1616             in.putBundle(EXTRA_OPTIONS, options);
1617         }
1618 
1619         final Bundle out = client.call(METHOD_CREATE_WEB_LINK_INTENT, null, in);
1620         return out.getParcelable(DocumentsContract.EXTRA_RESULT);
1621     }
1622 
1623     /**
1624      * Open the given image for thumbnail purposes, using any embedded EXIF
1625      * thumbnail if available, and providing orientation hints from the parent
1626      * image.
1627      *
1628      * @hide
1629      */
openImageThumbnail(File file)1630     public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
1631         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
1632                 file, ParcelFileDescriptor.MODE_READ_ONLY);
1633         Bundle extras = null;
1634 
1635         try {
1636             final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
1637 
1638             switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
1639                 case ExifInterface.ORIENTATION_ROTATE_90:
1640                     extras = new Bundle(1);
1641                     extras.putInt(EXTRA_ORIENTATION, 90);
1642                     break;
1643                 case ExifInterface.ORIENTATION_ROTATE_180:
1644                     extras = new Bundle(1);
1645                     extras.putInt(EXTRA_ORIENTATION, 180);
1646                     break;
1647                 case ExifInterface.ORIENTATION_ROTATE_270:
1648                     extras = new Bundle(1);
1649                     extras.putInt(EXTRA_ORIENTATION, 270);
1650                     break;
1651             }
1652 
1653             final long[] thumb = exif.getThumbnailRange();
1654             if (thumb != null) {
1655                 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
1656             }
1657         } catch (IOException e) {
1658         }
1659 
1660         return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
1661     }
1662 
rethrowIfNecessary(ContentResolver resolver, Exception e)1663     private static void rethrowIfNecessary(ContentResolver resolver, Exception e)
1664             throws FileNotFoundException {
1665         // We only want to throw applications targetting O and above
1666         if (resolver.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
1667             if (e instanceof ParcelableException) {
1668                 ((ParcelableException) e).maybeRethrow(FileNotFoundException.class);
1669             } else if (e instanceof RemoteException) {
1670                 ((RemoteException) e).rethrowAsRuntimeException();
1671             } else if (e instanceof RuntimeException) {
1672                 throw (RuntimeException) e;
1673             }
1674         }
1675     }
1676 
1677     /**
1678      * Holds a path from a document to a particular document under it. It
1679      * may also contains the root ID where the path resides.
1680      */
1681     public static final class Path implements Parcelable {
1682 
1683         private final @Nullable String mRootId;
1684         private final List<String> mPath;
1685 
1686         /**
1687          * Creates a Path.
1688          *
1689          * @param rootId the ID of the root. May be null.
1690          * @param path the list of document ID from the parent document at
1691          *          position 0 to the child document.
1692          */
Path(@ullable String rootId, List<String> path)1693         public Path(@Nullable String rootId, List<String> path) {
1694             checkCollectionNotEmpty(path, "path");
1695             checkCollectionElementsNotNull(path, "path");
1696 
1697             mRootId = rootId;
1698             mPath = path;
1699         }
1700 
1701         /**
1702          * Returns the root id or null if the calling package doesn't have
1703          * permission to access root information.
1704          */
getRootId()1705         public @Nullable String getRootId() {
1706             return mRootId;
1707         }
1708 
1709         /**
1710          * Returns the path. The path is trimmed to the top of tree if
1711          * calling package doesn't have permission to access those
1712          * documents.
1713          */
getPath()1714         public List<String> getPath() {
1715             return mPath;
1716         }
1717 
1718         @Override
equals(Object o)1719         public boolean equals(Object o) {
1720             if (this == o) {
1721                 return true;
1722             }
1723             if (o == null || !(o instanceof Path)) {
1724                 return false;
1725             }
1726             Path path = (Path) o;
1727             return Objects.equals(mRootId, path.mRootId) &&
1728                     Objects.equals(mPath, path.mPath);
1729         }
1730 
1731         @Override
hashCode()1732         public int hashCode() {
1733             return Objects.hash(mRootId, mPath);
1734         }
1735 
1736         @Override
toString()1737         public String toString() {
1738             return new StringBuilder()
1739                     .append("DocumentsContract.Path{")
1740                     .append("rootId=")
1741                     .append(mRootId)
1742                     .append(", path=")
1743                     .append(mPath)
1744                     .append("}")
1745                     .toString();
1746         }
1747 
1748         @Override
writeToParcel(Parcel dest, int flags)1749         public void writeToParcel(Parcel dest, int flags) {
1750             dest.writeString(mRootId);
1751             dest.writeStringList(mPath);
1752         }
1753 
1754         @Override
describeContents()1755         public int describeContents() {
1756             return 0;
1757         }
1758 
1759         public static final Creator<Path> CREATOR = new Creator<Path>() {
1760             @Override
1761             public Path createFromParcel(Parcel in) {
1762                 final String rootId = in.readString();
1763                 final List<String> path = in.createStringArrayList();
1764                 return new Path(rootId, path);
1765             }
1766 
1767             @Override
1768             public Path[] newArray(int size) {
1769                 return new Path[size];
1770             }
1771         };
1772     }
1773 }
1774