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