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