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;
24
25import 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 */
78public abstract class DocumentFile {
79    static final String TAG = "DocumentFile";
80
81    private final DocumentFile mParent;
82
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
349    public abstract boolean renameTo(String displayName);
350}
351