294 | * The underlying {@link android.provider.DocumentsProvider} only defines a
295 | * forward mapping from parent to child, so the reverse mapping of child to
296 | * parent offered here is purely a convenience method, and it may be
297 | * incorrect if the underlying tree structure changes.
298 | *
299 | * @return parent of the file, or null if it is the top of the file tree
300 | */
301 | @Nullable
302 | public UniFile getParentFile() {
303 | return mParent;
304 | }
305 |
306 | /**
307 | * Indicates if this file represents a directory.
308 | *
309 | * @return {@code true} if this file is a directory, {@code false}
310 | * otherwise.
311 | * @see android.provider.DocumentsContract.Document#MIME_TYPE_DIR
312 | */
313 | public abstract boolean isDirectory();
314 |
315 | /**
316 | * Indicates if this file represents a file.
317 | *
318 | * @return {@code true} if this file is a file, {@code false} otherwise.
319 | * @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE
320 | */
321 | public abstract boolean isFile();
322 |
323 | /**
324 | * Returns the time when this file was last modified, measured in
325 | * milliseconds since January 1st, 1970, midnight. Returns -1 if the file
326 | * does not exist, or if the modified time is unknown.
327 | *
328 | * @return the time when this file was last modified, -1L
if can't get it
329 | * @see android.provider.DocumentsContract.Document#COLUMN_LAST_MODIFIED
330 | */
331 | public abstract long lastModified();
332 |
333 | /**
334 | * Returns the length of this file in bytes. Returns -1 if the file does not
335 | * exist, or if the length is unknown. The result for a directory is not
336 | * defined.
337 | *
338 | * @return the number of bytes in this file, -1L
if can't get it
339 | * @see android.provider.DocumentsContract.Document#COLUMN_SIZE
340 | */
341 | public abstract long length();
342 |
343 | /**
344 | * Indicates whether the current context is allowed to read from this file.
345 | *
346 | * @return {@code true} if this file can be read, {@code false} otherwise.
347 | */
348 | public abstract boolean canRead();
349 |
350 | /**
351 | * Indicates whether the current context is allowed to write to this file.
352 | *
353 | * @return {@code true} if this file can be written, {@code false}
354 | * otherwise.
355 | * @see android.provider.DocumentsContract.Document#COLUMN_FLAGS
356 | * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE
357 | * @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE
358 | * @see android.provider.DocumentsContract.Document#FLAG_DIR_SUPPORTS_CREATE
359 | */
360 | public abstract boolean canWrite();
361 |
362 | /**
363 | * Deletes this file.
364 | *
365 | * Note that this method does not throw {@code IOException} on 366 | * failure. Callers must check the return value. 367 | * 368 | * @return {@code true} if this file was deleted, {@code false} otherwise. 369 | * @see android.provider.DocumentsContract#deleteDocument(ContentResolver, 370 | * Uri) 371 | */ 372 | public abstract boolean delete(); 373 | 374 | /** 375 | * Returns a boolean indicating whether this file can be found. 376 | * 377 | * @return {@code true} if this file exists, {@code false} otherwise. 378 | */ 379 | public abstract boolean exists(); 380 | 381 | /** 382 | * Returns an array of files contained in the directory represented by this 383 | * file. 384 | * 385 | * @return an array of files or {@code null}. 386 | * @see android.provider.DocumentsContract#buildChildDocumentsUriUsingTree(Uri, 387 | * String) 388 | */ 389 | @Nullable 390 | public abstract UniFile[] listFiles(); 391 | 392 | /** 393 | * Gets a list of the files in the directory represented by this file. This 394 | * list is then filtered through a FilenameFilter and the names of files 395 | * with matching names are returned as an array of strings. 396 | * 397 | * @param filter the filter to match names against, may be {@code null}. 398 | * @return an array of files or {@code null}. 399 | */ 400 | @Nullable 401 | public abstract UniFile[] listFiles(FilenameFilter filter); 402 | 403 | /** 404 | * Test there is a file with the display name in the directory. 405 | * 406 | * @return the file if found it, or {@code null}. 407 | */ 408 | @Nullable 409 | public abstract UniFile findFile(String displayName); 410 | 411 | /** 412 | * Renames this file to {@code displayName}. 413 | *
414 | * Note that this method does not throw {@code IOException} on 415 | * failure. Callers must check the return value. 416 | *
417 | * Some providers may need to create a new file to reflect the rename, 418 | * potentially with a different MIME type, so {@link #getUri()} and 419 | * {@link #getType()} may change to reflect the rename. 420 | *
421 | * When renaming a directory, children previously enumerated through 422 | * {@link #listFiles()} may no longer be valid. 423 | * 424 | * @param displayName the new display name. 425 | * @return true on success. 426 | * @see android.provider.DocumentsContract#renameDocument(ContentResolver, 427 | * Uri, String) 428 | */ 429 | public abstract boolean renameTo(String displayName); 430 | 431 | /** 432 | * Open a stream on to the content associated with the file, clean it if it exists 433 | * 434 | * @return the {@link OutputStream} 435 | * @throws IOException 436 | */ 437 | @NonNull 438 | public abstract OutputStream openOutputStream() throws IOException; 439 | 440 | /** 441 | * Open a stream on to the content associated with the file 442 | * 443 | * @param append {@code true} for do not clean it if it exists 444 | * @return the {@link OutputStream} 445 | * @throws IOException 446 | */ 447 | @NonNull 448 | public abstract OutputStream openOutputStream(boolean append) throws IOException; 449 | 450 | /** 451 | * Open a stream on to the content associated with the file 452 | * 453 | * @return the {@link InputStream} 454 | * @throws IOException 455 | */ 456 | @NonNull 457 | public abstract InputStream openInputStream() throws IOException; 458 | 459 | /** 460 | * Get a random access stuff of the UniFile 461 | * 462 | * @param mode "r" or "rw" 463 | * @return the random access stuff 464 | * @throws IOException 465 | */ 466 | @NonNull 467 | public abstract UniRandomAccessFile createRandomAccessFile(String mode) throws IOException; 468 | } 469 | -------------------------------------------------------------------------------- /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.support.annotation.Nullable; 24 | import android.text.TextUtils; 25 | import android.webkit.MimeTypeMap; 26 | 27 | class Utils { 28 | private Utils() {} 29 | 30 | @Nullable 31 | static String getTypeForName(String name) { 32 | if (TextUtils.isEmpty(name)) { 33 | return null; 34 | } 35 | 36 | final int lastDot = name.lastIndexOf('.'); 37 | if (lastDot >= 0) { 38 | final String extension = name.substring(lastDot + 1).toLowerCase(); 39 | final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 40 | if (!TextUtils.isEmpty(mime)) { 41 | return mime; 42 | } 43 | } 44 | 45 | return "application/octet-stream"; 46 | } 47 | 48 | /** 49 | * A normal Unix pathname does not contain consecutive slashes and does not end 50 | * with a slash. The empty string and "/" are special cases that are also 51 | * considered normal. 52 | */ 53 | static String normalize(String pathname) { 54 | int n = pathname.length(); 55 | char[] normalized = pathname.toCharArray(); 56 | int index = 0; 57 | char prevChar = 0; 58 | for (int i = 0; i < n; i++) { 59 | char current = normalized[i]; 60 | // Remove duplicate slashes. 61 | if (!(current == '/' && prevChar == '/')) { 62 | normalized[index++] = current; 63 | } 64 | 65 | prevChar = current; 66 | } 67 | 68 | // Omit the trailing slash, except when pathname == "/". 69 | if (prevChar == '/' && n > 1) { 70 | index--; 71 | } 72 | 73 | return (index != n) ? new String(normalized, 0, index) : pathname; 74 | } 75 | 76 | // Invariant: Both |parent| and |child| are normalized paths. 77 | static String resolve(String parent, String child) { 78 | if (child.length() == 0 || child.equals("/")) { 79 | return parent; 80 | } 81 | 82 | if (child.charAt(0) == '/') { 83 | if (parent.equals("/")) return child; 84 | return parent + child; 85 | } 86 | 87 | if (parent.equals("/")) return parent + child; 88 | return parent + '/' + child; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /library/src/test/java/com/hippo/unifile/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.hippo.unifile; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------