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
17package android.support.v4.provider;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent;
22import android.net.Uri;
23import android.os.Build;
24import android.provider.DocumentsContract;
25
26import 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 */
79public abstract class DocumentFile {
80    static final String TAG = "DocumentFile";
81
82    private final DocumentFile mParent;
83
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
351    public abstract boolean renameTo(String displayName);
352}
353