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 Uri}. 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 final int version = Build.VERSION.SDK_INT; 111 if (version >= 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 final int version = Build.VERSION.SDK_INT; 129 if (version >= 21) { 130 return new TreeDocumentFile(null, context, 131 DocumentsContractApi21.prepareTreeUri(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 final int version = Build.VERSION.SDK_INT; 143 if (version >= 19) { 144 return DocumentsContractApi19.isDocumentUri(context, uri); 145 } else { 146 return false; 147 } 148 } 149 150 /** 151 * Create a new document as a direct child of this directory. 152 * 153 * @param mimeType MIME type of new document, such as {@code image/png} or 154 * {@code audio/flac} 155 * @param displayName name of new document, without any file extension 156 * appended; the underlying provider may choose to append the 157 * extension 158 * @return file representing newly created document, or null if failed 159 * @throws UnsupportedOperationException when working with a single document 160 * created from {@link #fromSingleUri(Context, Uri)}. 161 * @see android.provider.DocumentsContract#createDocument(ContentResolver, 162 * Uri, String, String) 163 */ createFile(String mimeType, String displayName)164 public abstract DocumentFile createFile(String mimeType, String displayName); 165 166 /** 167 * Create a new directory as a direct child of this directory. 168 * 169 * @param displayName name of new directory 170 * @return file representing newly created directory, or null if failed 171 * @throws UnsupportedOperationException when working with a single document 172 * created from {@link #fromSingleUri(Context, Uri)}. 173 * @see android.provider.DocumentsContract#createDocument(ContentResolver, 174 * Uri, String, String) 175 */ createDirectory(String displayName)176 public abstract DocumentFile createDirectory(String displayName); 177 178 /** 179 * Return a Uri for the underlying document represented by this file. This 180 * can be used with other platform APIs to manipulate or share the 181 * underlying content. You can use {@link #isDocumentUri(Context, Uri)} to 182 * test if the returned Uri is backed by a 183 * {@link android.provider.DocumentsProvider}. 184 * 185 * @see Intent#setData(Uri) 186 * @see Intent#setClipData(android.content.ClipData) 187 * @see ContentResolver#openInputStream(Uri) 188 * @see ContentResolver#openOutputStream(Uri) 189 * @see ContentResolver#openFileDescriptor(Uri, String) 190 */ getUri()191 public abstract Uri getUri(); 192 193 /** 194 * Return the display name of this document. 195 * 196 * @see android.provider.DocumentsContract.Document#COLUMN_DISPLAY_NAME 197 */ getName()198 public abstract String getName(); 199 200 /** 201 * Return the MIME type of this document. 202 * 203 * @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE 204 */ getType()205 public abstract String getType(); 206 207 /** 208 * Return the parent file of this document. Only defined inside of the 209 * user-selected tree; you can never escape above the top of the tree. 210 * <p> 211 * The underlying {@link android.provider.DocumentsProvider} only defines a 212 * forward mapping from parent to child, so the reverse mapping of child to 213 * parent offered here is purely a convenience method, and it may be 214 * incorrect if the underlying tree structure changes. 215 */ getParentFile()216 public DocumentFile getParentFile() { 217 return mParent; 218 } 219 220 /** 221 * Indicates if this file represents a <em>directory</em>. 222 * 223 * @return {@code true} if this file is a directory, {@code false} 224 * otherwise. 225 * @see android.provider.DocumentsContract.Document#MIME_TYPE_DIR 226 */ isDirectory()227 public abstract boolean isDirectory(); 228 229 /** 230 * Indicates if this file represents a <em>file</em>. 231 * 232 * @return {@code true} if this file is a file, {@code false} otherwise. 233 * @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE 234 */ isFile()235 public abstract boolean isFile(); 236 237 /** 238 * Returns the time when this file was last modified, measured in 239 * milliseconds since January 1st, 1970, midnight. Returns 0 if the file 240 * does not exist, or if the modified time is unknown. 241 * 242 * @return the time when this file was last modified. 243 * @see android.provider.DocumentsContract.Document#COLUMN_LAST_MODIFIED 244 */ lastModified()245 public abstract long lastModified(); 246 247 /** 248 * Returns the length of this file in bytes. Returns 0 if the file does not 249 * exist, or if the length is unknown. The result for a directory is not 250 * defined. 251 * 252 * @return the number of bytes in this file. 253 * @see android.provider.DocumentsContract.Document#COLUMN_SIZE 254 */ length()255 public abstract long length(); 256 257 /** 258 * Indicates whether the current context is allowed to read from this file. 259 * 260 * @return {@code true} if this file can be read, {@code false} otherwise. 261 */ canRead()262 public abstract boolean canRead(); 263 264 /** 265 * Indicates whether the current context is allowed to write to this file. 266 * 267 * @return {@code true} if this file can be written, {@code false} 268 * otherwise. 269 * @see android.provider.DocumentsContract.Document#COLUMN_FLAGS 270 * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE 271 * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE 272 * @see android.provider.DocumentsContract.Document#FLAG_DIR_SUPPORTS_CREATE 273 */ canWrite()274 public abstract boolean canWrite(); 275 276 /** 277 * Deletes this file. 278 * <p> 279 * Note that this method does <i>not</i> throw {@code IOException} on 280 * failure. Callers must check the return value. 281 * 282 * @return {@code true} if this file was deleted, {@code false} otherwise. 283 * @see android.provider.DocumentsContract#deleteDocument(ContentResolver, 284 * Uri) 285 */ delete()286 public abstract boolean delete(); 287 288 /** 289 * Returns a boolean indicating whether this file can be found. 290 * 291 * @return {@code true} if this file exists, {@code false} otherwise. 292 */ exists()293 public abstract boolean exists(); 294 295 /** 296 * Returns an array of files contained in the directory represented by this 297 * file. 298 * 299 * @return an array of files or {@code null}. 300 * @throws UnsupportedOperationException when working with a single document 301 * created from {@link #fromSingleUri(Context, Uri)}. 302 * @see android.provider.DocumentsContract#buildChildDocumentsUriUsingTree(Uri, 303 * String) 304 */ listFiles()305 public abstract DocumentFile[] listFiles(); 306 307 /** 308 * Search through {@link #listFiles()} for the first document matching the 309 * given display name. Returns {@code null} when no matching document is 310 * found. 311 * 312 * @throws UnsupportedOperationException when working with a single document 313 * created from {@link #fromSingleUri(Context, Uri)}. 314 */ findFile(String displayName)315 public DocumentFile findFile(String displayName) { 316 for (DocumentFile doc : listFiles()) { 317 if (displayName.equals(doc.getName())) { 318 return doc; 319 } 320 } 321 return null; 322 } 323 324 /** 325 * Renames this file to {@code displayName}. 326 * <p> 327 * Note that this method does <i>not</i> throw {@code IOException} on 328 * failure. Callers must check the return value. 329 * <p> 330 * Some providers may need to create a new document to reflect the rename, 331 * potentially with a different MIME type, so {@link #getUri()} and 332 * {@link #getType()} may change to reflect the rename. 333 * <p> 334 * When renaming a directory, children previously enumerated through 335 * {@link #listFiles()} may no longer be valid. 336 * 337 * @param displayName the new display name. 338 * @return true on success. 339 * @throws UnsupportedOperationException when working with a single document 340 * created from {@link #fromSingleUri(Context, Uri)}. 341 * @see android.provider.DocumentsContract#renameDocument(ContentResolver, 342 * Uri, String) 343 */ renameTo(String displayName)344 public abstract boolean renameTo(String displayName); 345 } 346