• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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 androidx.documentfile.provider;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.net.Uri;
23 import android.os.Build;
24 import android.provider.DocumentsContract;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 
29 import java.io.File;
30 
31 /**
32  * Representation of a document backed by either a
33  * {@link android.provider.DocumentsProvider} or a raw file on disk. This is a
34  * utility class designed to emulate the traditional {@link File} interface. It
35  * offers a simplified view of a tree of documents, but it has substantial
36  * overhead. For optimal performance and a richer feature set, use the
37  * {@link android.provider.DocumentsContract} methods and constants directly.
38  * <p>
39  * There are several differences between documents and traditional files:
40  * <ul>
41  * <li>Documents express their display name and MIME type as separate fields,
42  * instead of relying on file extensions. Some documents providers may still
43  * choose to append extensions to their display names, but that's an
44  * implementation detail.
45  * <li>A single document may appear as the child of multiple directories, so it
46  * doesn't inherently know who its parent is. That is, documents don't have a
47  * strong notion of path. You can easily traverse a tree of documents from
48  * parent to child, but not from child to parent.
49  * <li>Each document has a unique identifier within that provider. This
50  * identifier is an <em>opaque</em> implementation detail of the provider, and
51  * as such it must not be parsed.
52  * </ul>
53  * <p>
54  * Before using this class, first consider if you really need access to an
55  * entire subtree of documents. The principle of least privilege dictates that
56  * you should only ask for access to documents you really need. If you only need
57  * the user to pick a single file, use {@link Intent#ACTION_OPEN_DOCUMENT} or
58  * {@link Intent#ACTION_GET_CONTENT}. If you want to let the user pick multiple
59  * files, add {@link Intent#EXTRA_ALLOW_MULTIPLE}. If you only need the user to
60  * save a single file, use {@link Intent#ACTION_CREATE_DOCUMENT}. If you use
61  * these APIs, you can pass the resulting {@link Intent#getData()} into
62  * {@link #fromSingleUri(Context, Uri)} to work with that document.
63  * <p>
64  * If you really do need full access to an entire subtree of documents, start by
65  * launching {@link Intent#ACTION_OPEN_DOCUMENT_TREE} to let the user pick a
66  * directory. Then pass the resulting {@link Intent#getData()} into
67  * {@link #fromTreeUri(Context, Uri)} to start working with the user selected
68  * tree.
69  * <p>
70  * As you navigate the tree of DocumentFile instances, you can always use
71  * {@link #getUri()} to obtain the Uri representing the underlying document for
72  * that object, for use with {@link ContentResolver#openInputStream(Uri)}, etc.
73  * <p>
74  * To simplify your code on devices running
75  * {@link android.os.Build.VERSION_CODES#KITKAT} or earlier, you can use
76  * {@link #fromFile(File)} which emulates the behavior of a
77  * {@link android.provider.DocumentsProvider}.
78  *
79  * @see android.provider.DocumentsProvider
80  * @see android.provider.DocumentsContract
81  */
82 public abstract class DocumentFile {
83     static final String TAG = "DocumentFile";
84 
85     @Nullable
86     private final DocumentFile mParent;
87 
DocumentFile(@ullable DocumentFile parent)88     DocumentFile(@Nullable DocumentFile parent) {
89         mParent = parent;
90     }
91 
92     /**
93      * Create a {@link DocumentFile} representing the filesystem tree rooted at
94      * the given {@link File}. This doesn't give you any additional access to the
95      * underlying files beyond what your app already has.
96      * <p>
97      * {@link #getUri()} will return {@code file://} Uris for files explored
98      * through this tree.
99      */
100     @NonNull
fromFile(@onNull File file)101     public static DocumentFile fromFile(@NonNull File file) {
102         return new RawDocumentFile(null, file);
103     }
104 
105     /**
106      * Create a {@link DocumentFile} representing the single document at the
107      * given {@link Uri}. This is only useful on devices running
108      * {@link android.os.Build.VERSION_CODES#KITKAT} or later, and will return
109      * {@code null} when called on earlier platform versions.
110      *
111      * @param singleUri the {@link Intent#getData()} from a successful
112      *            {@link Intent#ACTION_OPEN_DOCUMENT} or
113      *            {@link Intent#ACTION_CREATE_DOCUMENT} request.
114      */
115     @Nullable
fromSingleUri(@onNull Context context, @NonNull Uri singleUri)116     public static DocumentFile fromSingleUri(@NonNull Context context, @NonNull Uri singleUri) {
117         if (Build.VERSION.SDK_INT >= 19) {
118             return new SingleDocumentFile(null, context, singleUri);
119         } else {
120             return null;
121         }
122     }
123 
124     /**
125      * Create a {@link DocumentFile} representing the document tree rooted at
126      * the given {@link Uri}. This is only useful on devices running
127      * {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later, and will return
128      * {@code null} when called on earlier platform versions.
129      *
130      * @param treeUri the {@link Intent#getData()} from a successful
131      *            {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request.
132      */
133     @Nullable
fromTreeUri(@onNull Context context, @NonNull Uri treeUri)134     public static DocumentFile fromTreeUri(@NonNull Context context, @NonNull Uri treeUri) {
135         if (Build.VERSION.SDK_INT >= 21) {
136             return new TreeDocumentFile(null, context,
137                     DocumentsContract.buildDocumentUriUsingTree(treeUri,
138                             DocumentsContract.getTreeDocumentId(treeUri)));
139         } else {
140             return null;
141         }
142     }
143 
144     /**
145      * Test if given Uri is backed by a
146      * {@link android.provider.DocumentsProvider}.
147      */
isDocumentUri(@onNull Context context, @Nullable Uri uri)148     public static boolean isDocumentUri(@NonNull Context context, @Nullable Uri uri) {
149         if (Build.VERSION.SDK_INT >= 19) {
150             return DocumentsContract.isDocumentUri(context, uri);
151         } else {
152             return false;
153         }
154     }
155 
156     /**
157      * Create a new document as a direct child of this directory.
158      *
159      * @param mimeType MIME type of new document, such as {@code image/png} or
160      *            {@code audio/flac}
161      * @param displayName name of new document, without any file extension
162      *            appended; the underlying provider may choose to append the
163      *            extension
164      * @return file representing newly created document, or null if failed
165      * @throws UnsupportedOperationException when working with a single document
166      *             created from {@link #fromSingleUri(Context, Uri)}.
167      * @see android.provider.DocumentsContract#createDocument(ContentResolver,
168      *      Uri, String, String)
169      */
170     @Nullable
createFile(@onNull String mimeType, @NonNull String displayName)171     public abstract DocumentFile createFile(@NonNull String mimeType, @NonNull String displayName);
172 
173     /**
174      * Create a new directory as a direct child of this directory.
175      *
176      * @param displayName name of new directory
177      * @return file representing newly created directory, or null if failed
178      * @throws UnsupportedOperationException when working with a single document
179      *             created from {@link #fromSingleUri(Context, Uri)}.
180      * @see android.provider.DocumentsContract#createDocument(ContentResolver,
181      *      Uri, String, String)
182      */
183     @Nullable
createDirectory(@onNull String displayName)184     public abstract DocumentFile createDirectory(@NonNull String displayName);
185 
186     /**
187      * Return a Uri for the underlying document represented by this file. This
188      * can be used with other platform APIs to manipulate or share the
189      * underlying content. You can use {@link #isDocumentUri(Context, Uri)} to
190      * test if the returned Uri is backed by a
191      * {@link android.provider.DocumentsProvider}.
192      *
193      * @see Intent#setData(Uri)
194      * @see Intent#setClipData(android.content.ClipData)
195      * @see ContentResolver#openInputStream(Uri)
196      * @see ContentResolver#openOutputStream(Uri)
197      * @see ContentResolver#openFileDescriptor(Uri, String)
198      */
199     @NonNull
getUri()200     public abstract Uri getUri();
201 
202     /**
203      * Return the display name of this document.
204      *
205      * @see android.provider.DocumentsContract.Document#COLUMN_DISPLAY_NAME
206      */
207     @Nullable
getName()208     public abstract String getName();
209 
210     /**
211      * Return the MIME type of this document.
212      *
213      * @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE
214      */
215     @Nullable
getType()216     public abstract String getType();
217 
218     /**
219      * Return the parent file of this document. Only defined inside of the
220      * user-selected tree; you can never escape above the top of the tree.
221      * <p>
222      * The underlying {@link android.provider.DocumentsProvider} only defines a
223      * forward mapping from parent to child, so the reverse mapping of child to
224      * parent offered here is purely a convenience method, and it may be
225      * incorrect if the underlying tree structure changes.
226      */
227     @Nullable
getParentFile()228     public DocumentFile getParentFile() {
229         return mParent;
230     }
231 
232     /**
233      * Indicates if this file represents a <em>directory</em>.
234      *
235      * @return {@code true} if this file is a directory, {@code false}
236      *         otherwise.
237      * @see android.provider.DocumentsContract.Document#MIME_TYPE_DIR
238      */
isDirectory()239     public abstract boolean isDirectory();
240 
241     /**
242      * Indicates if this file represents a <em>file</em>.
243      *
244      * @return {@code true} if this file is a file, {@code false} otherwise.
245      * @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE
246      */
isFile()247     public abstract boolean isFile();
248 
249     /**
250      * Indicates if this file represents a <em>virtual</em> document.
251      *
252      * @return {@code true} if this file is a virtual document.
253      * @see android.provider.DocumentsContract.Document#FLAG_VIRTUAL_DOCUMENT
254      */
isVirtual()255     public abstract boolean isVirtual();
256 
257     /**
258      * Returns the time when this file was last modified, measured in
259      * milliseconds since January 1st, 1970, midnight. Returns 0 if the file
260      * does not exist, or if the modified time is unknown.
261      *
262      * @return the time when this file was last modified.
263      * @see android.provider.DocumentsContract.Document#COLUMN_LAST_MODIFIED
264      */
lastModified()265     public abstract long lastModified();
266 
267     /**
268      * Returns the length of this file in bytes. Returns 0 if the file does not
269      * exist, or if the length is unknown. The result for a directory is not
270      * defined.
271      *
272      * @return the number of bytes in this file.
273      * @see android.provider.DocumentsContract.Document#COLUMN_SIZE
274      */
length()275     public abstract long length();
276 
277     /**
278      * Indicates whether the current context is allowed to read from this file.
279      *
280      * @return {@code true} if this file can be read, {@code false} otherwise.
281      */
canRead()282     public abstract boolean canRead();
283 
284     /**
285      * Indicates whether the current context is allowed to write to this file.
286      *
287      * @return {@code true} if this file can be written, {@code false}
288      *         otherwise.
289      * @see android.provider.DocumentsContract.Document#COLUMN_FLAGS
290      * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE
291      * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE
292      * @see android.provider.DocumentsContract.Document#FLAG_DIR_SUPPORTS_CREATE
293      */
canWrite()294     public abstract boolean canWrite();
295 
296     /**
297      * Deletes this file.
298      * <p>
299      * Note that this method does <i>not</i> throw {@code IOException} on
300      * failure. Callers must check the return value.
301      *
302      * @return {@code true} if this file was deleted, {@code false} otherwise.
303      * @see android.provider.DocumentsContract#deleteDocument(ContentResolver,
304      *      Uri)
305      */
delete()306     public abstract boolean delete();
307 
308     /**
309      * Returns a boolean indicating whether this file can be found.
310      *
311      * @return {@code true} if this file exists, {@code false} otherwise.
312      */
exists()313     public abstract boolean exists();
314 
315     /**
316      * Returns an array of files contained in the directory represented by this
317      * file.
318      *
319      * @return an array of files or {@code null}.
320      * @throws UnsupportedOperationException when working with a single document
321      *             created from {@link #fromSingleUri(Context, Uri)}.
322      * @see android.provider.DocumentsContract#buildChildDocumentsUriUsingTree(Uri,
323      *      String)
324      */
325     @NonNull
listFiles()326     public abstract DocumentFile[] listFiles();
327 
328     /**
329      * Search through {@link #listFiles()} for the first document matching the
330      * given display name. Returns {@code null} when no matching document is
331      * found.
332      *
333      * @throws UnsupportedOperationException when working with a single document
334      *             created from {@link #fromSingleUri(Context, Uri)}.
335      */
336     @Nullable
findFile(@onNull String displayName)337     public DocumentFile findFile(@NonNull String displayName) {
338         for (DocumentFile doc : listFiles()) {
339             if (displayName.equals(doc.getName())) {
340                 return doc;
341             }
342         }
343         return null;
344     }
345 
346     /**
347      * Renames this file to {@code displayName}.
348      * <p>
349      * Note that this method does <i>not</i> throw {@code IOException} on
350      * failure. Callers must check the return value.
351      * <p>
352      * Some providers may need to create a new document to reflect the rename,
353      * potentially with a different MIME type, so {@link #getUri()} and
354      * {@link #getType()} may change to reflect the rename.
355      * <p>
356      * When renaming a directory, children previously enumerated through
357      * {@link #listFiles()} may no longer be valid.
358      *
359      * @param displayName the new display name.
360      * @return true on success.
361      * @throws UnsupportedOperationException when working with a single document
362      *             created from {@link #fromSingleUri(Context, Uri)}.
363      * @see android.provider.DocumentsContract#renameDocument(ContentResolver,
364      *      Uri, String)
365      */
renameTo(@onNull String displayName)366     public abstract boolean renameTo(@NonNull String displayName);
367 }
368