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