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