288 | * The underlying {@link android.provider.DocumentsProvider} only defines a
289 | * forward mapping from parent to child, so the reverse mapping of child to
290 | * parent offered here is purely a convenience method, and it may be
291 | * incorrect if the underlying tree structure changes.
292 | *
293 | * @return parent of the file, or null if it is the top of the file tree
294 | */
295 | @Nullable
296 | public UniFile getParentFile() {
297 | return mParent;
298 | }
299 |
300 | /**
301 | * Indicates if this file represents a directory.
302 | *
303 | * @return {@code true} if this file is a directory, {@code false}
304 | * otherwise.
305 | * @see android.provider.DocumentsContract.Document#MIME_TYPE_DIR
306 | */
307 | public abstract boolean isDirectory();
308 |
309 | /**
310 | * Indicates if this file represents a file.
311 | *
312 | * @return {@code true} if this file is a file, {@code false} otherwise.
313 | * @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE
314 | */
315 | public abstract boolean isFile();
316 |
317 | /**
318 | * Returns the time when this file was last modified, measured in
319 | * milliseconds since January 1st, 1970, midnight. Returns -1 if the file
320 | * does not exist, or if the modified time is unknown.
321 | *
322 | * @return the time when this file was last modified, -1L
if can't get it
323 | * @see android.provider.DocumentsContract.Document#COLUMN_LAST_MODIFIED
324 | */
325 | public abstract long lastModified();
326 |
327 | /**
328 | * Returns the length of this file in bytes. Returns -1 if the file does not
329 | * exist, or if the length is unknown. The result for a directory is not
330 | * defined.
331 | *
332 | * @return the number of bytes in this file, -1L
if can't get it
333 | * @see android.provider.DocumentsContract.Document#COLUMN_SIZE
334 | */
335 | public abstract long length();
336 |
337 | /**
338 | * Indicates whether the current context is allowed to read from this file.
339 | *
340 | * @return {@code true} if this file can be read, {@code false} otherwise.
341 | */
342 | public abstract boolean canRead();
343 |
344 | /**
345 | * Indicates whether the current context is allowed to write to this file.
346 | *
347 | * @return {@code true} if this file can be written, {@code false}
348 | * otherwise.
349 | * @see android.provider.DocumentsContract.Document#COLUMN_FLAGS
350 | * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE
351 | * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE
352 | * @see android.provider.DocumentsContract.Document#FLAG_DIR_SUPPORTS_CREATE
353 | */
354 | public abstract boolean canWrite();
355 |
356 | /**
357 | * Deletes this file.
358 | *
359 | * Note that this method does not throw {@code IOException} on 360 | * failure. Callers must check the return value. 361 | * 362 | * @return {@code true} if this file was deleted, {@code false} otherwise. 363 | * @see android.provider.DocumentsContract#deleteDocument(ContentResolver, 364 | * Uri) 365 | */ 366 | public abstract boolean delete(); 367 | 368 | /** 369 | * Returns a boolean indicating whether this file can be found. 370 | * 371 | * @return {@code true} if this file exists, {@code false} otherwise. 372 | */ 373 | public abstract boolean exists(); 374 | 375 | /** 376 | * Returns an array of files contained in the directory represented by this 377 | * file. 378 | * 379 | * @return an array of files or {@code null}. 380 | * @see android.provider.DocumentsContract#buildChildDocumentsUriUsingTree(Uri, 381 | * String) 382 | */ 383 | @Nullable 384 | public abstract UniFile[] listFiles(); 385 | 386 | /** 387 | * Gets a list of the files in the directory represented by this file. This 388 | * list is then filtered through a FilenameFilter and the names of files 389 | * with matching names are returned as an array of strings. 390 | * 391 | * @param filter the filter to match names against, may be {@code null}. 392 | * @return an array of files or {@code null}. 393 | */ 394 | @Nullable 395 | public abstract UniFile[] listFiles(FilenameFilter filter); 396 | 397 | /** 398 | * Test there is a file with the exact same display name in the directory. 399 | * 400 | * @return the file if found it, or {@code null}. 401 | */ 402 | @Nullable 403 | public abstract UniFile findFile(String displayName); 404 | 405 | /** 406 | * Renames this file to {@code displayName}. 407 | *
408 | * Note that this method does not throw {@code IOException} on 409 | * failure. Callers must check the return value. 410 | *
411 | * Some providers may need to create a new file to reflect the rename, 412 | * potentially with a different MIME type, so {@link #getUri()} and 413 | * {@link #getType()} may change to reflect the rename. 414 | *
415 | * When renaming a directory, children previously enumerated through 416 | * {@link #listFiles()} may no longer be valid. 417 | * 418 | * @param displayName the new display name. 419 | * @return true on success. 420 | * @see android.provider.DocumentsContract#renameDocument(ContentResolver, 421 | * Uri, String) 422 | */ 423 | public abstract boolean renameTo(String displayName); 424 | 425 | /** 426 | * Open a stream on to the content associated with the file, clean it if it exists 427 | * 428 | * @return the {@link OutputStream} 429 | * @throws IOException 430 | */ 431 | @NonNull 432 | public abstract OutputStream openOutputStream() throws IOException; 433 | 434 | /** 435 | * Open a stream on to the content associated with the file 436 | * 437 | * @param append {@code true} for do not clean it if it exists 438 | * @return the {@link OutputStream} 439 | * @throws IOException 440 | */ 441 | @NonNull 442 | public abstract OutputStream openOutputStream(boolean append) throws IOException; 443 | 444 | /** 445 | * Open a stream on to the content associated with the file 446 | * 447 | * @return the {@link InputStream} 448 | * @throws IOException 449 | */ 450 | @NonNull 451 | public abstract InputStream openInputStream() throws IOException; 452 | 453 | /** 454 | * Get a random access stuff of the UniFile 455 | * 456 | * @param mode "r" or "rw" 457 | * @return the random access stuff 458 | * @throws IOException 459 | */ 460 | @NonNull 461 | public abstract UniRandomAccessFile createRandomAccessFile(String mode) throws IOException; 462 | } 463 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/unifile/UniRandomAccessFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Hippo Seven 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 com.hippo.unifile; 18 | 19 | /* 20 | * Created by Hippo on 8/15/2016. 21 | */ 22 | 23 | import java.io.EOFException; 24 | import java.io.IOException; 25 | 26 | /** 27 | * The UniRandomAccessFile is designed to emulate RandomAccessFile interface for UniFile 28 | */ 29 | public interface UniRandomAccessFile { 30 | 31 | /** 32 | * Closes this UniRandomAccessFile and releases any resources associated with it. 33 | * A closed UniRandomAccessFile cannot perform any operations. 34 | * 35 | * @throws IOException 36 | */ 37 | void close() throws IOException; 38 | 39 | /** 40 | * Returns the current offset in this file. 41 | * 42 | * @throws IOException 43 | */ 44 | long getFilePointer() throws IOException; 45 | 46 | /** 47 | * Move current location pointer to the new offset. 48 | * 49 | * @param pos the offset position, measured in bytes from the beginning 50 | * @throws IOException 51 | */ 52 | void seek(long pos) throws IOException; 53 | 54 | /** 55 | * Attempts to skip over n bytes of input discarding the skipped bytes. 56 | * 57 | * @param n the number of bytes to be skipped 58 | * @return the actual number of bytes skipped 59 | * @throws IOException 60 | */ 61 | int skipBytes(int n) throws IOException; 62 | 63 | /** 64 | * Returns the length of this file. 65 | * 66 | * @throws IOException 67 | */ 68 | long length() throws IOException; 69 | 70 | /** 71 | * Sets the length of this file. 72 | * 73 | * @param newLength the desired length of the file 74 | * @throws IOException 75 | */ 76 | void setLength(long newLength) throws IOException; 77 | 78 | 79 | /** 80 | * Reads b.length bytes from this file into the byte array, 81 | * starting at the current file pointer. 82 | * 83 | * @param b the buffer into which the data is read 84 | * @throws EOFException if this file reaches the end before reading all the bytes 85 | * @throws IOException if an I/O error occurs 86 | */ 87 | void read(byte[] b) throws IOException; 88 | 89 | /** 90 | * Reads exactly len bytes from this file into the byte array, 91 | * starting at the current file pointer. 92 | * 93 | * @param b the buffer into which the data is read 94 | * @param off the start offset of the data 95 | * @param len the number of bytes to read 96 | * @throws IOException 97 | */ 98 | void read(byte[] b, int off, int len) throws IOException; 99 | 100 | /** 101 | * Writes b.length bytes from the specified byte array to this file, 102 | * starting at the current file pointer. 103 | * 104 | * @param b the data 105 | * @throws IOException 106 | */ 107 | void write(byte[] b) throws IOException; 108 | 109 | /** 110 | * Writes len bytes from the specified byte array starting at offset off to this file. 111 | * 112 | * @param b the data 113 | * @param off the start offset in the data 114 | * @param len the number of bytes to write 115 | * @throws IOException 116 | */ 117 | void write(byte[] b, int off, int len) throws IOException; 118 | } 119 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/unifile/UriHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Hippo Seven 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 com.hippo.unifile; 18 | 19 | /* 20 | * Created by Hippo on 8/16/2016. 21 | */ 22 | 23 | import android.content.Context; 24 | import android.net.Uri; 25 | 26 | /** 27 | * A UriHandler is to get UniFile from custom uri for extensions 28 | */ 29 | public interface UriHandler { 30 | 31 | /** 32 | * Create a {@link UniFile} representing the uri 33 | */ 34 | UniFile fromUri(Context context, Uri uri); 35 | } 36 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/unifile/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Hippo Seven 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 com.hippo.unifile; 18 | 19 | /* 20 | * Created by Hippo on 11/19/2016. 21 | */ 22 | 23 | import android.text.TextUtils; 24 | import android.webkit.MimeTypeMap; 25 | 26 | import androidx.annotation.Nullable; 27 | 28 | class Utils { 29 | private Utils() {} 30 | 31 | @Nullable 32 | static String getTypeForName(String name) { 33 | if (TextUtils.isEmpty(name)) { 34 | return null; 35 | } 36 | 37 | final int lastDot = name.lastIndexOf('.'); 38 | if (lastDot >= 0) { 39 | final String extension = name.substring(lastDot + 1).toLowerCase(); 40 | final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 41 | if (!TextUtils.isEmpty(mime)) { 42 | return mime; 43 | } 44 | } 45 | 46 | return "application/octet-stream"; 47 | } 48 | 49 | /** 50 | * A normal Unix pathname does not contain consecutive slashes and does not end 51 | * with a slash. The empty string and "/" are special cases that are also 52 | * considered normal. 53 | */ 54 | static String normalize(String pathname) { 55 | int n = pathname.length(); 56 | char[] normalized = pathname.toCharArray(); 57 | int index = 0; 58 | char prevChar = 0; 59 | for (int i = 0; i < n; i++) { 60 | char current = normalized[i]; 61 | // Remove duplicate slashes. 62 | if (!(current == '/' && prevChar == '/')) { 63 | normalized[index++] = current; 64 | } 65 | 66 | prevChar = current; 67 | } 68 | 69 | // Omit the trailing slash, except when pathname == "/". 70 | if (prevChar == '/' && n > 1) { 71 | index--; 72 | } 73 | 74 | return (index != n) ? new String(normalized, 0, index) : pathname; 75 | } 76 | 77 | // Invariant: Both |parent| and |child| are normalized paths. 78 | static String resolve(String parent, String child) { 79 | if (child.length() == 0 || child.equals("/")) { 80 | return parent; 81 | } 82 | 83 | if (child.charAt(0) == '/') { 84 | if (parent.equals("/")) return child; 85 | return parent + child; 86 | } 87 | 88 | if (parent.equals("/")) return parent + child; 89 | return parent + '/' + child; 90 | } 91 | 92 | static boolean equals(String str1, String str2, boolean ignoreCase) { 93 | if (ignoreCase) { 94 | return str1.equalsIgnoreCase(str2); 95 | } else { 96 | return str1.equals(str2); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------