diff options
| author | Jeff Sharkey <jsharkey@android.com> | 2018-07-09 16:38:20 -0600 |
|---|---|---|
| committer | Jeff Sharkey <jsharkey@android.com> | 2018-07-11 14:41:24 -0600 |
| commit | 5aae0c9df7dc3be65dd9aa75d4c11c49a41638ee (patch) | |
| tree | d323416ee211040d6e52776f0acda743207563bf /core/java/android/os/FileUtils.java | |
| parent | 0b5f738f4cb0f25c20244f09a484d4acf16f257f (diff) | |
Utility methods useful for working with files.
Part of getting DocumentsUI ready for building against public API.
Test: builds
Bug: 110959821
Change-Id: I7cc0acd5ac3bcc89790cb49f34291ae523e44019
Diffstat (limited to 'core/java/android/os/FileUtils.java')
| -rw-r--r-- | core/java/android/os/FileUtils.java | 256 |
1 files changed, 192 insertions, 64 deletions
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 88d6e847b644..9fccd1ec7b43 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -53,32 +53,35 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; /** - * Tools for managing files. Not for public consumption. - * @hide + * Utility methods useful for working with files. */ public class FileUtils { private static final String TAG = "FileUtils"; - public static final int S_IRWXU = 00700; - public static final int S_IRUSR = 00400; - public static final int S_IWUSR = 00200; - public static final int S_IXUSR = 00100; + /** {@hide} */ public static final int S_IRWXU = 00700; + /** {@hide} */ public static final int S_IRUSR = 00400; + /** {@hide} */ public static final int S_IWUSR = 00200; + /** {@hide} */ public static final int S_IXUSR = 00100; - public static final int S_IRWXG = 00070; - public static final int S_IRGRP = 00040; - public static final int S_IWGRP = 00020; - public static final int S_IXGRP = 00010; + /** {@hide} */ public static final int S_IRWXG = 00070; + /** {@hide} */ public static final int S_IRGRP = 00040; + /** {@hide} */ public static final int S_IWGRP = 00020; + /** {@hide} */ public static final int S_IXGRP = 00010; - public static final int S_IRWXO = 00007; - public static final int S_IROTH = 00004; - public static final int S_IWOTH = 00002; - public static final int S_IXOTH = 00001; + /** {@hide} */ public static final int S_IRWXO = 00007; + /** {@hide} */ public static final int S_IROTH = 00004; + /** {@hide} */ public static final int S_IWOTH = 00002; + /** {@hide} */ public static final int S_IXOTH = 00001; + + private FileUtils() { + } /** Regular expression for safe filenames: no spaces or metacharacters. * @@ -94,6 +97,9 @@ public class FileUtils { private static final long COPY_CHECKPOINT_BYTES = 524288; + /** + * Listener that is called periodically as progress is made. + */ public interface ProgressListener { public void onProgress(long progress); } @@ -105,6 +111,7 @@ public class FileUtils { * @param uid to apply through {@code chown}, or -1 to leave unchanged * @param gid to apply through {@code chown}, or -1 to leave unchanged * @return 0 on success, otherwise errno. + * @hide */ public static int setPermissions(File path, int mode, int uid, int gid) { return setPermissions(path.getAbsolutePath(), mode, uid, gid); @@ -117,6 +124,7 @@ public class FileUtils { * @param uid to apply through {@code chown}, or -1 to leave unchanged * @param gid to apply through {@code chown}, or -1 to leave unchanged * @return 0 on success, otherwise errno. + * @hide */ public static int setPermissions(String path, int mode, int uid, int gid) { try { @@ -145,6 +153,7 @@ public class FileUtils { * @param uid to apply through {@code chown}, or -1 to leave unchanged * @param gid to apply through {@code chown}, or -1 to leave unchanged * @return 0 on success, otherwise errno. + * @hide */ public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) { try { @@ -166,7 +175,14 @@ public class FileUtils { return 0; } - public static void copyPermissions(File from, File to) throws IOException { + /** + * Copy the owner UID, owner GID, and mode bits from one file to another. + * + * @param from File where attributes should be copied from. + * @param to File where attributes should be copied to. + * @hide + */ + public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException { try { final StructStat stat = Os.stat(from.getAbsolutePath()); Os.chmod(to.getAbsolutePath(), stat.st_mode); @@ -177,8 +193,10 @@ public class FileUtils { } /** - * Return owning UID of given path, otherwise -1. + * @deprecated use {@link Os#stat(String)} instead. + * @hide */ + @Deprecated public static int getUid(String path) { try { return Os.stat(path).st_uid; @@ -190,6 +208,8 @@ public class FileUtils { /** * Perform an fsync on the given FileOutputStream. The stream at this * point must be flushed but not yet closed. + * + * @hide */ public static boolean sync(FileOutputStream stream) { try { @@ -204,6 +224,7 @@ public class FileUtils { /** * @deprecated use {@link #copy(File, File)} instead. + * @hide */ @Deprecated public static boolean copyFile(File srcFile, File destFile) { @@ -217,6 +238,7 @@ public class FileUtils { /** * @deprecated use {@link #copy(File, File)} instead. + * @hide */ @Deprecated public static void copyFileOrThrow(File srcFile, File destFile) throws IOException { @@ -227,6 +249,7 @@ public class FileUtils { /** * @deprecated use {@link #copy(InputStream, OutputStream)} instead. + * @hide */ @Deprecated public static boolean copyToFile(InputStream inputStream, File destFile) { @@ -240,6 +263,7 @@ public class FileUtils { /** * @deprecated use {@link #copy(InputStream, OutputStream)} instead. + * @hide */ @Deprecated public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException { @@ -265,7 +289,7 @@ public class FileUtils { * @return number of bytes copied. */ public static long copy(@NonNull File from, @NonNull File to) throws IOException { - return copy(from, to, null, null); + return copy(from, to, null, null, null); } /** @@ -274,16 +298,17 @@ public class FileUtils { * Attempts to use several optimization strategies to copy the data in the * kernel before falling back to a userspace copy as a last resort. * - * @param listener to be periodically notified as the copy progresses. * @param signal to signal if the copy should be cancelled early. + * @param executor that listener events should be delivered via. + * @param listener to be periodically notified as the copy progresses. * @return number of bytes copied. */ public static long copy(@NonNull File from, @NonNull File to, - @Nullable ProgressListener listener, @Nullable CancellationSignal signal) - throws IOException { + @Nullable CancellationSignal signal, @Nullable Executor executor, + @Nullable ProgressListener listener) throws IOException { try (FileInputStream in = new FileInputStream(from); FileOutputStream out = new FileOutputStream(to)) { - return copy(in, out, listener, signal); + return copy(in, out, signal, executor, listener); } } @@ -296,7 +321,7 @@ public class FileUtils { * @return number of bytes copied. */ public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException { - return copy(in, out, null, null); + return copy(in, out, null, null, null); } /** @@ -305,22 +330,23 @@ public class FileUtils { * Attempts to use several optimization strategies to copy the data in the * kernel before falling back to a userspace copy as a last resort. * - * @param listener to be periodically notified as the copy progresses. * @param signal to signal if the copy should be cancelled early. + * @param executor that listener events should be delivered via. + * @param listener to be periodically notified as the copy progresses. * @return number of bytes copied. */ public static long copy(@NonNull InputStream in, @NonNull OutputStream out, - @Nullable ProgressListener listener, @Nullable CancellationSignal signal) - throws IOException { + @Nullable CancellationSignal signal, @Nullable Executor executor, + @Nullable ProgressListener listener) throws IOException { if (ENABLE_COPY_OPTIMIZATIONS) { if (in instanceof FileInputStream && out instanceof FileOutputStream) { return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(), - listener, signal); + signal, executor, listener); } } // Worse case fallback to userspace - return copyInternalUserspace(in, out, listener, signal); + return copyInternalUserspace(in, out, signal, executor, listener); } /** @@ -333,7 +359,7 @@ public class FileUtils { */ public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out) throws IOException { - return copy(in, out, null, null); + return copy(in, out, null, null, null); } /** @@ -342,14 +368,15 @@ public class FileUtils { * Attempts to use several optimization strategies to copy the data in the * kernel before falling back to a userspace copy as a last resort. * - * @param listener to be periodically notified as the copy progresses. * @param signal to signal if the copy should be cancelled early. + * @param executor that listener events should be delivered via. + * @param listener to be periodically notified as the copy progresses. * @return number of bytes copied. */ public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, - @Nullable ProgressListener listener, @Nullable CancellationSignal signal) - throws IOException { - return copy(in, out, listener, signal, Long.MAX_VALUE); + @Nullable CancellationSignal signal, @Nullable Executor executor, + @Nullable ProgressListener listener) throws IOException { + return copy(in, out, Long.MAX_VALUE, signal, executor, listener); } /** @@ -358,22 +385,24 @@ public class FileUtils { * Attempts to use several optimization strategies to copy the data in the * kernel before falling back to a userspace copy as a last resort. * - * @param listener to be periodically notified as the copy progresses. - * @param signal to signal if the copy should be cancelled early. * @param count the number of bytes to copy. + * @param signal to signal if the copy should be cancelled early. + * @param executor that listener events should be delivered via. + * @param listener to be periodically notified as the copy progresses. * @return number of bytes copied. + * @hide */ - public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, - @Nullable ProgressListener listener, @Nullable CancellationSignal signal, long count) - throws IOException { + public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, long count, + @Nullable CancellationSignal signal, @Nullable Executor executor, + @Nullable ProgressListener listener) throws IOException { if (ENABLE_COPY_OPTIMIZATIONS) { try { final StructStat st_in = Os.fstat(in); final StructStat st_out = Os.fstat(out); if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) { - return copyInternalSendfile(in, out, listener, signal, count); + return copyInternalSendfile(in, out, count, signal, executor, listener); } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) { - return copyInternalSplice(in, out, listener, signal, count); + return copyInternalSplice(in, out, count, signal, executor, listener); } } catch (ErrnoException e) { throw e.rethrowAsIOException(); @@ -381,15 +410,17 @@ public class FileUtils { } // Worse case fallback to userspace - return copyInternalUserspace(in, out, listener, signal, count); + return copyInternalUserspace(in, out, count, signal, executor, listener); } /** * Requires one of input or output to be a pipe. + * + * @hide */ @VisibleForTesting - public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, - ProgressListener listener, CancellationSignal signal, long count) + public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count, + CancellationSignal signal, Executor executor, ProgressListener listener) throws ErrnoException { long progress = 0; long checkpoint = 0; @@ -405,24 +436,32 @@ public class FileUtils { if (signal != null) { signal.throwIfCanceled(); } - if (listener != null) { - listener.onProgress(progress); + if (executor != null && listener != null) { + final long progressSnapshot = progress; + executor.execute(() -> { + listener.onProgress(progressSnapshot); + }); } checkpoint = 0; } } - if (listener != null) { - listener.onProgress(progress); + if (executor != null && listener != null) { + final long progressSnapshot = progress; + executor.execute(() -> { + listener.onProgress(progressSnapshot); + }); } return progress; } /** * Requires both input and output to be a regular file. + * + * @hide */ @VisibleForTesting - public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, - ProgressListener listener, CancellationSignal signal, long count) + public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count, + CancellationSignal signal, Executor executor, ProgressListener listener) throws ErrnoException { long progress = 0; long checkpoint = 0; @@ -437,33 +476,52 @@ public class FileUtils { if (signal != null) { signal.throwIfCanceled(); } - if (listener != null) { - listener.onProgress(progress); + if (executor != null && listener != null) { + final long progressSnapshot = progress; + executor.execute(() -> { + listener.onProgress(progressSnapshot); + }); } checkpoint = 0; } } - if (listener != null) { - listener.onProgress(progress); + if (executor != null && listener != null) { + final long progressSnapshot = progress; + executor.execute(() -> { + listener.onProgress(progressSnapshot); + }); } return progress; } + /** {@hide} */ + @Deprecated @VisibleForTesting public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, - ProgressListener listener, CancellationSignal signal, long count) throws IOException { + ProgressListener listener, CancellationSignal signal, long count) + throws IOException { + return copyInternalUserspace(in, out, count, signal, Runnable::run, listener); + } + + /** {@hide} */ + @VisibleForTesting + public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, long count, + CancellationSignal signal, Executor executor, ProgressListener listener) + throws IOException { if (count != Long.MAX_VALUE) { return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count), - new FileOutputStream(out), listener, signal); + new FileOutputStream(out), signal, executor, listener); } else { return copyInternalUserspace(new FileInputStream(in), - new FileOutputStream(out), listener, signal); + new FileOutputStream(out), signal, executor, listener); } } + /** {@hide} */ @VisibleForTesting public static long copyInternalUserspace(InputStream in, OutputStream out, - ProgressListener listener, CancellationSignal signal) throws IOException { + CancellationSignal signal, Executor executor, ProgressListener listener) + throws IOException { long progress = 0; long checkpoint = 0; byte[] buffer = new byte[8192]; @@ -479,14 +537,20 @@ public class FileUtils { if (signal != null) { signal.throwIfCanceled(); } - if (listener != null) { - listener.onProgress(progress); + if (executor != null && listener != null) { + final long progressSnapshot = progress; + executor.execute(() -> { + listener.onProgress(progressSnapshot); + }); } checkpoint = 0; } } - if (listener != null) { - listener.onProgress(progress); + if (executor != null && listener != null) { + final long progressSnapshot = progress; + executor.execute(() -> { + listener.onProgress(progressSnapshot); + }); } return progress; } @@ -494,6 +558,7 @@ public class FileUtils { /** * Check if a filename is "safe" (no metacharacters or spaces). * @param file The file to check + * @hide */ public static boolean isFilenameSafe(File file) { // Note, we check whether it matches what's known to be safe, @@ -509,6 +574,7 @@ public class FileUtils { * @param ellipsis to add of the file was truncated (can be null) * @return the contents of the file, possibly truncated * @throws IOException if something goes wrong reading the file + * @hide */ public static String readTextFile(File file, int max, String ellipsis) throws IOException { InputStream input = new FileInputStream(file); @@ -563,13 +629,16 @@ public class FileUtils { } } + /** {@hide} */ public static void stringToFile(File file, String string) throws IOException { stringToFile(file.getAbsolutePath(), string); } - /* + /** * Writes the bytes given in {@code content} to the file whose absolute path * is {@code filename}. + * + * @hide */ public static void bytesToFile(String filename, byte[] content) throws IOException { if (filename.startsWith("/proc/")) { @@ -592,18 +661,23 @@ public class FileUtils { * @param filename * @param string * @throws IOException + * @hide */ public static void stringToFile(String filename, String string) throws IOException { bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8)); } /** - * Computes the checksum of a file using the CRC32 checksum routine. - * The value of the checksum is returned. + * Computes the checksum of a file using the CRC32 checksum routine. The + * value of the checksum is returned. * - * @param file the file to checksum, must not be null + * @param file the file to checksum, must not be null * @return the checksum value or an exception is thrown. + * @deprecated this is a weak hashing algorithm, and should not be used due + * to its potential for collision. + * @hide */ + @Deprecated public static long checksumCrc32(File file) throws FileNotFoundException, IOException { CRC32 checkSummer = new CRC32(); CheckedInputStream cis = null; @@ -632,6 +706,7 @@ public class FileUtils { * @param minCount Always keep at least this many files. * @param minAgeMs Always keep files younger than this age, in milliseconds. * @return if any files were deleted. + * @hide */ public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) { if (minCount < 0 || minAgeMs < 0) { @@ -673,6 +748,8 @@ public class FileUtils { * Both files <em>must</em> have been resolved using * {@link File#getCanonicalFile()} to avoid symlink or path traversal * attacks. + * + * @hide */ public static boolean contains(File[] dirs, File file) { for (File dir : dirs) { @@ -690,12 +767,15 @@ public class FileUtils { * Both files <em>must</em> have been resolved using * {@link File#getCanonicalFile()} to avoid symlink or path traversal * attacks. + * + * @hide */ public static boolean contains(File dir, File file) { if (dir == null || file == null) return false; return contains(dir.getAbsolutePath(), file.getAbsolutePath()); } + /** {@hide} */ public static boolean contains(String dirPath, String filePath) { if (dirPath.equals(filePath)) { return true; @@ -706,6 +786,7 @@ public class FileUtils { return filePath.startsWith(dirPath); } + /** {@hide} */ public static boolean deleteContentsAndDir(File dir) { if (deleteContents(dir)) { return dir.delete(); @@ -714,6 +795,7 @@ public class FileUtils { } } + /** {@hide} */ public static boolean deleteContents(File dir) { File[] files = dir.listFiles(); boolean success = true; @@ -743,6 +825,8 @@ public class FileUtils { /** * Check if given filename is valid for an ext4 filesystem. + * + * @hide */ public static boolean isValidExtFilename(String name) { return (name != null) && name.equals(buildValidExtFilename(name)); @@ -751,6 +835,8 @@ public class FileUtils { /** * Mutate the given filename to make it valid for an ext4 filesystem, * replacing any invalid characters with "_". + * + * @hide */ public static String buildValidExtFilename(String name) { if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { @@ -792,6 +878,8 @@ public class FileUtils { /** * Check if given filename is valid for a FAT filesystem. + * + * @hide */ public static boolean isValidFatFilename(String name) { return (name != null) && name.equals(buildValidFatFilename(name)); @@ -800,6 +888,8 @@ public class FileUtils { /** * Mutate the given filename to make it valid for a FAT filesystem, * replacing any invalid characters with "_". + * + * @hide */ public static String buildValidFatFilename(String name) { if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { @@ -820,6 +910,7 @@ public class FileUtils { return res.toString(); } + /** {@hide} */ @VisibleForTesting public static String trimFilename(String str, int maxBytes) { final StringBuilder res = new StringBuilder(str); @@ -827,6 +918,7 @@ public class FileUtils { return res.toString(); } + /** {@hide} */ private static void trimFilename(StringBuilder res, int maxBytes) { byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8); if (raw.length > maxBytes) { @@ -839,12 +931,14 @@ public class FileUtils { } } + /** {@hide} */ public static String rewriteAfterRename(File beforeDir, File afterDir, String path) { if (path == null) return null; final File result = rewriteAfterRename(beforeDir, afterDir, new File(path)); return (result != null) ? result.getAbsolutePath() : null; } + /** {@hide} */ public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) { if (paths == null) return null; final String[] result = new String[paths.length]; @@ -858,6 +952,8 @@ public class FileUtils { * Given a path under the "before" directory, rewrite it to live under the * "after" directory. For example, {@code /before/foo/bar.txt} would become * {@code /after/foo/bar.txt}. + * + * @hide */ public static File rewriteAfterRename(File beforeDir, File afterDir, File file) { if (file == null || beforeDir == null || afterDir == null) return null; @@ -869,6 +965,7 @@ public class FileUtils { return null; } + /** {@hide} */ private static File buildUniqueFileWithExtension(File parent, String name, String ext) throws FileNotFoundException { File file = buildFile(parent, name, ext); @@ -895,6 +992,7 @@ public class FileUtils { * 'example.txt' or 'example (1).txt', etc. * * @throws FileNotFoundException + * @hide */ public static File buildUniqueFile(File parent, String mimeType, String displayName) throws FileNotFoundException { @@ -905,6 +1003,8 @@ public class FileUtils { /** * Generates a unique file name under the given parent directory, keeping * any extension intact. + * + * @hide */ public static File buildUniqueFile(File parent, String displayName) throws FileNotFoundException { @@ -929,6 +1029,8 @@ public class FileUtils { * If the display name doesn't have an extension that matches the requested MIME type, the * extension is regarded as a part of filename and default extension for that MIME type is * appended. + * + * @hide */ public static String[] splitFileName(String mimeType, String displayName) { String name; @@ -975,6 +1077,7 @@ public class FileUtils { return new String[] { name, ext }; } + /** {@hide} */ private static File buildFile(File parent, String name, String ext) { if (TextUtils.isEmpty(ext)) { return new File(parent, name); @@ -983,6 +1086,7 @@ public class FileUtils { } } + /** {@hide} */ public static @NonNull String[] listOrEmpty(@Nullable File dir) { if (dir == null) return EmptyArray.STRING; final String[] res = dir.list(); @@ -993,6 +1097,7 @@ public class FileUtils { } } + /** {@hide} */ public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) { if (dir == null) return EMPTY; final File[] res = dir.listFiles(); @@ -1003,6 +1108,7 @@ public class FileUtils { } } + /** {@hide} */ public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) { if (dir == null) return EMPTY; final File[] res = dir.listFiles(filter); @@ -1013,6 +1119,7 @@ public class FileUtils { } } + /** {@hide} */ public static @Nullable File newFileOrNull(@Nullable String path) { return (path != null) ? new File(path) : null; } @@ -1021,6 +1128,8 @@ public class FileUtils { * Creates a directory with name {@code name} under an existing directory {@code baseDir}. * Returns a {@code File} object representing the directory on success, {@code null} on * failure. + * + * @hide */ public static @Nullable File createDir(File baseDir, String name) { final File dir = new File(baseDir, name); @@ -1036,6 +1145,8 @@ public class FileUtils { * Round the given size of a storage device to a nice round power-of-two * value, such as 256MB or 32GB. This avoids showing weird values like * "29.5GB" in UI. + * + * @hide */ public static long roundStorageSize(long size) { long val = 1; @@ -1050,6 +1161,23 @@ public class FileUtils { return val * pow; } + /** + * Closes the given object quietly, ignoring any checked exceptions. Does + * nothing if the given object is {@code null}. + */ + public static void closeQuietly(@Nullable AutoCloseable closeable) { + IoUtils.closeQuietly(closeable); + } + + /** + * Closes the given object quietly, ignoring any checked exceptions. Does + * nothing if the given object is {@code null}. + */ + public static void closeQuietly(@Nullable FileDescriptor fd) { + IoUtils.closeQuietly(fd); + } + + /** {@hide} */ @VisibleForTesting public static class MemoryPipe extends Thread implements AutoCloseable { private final FileDescriptor[] pipe; |
