FileUtils.java revision 373d01766f27476e81a174727dcfeee406742417
19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/*
29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2006 The Android Open Source Project
39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License.
69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at
79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and
149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License.
159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.os;
189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1985ced632680642fce680d141ddd10299ff849233Jeff Sharkeyimport android.annotation.NonNull;
2015447798a38d2b5acb1998731340255f4203f294Jeff Sharkeyimport android.annotation.Nullable;
2162539a220c6810f66b63060326bd1668f7d6b029Ben Kwaimport android.provider.DocumentsContract.Document;
2234385d352da19805ae948215e2edbeedd16b7941Elliott Hughesimport android.system.ErrnoException;
2334385d352da19805ae948215e2edbeedd16b7941Elliott Hughesimport android.system.Os;
2435871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkeyimport android.system.StructStat;
250cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkeyimport android.text.TextUtils;
26d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkeyimport android.util.Log;
27184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkeyimport android.util.Slog;
2862539a220c6810f66b63060326bd1668f7d6b029Ben Kwaimport android.webkit.MimeTypeMap;
29184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
304f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkeyimport com.android.internal.annotations.VisibleForTesting;
314f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey
32c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkeyimport libcore.util.EmptyArray;
33c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey
3490619816d99154d504a14774c6f2d5f4254ff780Guang Zhuimport java.io.BufferedInputStream;
359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.ByteArrayOutputStream;
369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.File;
37184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkeyimport java.io.FileDescriptor;
389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileInputStream;
396d25a990afffd5eb385aba3043d5dfad36f1539aWink Savilleimport java.io.FileNotFoundException;
409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileOutputStream;
4135871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkeyimport java.io.FilenameFilter;
429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.IOException;
439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.InputStream;
444f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkeyimport java.nio.charset.StandardCharsets;
45d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkeyimport java.util.Arrays;
46d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkeyimport java.util.Comparator;
4762539a220c6810f66b63060326bd1668f7d6b029Ben Kwaimport java.util.Objects;
489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.regex.Pattern;
496d25a990afffd5eb385aba3043d5dfad36f1539aWink Savilleimport java.util.zip.CRC32;
506d25a990afffd5eb385aba3043d5dfad36f1539aWink Savilleimport java.util.zip.CheckedInputStream;
519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/**
539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Tools for managing files.  Not for public consumption.
549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @hide
559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
566d25a990afffd5eb385aba3043d5dfad36f1539aWink Savillepublic class FileUtils {
57d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey    private static final String TAG = "FileUtils";
58d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRWXU = 00700;
609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRUSR = 00400;
619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IWUSR = 00200;
629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IXUSR = 00100;
639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRWXG = 00070;
659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRGRP = 00040;
669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IWGRP = 00020;
679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IXGRP = 00010;
689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRWXO = 00007;
709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IROTH = 00004;
719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IWOTH = 00002;
729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IXOTH = 00001;
739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
740693fd85f69db553204168eeb342e6bf0b5fe7b5Andreas Gampe    /** Regular expression for safe filenames: no spaces or metacharacters.
750693fd85f69db553204168eeb342e6bf0b5fe7b5Andreas Gampe      *
760693fd85f69db553204168eeb342e6bf0b5fe7b5Andreas Gampe      * Use a preload holder so that FileUtils can be compile-time initialized.
770693fd85f69db553204168eeb342e6bf0b5fe7b5Andreas Gampe      */
780693fd85f69db553204168eeb342e6bf0b5fe7b5Andreas Gampe    private static class NoImagePreloadHolder {
790693fd85f69db553204168eeb342e6bf0b5fe7b5Andreas Gampe        public static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
800693fd85f69db553204168eeb342e6bf0b5fe7b5Andreas Gampe    }
819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
8285ced632680642fce680d141ddd10299ff849233Jeff Sharkey    private static final File[] EMPTY = new File[0];
8385ced632680642fce680d141ddd10299ff849233Jeff Sharkey
84184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    /**
85184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * Set owner and mode of of given {@link File}.
86184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     *
87184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param mode to apply through {@code chmod}
88184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param uid to apply through {@code chown}, or -1 to leave unchanged
89184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param gid to apply through {@code chown}, or -1 to leave unchanged
90184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @return 0 on success, otherwise errno.
91184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     */
92184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    public static int setPermissions(File path, int mode, int uid, int gid) {
93184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        return setPermissions(path.getAbsolutePath(), mode, uid, gid);
94184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    }
95184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
96184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    /**
97184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * Set owner and mode of of given path.
98184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     *
99184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param mode to apply through {@code chmod}
100184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param uid to apply through {@code chown}, or -1 to leave unchanged
101184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param gid to apply through {@code chown}, or -1 to leave unchanged
102184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @return 0 on success, otherwise errno.
103184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     */
104184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    public static int setPermissions(String path, int mode, int uid, int gid) {
105184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        try {
10634385d352da19805ae948215e2edbeedd16b7941Elliott Hughes            Os.chmod(path, mode);
107184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        } catch (ErrnoException e) {
108184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
109184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            return e.errno;
110184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
111184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
112184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        if (uid >= 0 || gid >= 0) {
113184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            try {
11434385d352da19805ae948215e2edbeedd16b7941Elliott Hughes                Os.chown(path, uid, gid);
115184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            } catch (ErrnoException e) {
116184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey                Slog.w(TAG, "Failed to chown(" + path + "): " + e);
117184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey                return e.errno;
118184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            }
119184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
120184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
121184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        return 0;
122184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    }
1239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
124184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    /**
125184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * Set owner and mode of of given {@link FileDescriptor}.
126184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     *
127184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param mode to apply through {@code chmod}
128184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param uid to apply through {@code chown}, or -1 to leave unchanged
129184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param gid to apply through {@code chown}, or -1 to leave unchanged
130184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @return 0 on success, otherwise errno.
131184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     */
132184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
133184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        try {
13434385d352da19805ae948215e2edbeedd16b7941Elliott Hughes            Os.fchmod(fd, mode);
135184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        } catch (ErrnoException e) {
136184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            Slog.w(TAG, "Failed to fchmod(): " + e);
137184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            return e.errno;
138184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
139184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
140184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        if (uid >= 0 || gid >= 0) {
141184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            try {
14234385d352da19805ae948215e2edbeedd16b7941Elliott Hughes                Os.fchown(fd, uid, gid);
143184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            } catch (ErrnoException e) {
144184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey                Slog.w(TAG, "Failed to fchown(): " + e);
145184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey                return e.errno;
146184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            }
147184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
148184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
149184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        return 0;
150184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    }
151184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
15235871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    public static void copyPermissions(File from, File to) throws IOException {
15335871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        try {
15435871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            final StructStat stat = Os.stat(from.getAbsolutePath());
15535871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            Os.chmod(to.getAbsolutePath(), stat.st_mode);
15635871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            Os.chown(to.getAbsolutePath(), stat.st_uid, stat.st_gid);
15735871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        } catch (ErrnoException e) {
15835871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            throw e.rethrowAsIOException();
15935871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        }
16035871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    }
16135871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey
162184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    /**
163184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * Return owning UID of given path, otherwise -1.
164184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     */
165184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    public static int getUid(String path) {
166184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        try {
16734385d352da19805ae948215e2edbeedd16b7941Elliott Hughes            return Os.stat(path).st_uid;
168184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        } catch (ErrnoException e) {
169184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            return -1;
170184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
171184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    }
172053f61d6a6e23825e680dc49982e55c5b4299d61Dianne Hackborn
1738bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn    /**
1748bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn     * Perform an fsync on the given FileOutputStream.  The stream at this
1758bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn     * point must be flushed but not yet closed.
1768bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn     */
1778bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn    public static boolean sync(FileOutputStream stream) {
1788bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn        try {
1798bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            if (stream != null) {
1808bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn                stream.getFD().sync();
1818bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            }
1828bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            return true;
1838bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn        } catch (IOException e) {
1848bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn        }
1858bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn        return false;
1868bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn    }
1878bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn
18835871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    @Deprecated
18935871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    public static boolean copyFile(File srcFile, File destFile) {
19035871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        try {
19135871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            copyFileOrThrow(srcFile, destFile);
19235871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            return true;
19335871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        } catch (IOException e) {
19435871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            return false;
19535871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        }
19635871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    }
19735871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey
1989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    // copy a file from srcFile to destFile, return true if succeed, return
1999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    // false if fail
20035871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    public static void copyFileOrThrow(File srcFile, File destFile) throws IOException {
20135871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        try (InputStream in = new FileInputStream(srcFile)) {
20235871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            copyToFileOrThrow(in, destFile);
20335871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        }
20435871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    }
20535871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey
20635871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    @Deprecated
20735871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    public static boolean copyToFile(InputStream inputStream, File destFile) {
2089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
20935871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            copyToFileOrThrow(inputStream, destFile);
21035871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            return true;
2119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (IOException e) {
21235871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            return false;
2139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
21590619816d99154d504a14774c6f2d5f4254ff780Guang Zhu
2169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
2179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Copy data from a source stream to destFile.
2189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Return true if succeed, return false if failed.
2199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
22035871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    public static void copyToFileOrThrow(InputStream inputStream, File destFile)
22135871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            throws IOException {
22235871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        if (destFile.exists()) {
22335871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            destFile.delete();
22435871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        }
22535871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        FileOutputStream out = new FileOutputStream(destFile);
2269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
22735871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            byte[] buffer = new byte[4096];
22835871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            int bytesRead;
22935871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            while ((bytesRead = inputStream.read(buffer)) >= 0) {
23035871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey                out.write(buffer, 0, bytesRead);
2311afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn            }
23235871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        } finally {
23335871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            out.flush();
2349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            try {
23535871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey                out.getFD().sync();
23635871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            } catch (IOException e) {
2379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
23835871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            out.close();
2399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
2419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
2439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Check if a filename is "safe" (no metacharacters or spaces).
2449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param file  The file to check
2459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
2469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static boolean isFilenameSafe(File file) {
2479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // Note, we check whether it matches what's known to be safe,
2489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // rather than what's known to be unsafe.  Non-ASCII, control
2499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // characters, etc. are all unsafe by default.
2500693fd85f69db553204168eeb342e6bf0b5fe7b5Andreas Gampe        return NoImagePreloadHolder.SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches();
2519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
2529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
2549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Read a text file into a String, optionally limiting the length.
2559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param file to read (will not seek, so things like /proc files are OK)
2569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param max length (positive for head, negative of tail, 0 for no limit)
2579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param ellipsis to add of the file was truncated (can be null)
2589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @return the contents of the file, possibly truncated
2599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @throws IOException if something goes wrong reading the file
2609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
2619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static String readTextFile(File file, int max, String ellipsis) throws IOException {
2629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        InputStream input = new FileInputStream(file);
26390619816d99154d504a14774c6f2d5f4254ff780Guang Zhu        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
26490619816d99154d504a14774c6f2d5f4254ff780Guang Zhu        // input stream, bytes read not equal to buffer size is not necessarily the correct
26590619816d99154d504a14774c6f2d5f4254ff780Guang Zhu        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
26690619816d99154d504a14774c6f2d5f4254ff780Guang Zhu        BufferedInputStream bis = new BufferedInputStream(input);
2679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
26842471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor            long size = file.length();
26942471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
27042471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor                if (size > 0 && (max == 0 || size < max)) max = (int) size;
2719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                byte[] data = new byte[max + 1];
27290619816d99154d504a14774c6f2d5f4254ff780Guang Zhu                int length = bis.read(data);
2739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (length <= 0) return "";
2749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (length <= max) return new String(data, 0, length);
2759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (ellipsis == null) return new String(data, 0, max);
2769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                return new String(data, 0, max) + ellipsis;
27742471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor            } else if (max < 0) {  // "tail" mode: keep the last N
2789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                int len;
2799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                boolean rolled = false;
280d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey                byte[] last = null;
281d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey                byte[] data = null;
2829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                do {
2839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (last != null) rolled = true;
2849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    byte[] tmp = last; last = data; data = tmp;
2859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (data == null) data = new byte[-max];
28690619816d99154d504a14774c6f2d5f4254ff780Guang Zhu                    len = bis.read(data);
2879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                } while (len == data.length);
2889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (last == null && len <= 0) return "";
2909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (last == null) return new String(data, 0, len);
2919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (len > 0) {
2929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    rolled = true;
2939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    System.arraycopy(last, len, last, 0, last.length - len);
2949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    System.arraycopy(data, 0, last, last.length - len, len);
2959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
2969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (ellipsis == null || !rolled) return new String(last);
2979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                return ellipsis + new String(last);
29842471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor            } else {  // "cat" mode: size unknown, read it all in streaming fashion
2999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                ByteArrayOutputStream contents = new ByteArrayOutputStream();
3009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                int len;
3019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                byte[] data = new byte[1024];
3029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                do {
30390619816d99154d504a14774c6f2d5f4254ff780Guang Zhu                    len = bis.read(data);
3049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (len > 0) contents.write(data, 0, len);
3059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                } while (len == data.length);
3069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                return contents.toString();
3079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
3089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } finally {
30990619816d99154d504a14774c6f2d5f4254ff780Guang Zhu            bis.close();
3109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            input.close();
3119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
3129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
313da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood
3142271ba3627d18b65ed5ea63218cee7f9562acd31Jeff Sharkey    public static void stringToFile(File file, String string) throws IOException {
3152271ba3627d18b65ed5ea63218cee7f9562acd31Jeff Sharkey        stringToFile(file.getAbsolutePath(), string);
3162271ba3627d18b65ed5ea63218cee7f9562acd31Jeff Sharkey    }
3172271ba3627d18b65ed5ea63218cee7f9562acd31Jeff Sharkey
3186d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath    /*
3196d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath     * Writes the bytes given in {@code content} to the file whose absolute path
3206d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath     * is {@code filename}.
3216d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath     */
3226d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath    public static void bytesToFile(String filename, byte[] content) throws IOException {
3236d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath        try (FileOutputStream fos = new FileOutputStream(filename)) {
3246d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath            fos.write(content);
3256d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath        }
3266d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath    }
3276d051fc68d1b1f2c09eb2e58c26821e1e13935dcNarayan Kamath
3282271ba3627d18b65ed5ea63218cee7f9562acd31Jeff Sharkey    /**
329da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     * Writes string to file. Basically same as "echo -n $string > $filename"
330da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     *
331da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     * @param filename
332da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     * @param string
333da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     * @throws IOException
334da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     */
335da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood    public static void stringToFile(String filename, String string) throws IOException {
336032c08ad6bf21c26347b4acca4c5ca6e1557249eJeff Sharkey        bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8));
337da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood    }
3381b9a6a6e58fd73b5d1b6a434d17f0a69806858ecWink Saville
3396d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville    /**
3406d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     * Computes the checksum of a file using the CRC32 checksum routine.
3416d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     * The value of the checksum is returned.
3426d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     *
3436d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     * @param file  the file to checksum, must not be null
3446d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     * @return the checksum value or an exception is thrown.
3456d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     */
3466d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville    public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
3476d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        CRC32 checkSummer = new CRC32();
3486d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        CheckedInputStream cis = null;
3496d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville
3506d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        try {
3516d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            cis = new CheckedInputStream( new FileInputStream(file), checkSummer);
3526d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            byte[] buf = new byte[128];
3536d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            while(cis.read(buf) >= 0) {
3546d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                // Just read for checksum to get calculated.
3556d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            }
3566d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            return checkSummer.getValue();
3576d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        } finally {
3586d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            if (cis != null) {
3596d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                try {
3606d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                    cis.close();
3616d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                } catch (IOException e) {
3626d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                }
3636d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            }
3646d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        }
3656d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville    }
366d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
367d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey    /**
368d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     * Delete older files in a directory until only those matching the given
369d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     * constraints remain.
370d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     *
371d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     * @param minCount Always keep at least this many files.
372d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     * @param minAge Always keep files younger than this age.
373ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey     * @return if any files were deleted.
374d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     */
375ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey    public static boolean deleteOlderFiles(File dir, int minCount, long minAge) {
376d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        if (minCount < 0 || minAge < 0) {
377d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            throw new IllegalArgumentException("Constraints must be positive or 0");
378d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        }
379d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
380d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        final File[] files = dir.listFiles();
381ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey        if (files == null) return false;
382d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
383d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        // Sort with newest files first
384d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        Arrays.sort(files, new Comparator<File>() {
385d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            @Override
386d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            public int compare(File lhs, File rhs) {
387660e6def20d1b7b9b6edb8e70ec04d17b069af5bIan Rogers                return Long.compare(rhs.lastModified(), lhs.lastModified());
388d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            }
389d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        });
390d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
391d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        // Keep at least minCount files
392ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey        boolean deleted = false;
393d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        for (int i = minCount; i < files.length; i++) {
394d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            final File file = files[i];
395d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
396d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            // Keep files newer than minAge
397d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            final long age = System.currentTimeMillis() - file.lastModified();
398d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            if (age > minAge) {
399ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey                if (file.delete()) {
400ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey                    Log.d(TAG, "Deleted old file " + file);
401ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey                    deleted = true;
402ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey                }
403d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            }
404d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        }
405ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey        return deleted;
406d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey    }
4074ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey
4084ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey    /**
4094ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * Test if a file lives under the given directory, either as a direct child
4104ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * or a distant grandchild.
4114ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * <p>
4124ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * Both files <em>must</em> have been resolved using
4134ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
4144ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * attacks.
4154ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     */
4164887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey    public static boolean contains(File[] dirs, File file) {
4174887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey        for (File dir : dirs) {
4184887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey            if (contains(dir, file)) {
4194887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey                return true;
4204887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey            }
4214887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey        }
4224887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey        return false;
4234887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey    }
4244887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey
4254887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey    /**
4264887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * Test if a file lives under the given directory, either as a direct child
4274887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * or a distant grandchild.
4284887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * <p>
4294887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * Both files <em>must</em> have been resolved using
4304887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
4314887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * attacks.
4324887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     */
4334ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey    public static boolean contains(File dir, File file) {
43450a05454795c93ac483f5cb6819e74cb17be1b5bJeff Sharkey        if (dir == null || file == null) return false;
435d5d5e926eb8ff2541a845d6a5657cee5e5c6e7b6Jeff Sharkey        return contains(dir.getAbsolutePath(), file.getAbsolutePath());
436d5d5e926eb8ff2541a845d6a5657cee5e5c6e7b6Jeff Sharkey    }
437d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey
438d5d5e926eb8ff2541a845d6a5657cee5e5c6e7b6Jeff Sharkey    public static boolean contains(String dirPath, String filePath) {
4394ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        if (dirPath.equals(filePath)) {
4404ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey            return true;
4414ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        }
4424ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        if (!dirPath.endsWith("/")) {
4434ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey            dirPath += "/";
4444ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        }
4454ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        return filePath.startsWith(dirPath);
4464ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey    }
4473a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey
448fcf1e55821b694df3b8434f40aa3b6d3c3e7ea50Jeff Sharkey    public static boolean deleteContentsAndDir(File dir) {
449fcf1e55821b694df3b8434f40aa3b6d3c3e7ea50Jeff Sharkey        if (deleteContents(dir)) {
450fcf1e55821b694df3b8434f40aa3b6d3c3e7ea50Jeff Sharkey            return dir.delete();
451fcf1e55821b694df3b8434f40aa3b6d3c3e7ea50Jeff Sharkey        } else {
452fcf1e55821b694df3b8434f40aa3b6d3c3e7ea50Jeff Sharkey            return false;
453fcf1e55821b694df3b8434f40aa3b6d3c3e7ea50Jeff Sharkey        }
454fcf1e55821b694df3b8434f40aa3b6d3c3e7ea50Jeff Sharkey    }
455fcf1e55821b694df3b8434f40aa3b6d3c3e7ea50Jeff Sharkey
45657dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    public static boolean deleteContents(File dir) {
4573a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        File[] files = dir.listFiles();
45857dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        boolean success = true;
4593a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        if (files != null) {
4603a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey            for (File file : files) {
4613a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey                if (file.isDirectory()) {
46257dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey                    success &= deleteContents(file);
4633a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey                }
46473767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey                if (!file.delete()) {
46573767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey                    Log.w(TAG, "Failed to delete " + file);
46673767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey                    success = false;
46773767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey                }
4683a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey            }
4693a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        }
47057dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        return success;
4713a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey    }
4723a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey
4730cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    private static boolean isValidExtFilenameChar(char c) {
4740cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        switch (c) {
4750cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '\0':
4760cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '/':
4770cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                return false;
4780cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            default:
4790cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                return true;
4800cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
4810cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
4820cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
4833a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey    /**
4840cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * Check if given filename is valid for an ext4 filesystem.
4853a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey     */
4863a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey    public static boolean isValidExtFilename(String name) {
4870cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        return (name != null) && name.equals(buildValidExtFilename(name));
4880cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
4890cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
4900cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    /**
4910cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * Mutate the given filename to make it valid for an ext4 filesystem,
4920cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * replacing any invalid characters with "_".
4930cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     */
4940cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    public static String buildValidExtFilename(String name) {
4953a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
4960cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            return "(invalid)";
4973a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        }
4980cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        final StringBuilder res = new StringBuilder(name.length());
4993a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        for (int i = 0; i < name.length(); i++) {
5003a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey            final char c = name.charAt(i);
5010cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            if (isValidExtFilenameChar(c)) {
5020cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                res.append(c);
5030cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            } else {
5040cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                res.append('_');
5050cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            }
5060cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
5074f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        trimFilename(res, 255);
5080cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        return res.toString();
5090cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
5100cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
5110cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    private static boolean isValidFatFilenameChar(char c) {
5120cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        if ((0x00 <= c && c <= 0x1f)) {
5130cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            return false;
5140cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
5150cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        switch (c) {
5160cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '"':
5170cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '*':
5180cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '/':
5190cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case ':':
5200cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '<':
5210cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '>':
5220cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '?':
5230cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '\\':
5240cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '|':
5250cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case 0x7F:
5263a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey                return false;
5270cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            default:
5280cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                return true;
5290cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
5300cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
5310cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
5320cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    /**
5330cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * Check if given filename is valid for a FAT filesystem.
5340cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     */
5350cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    public static boolean isValidFatFilename(String name) {
5360cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        return (name != null) && name.equals(buildValidFatFilename(name));
5370cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
5380cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
5390cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    /**
5400cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * Mutate the given filename to make it valid for a FAT filesystem,
5410cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * replacing any invalid characters with "_".
5420cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     */
5430cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    public static String buildValidFatFilename(String name) {
5440cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
5450cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            return "(invalid)";
5460cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
5470cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        final StringBuilder res = new StringBuilder(name.length());
5480cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        for (int i = 0; i < name.length(); i++) {
5490cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            final char c = name.charAt(i);
5500cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            if (isValidFatFilenameChar(c)) {
5510cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                res.append(c);
5520cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            } else {
5530cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                res.append('_');
5543a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey            }
5553a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        }
5564f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        // Even though vfat allows 255 UCS-2 chars, we might eventually write to
5574f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        // ext4 through a FUSE layer, so use that limit.
5584f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        trimFilename(res, 255);
5594f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        return res.toString();
5604f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    }
5614f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey
5624f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    @VisibleForTesting
5634f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    public static String trimFilename(String str, int maxBytes) {
5644f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        final StringBuilder res = new StringBuilder(str);
5654f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        trimFilename(res, maxBytes);
5660cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        return res.toString();
5673a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey    }
56857dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey
5694f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    private static void trimFilename(StringBuilder res, int maxBytes) {
5704f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
5714f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        if (raw.length > maxBytes) {
5724f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey            maxBytes -= 3;
5734f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey            while (raw.length > maxBytes) {
5744f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey                res.deleteCharAt(res.length() / 2);
5754f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey                raw = res.toString().getBytes(StandardCharsets.UTF_8);
5764f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey            }
5774f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey            res.insert(res.length() / 2, "...");
5784f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        }
5794f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    }
5804f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey
58157dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
582d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        if (path == null) return null;
58357dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
58457dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        return (result != null) ? result.getAbsolutePath() : null;
58557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    }
58657dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey
587d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey    public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
588d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        if (paths == null) return null;
589d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        final String[] result = new String[paths.length];
590d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        for (int i = 0; i < paths.length; i++) {
591d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey            result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]);
592d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        }
593d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        return result;
594d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey    }
595d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey
59657dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    /**
59757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey     * Given a path under the "before" directory, rewrite it to live under the
59857dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey     * "after" directory. For example, {@code /before/foo/bar.txt} would become
59957dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey     * {@code /after/foo/bar.txt}.
60057dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey     */
60157dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
60241be35dd011dbd7ed3a9d08e8fe3eb85ea0e5b64Jeff Sharkey        if (file == null || beforeDir == null || afterDir == null) return null;
60357dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        if (contains(beforeDir, file)) {
60457dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey            final String splice = file.getAbsolutePath().substring(
60557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey                    beforeDir.getAbsolutePath().length());
60657dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey            return new File(afterDir, splice);
60757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        }
60857dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        return null;
60957dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    }
61062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
611aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey    private static File buildUniqueFileWithExtension(File parent, String name, String ext)
612aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey            throws FileNotFoundException {
613aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        File file = buildFile(parent, name, ext);
614aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey
615aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        // If conflicting file, try adding counter suffix
616aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        int n = 0;
617aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        while (file.exists()) {
618aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey            if (n++ >= 32) {
619aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey                throw new FileNotFoundException("Failed to create unique file");
620aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey            }
621aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey            file = buildFile(parent, name + " (" + n + ")", ext);
622aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        }
623aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey
624aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        return file;
625aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey    }
626aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey
62762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    /**
62862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * Generates a unique file name under the given parent directory. If the display name doesn't
62962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * have an extension that matches the requested MIME type, the default extension for that MIME
63062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * type is appended. If a file already exists, the name is appended with a numerical value to
63162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * make it unique.
63262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     *
63362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * For example, the display name 'example' with 'text/plain' MIME might produce
63462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * 'example.txt' or 'example (1).txt', etc.
63562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     *
63662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * @throws FileNotFoundException
63762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     */
63862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    public static File buildUniqueFile(File parent, String mimeType, String displayName)
63962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            throws FileNotFoundException {
640fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final String[] parts = splitFileName(mimeType, displayName);
641aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        return buildUniqueFileWithExtension(parent, parts[0], parts[1]);
642aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey    }
643fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
644aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey    /**
645aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey     * Generates a unique file name under the given parent directory, keeping
646aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey     * any extension intact.
647aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey     */
648aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey    public static File buildUniqueFile(File parent, String displayName)
649aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey            throws FileNotFoundException {
650aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        final String name;
651aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        final String ext;
652aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey
653aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        // Extract requested extension from display name
654aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        final int lastDot = displayName.lastIndexOf('.');
655aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        if (lastDot >= 0) {
656aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey            name = displayName.substring(0, lastDot);
657aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey            ext = displayName.substring(lastDot + 1);
658aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        } else {
659aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey            name = displayName;
660aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey            ext = null;
661fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        }
662fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
663aa44476f64ed5d47b21bc313d065964b065ce918Jeff Sharkey        return buildUniqueFileWithExtension(parent, name, ext);
664fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono    }
665fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
666fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono    /**
667fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono     * Splits file name into base name and extension.
668fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono     * If the display name doesn't have an extension that matches the requested MIME type, the
669fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono     * extension is regarded as a part of filename and default extension for that MIME type is
670fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono     * appended.
671fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono     */
672fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono    public static String[] splitFileName(String mimeType, String displayName) {
67362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        String name;
67462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        String ext;
67562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
67662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
67762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            name = displayName;
67862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            ext = null;
67962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        } else {
68062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            String mimeTypeFromExt;
68162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
68262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            // Extract requested extension from display name
68362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            final int lastDot = displayName.lastIndexOf('.');
68462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            if (lastDot >= 0) {
68562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                name = displayName.substring(0, lastDot);
68662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                ext = displayName.substring(lastDot + 1);
68762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
68862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                        ext.toLowerCase());
68962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            } else {
69062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                name = displayName;
69162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                ext = null;
69262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                mimeTypeFromExt = null;
69362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            }
69462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
69562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            if (mimeTypeFromExt == null) {
69662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                mimeTypeFromExt = "application/octet-stream";
69762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            }
69862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
69962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            final String extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(
70062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                    mimeType);
70162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) {
70262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                // Extension maps back to requested MIME type; allow it
70362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            } else {
70462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                // No match; insist that create file matches requested MIME
70562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                name = displayName;
70662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                ext = extFromMimeType;
70762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            }
70862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        }
70962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
710fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        if (ext == null) {
711fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            ext = "";
71262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        }
71362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
714fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        return new String[] { name, ext };
71562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    }
71662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
71762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    private static File buildFile(File parent, String name, String ext) {
71862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        if (TextUtils.isEmpty(ext)) {
71962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            return new File(parent, name);
72062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        } else {
72162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            return new File(parent, name + "." + ext);
72262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        }
72362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    }
72485ced632680642fce680d141ddd10299ff849233Jeff Sharkey
725c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey    public static @NonNull String[] listOrEmpty(@Nullable File dir) {
726c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey        if (dir == null) return EmptyArray.STRING;
727c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey        final String[] res = dir.list();
728c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey        if (res != null) {
729c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey            return res;
730c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey        } else {
731c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey            return EmptyArray.STRING;
732c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey        }
733c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey    }
734c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey
735c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
736c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey        if (dir == null) return EMPTY;
737c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey        final File[] res = dir.listFiles();
73885ced632680642fce680d141ddd10299ff849233Jeff Sharkey        if (res != null) {
73985ced632680642fce680d141ddd10299ff849233Jeff Sharkey            return res;
74085ced632680642fce680d141ddd10299ff849233Jeff Sharkey        } else {
74185ced632680642fce680d141ddd10299ff849233Jeff Sharkey            return EMPTY;
74285ced632680642fce680d141ddd10299ff849233Jeff Sharkey        }
74385ced632680642fce680d141ddd10299ff849233Jeff Sharkey    }
74415447798a38d2b5acb1998731340255f4203f294Jeff Sharkey
745c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
746c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey        if (dir == null) return EMPTY;
747c4bab9843ab936f9a9f134e2088a5bd891eb55c2Jeff Sharkey        final File[] res = dir.listFiles(filter);
74835871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        if (res != null) {
74935871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            return res;
75035871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        } else {
75135871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey            return EMPTY;
75235871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey        }
75335871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    }
75435871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey
75515447798a38d2b5acb1998731340255f4203f294Jeff Sharkey    public static @Nullable File newFileOrNull(@Nullable String path) {
75615447798a38d2b5acb1998731340255f4203f294Jeff Sharkey        return (path != null) ? new File(path) : null;
75715447798a38d2b5acb1998731340255f4203f294Jeff Sharkey    }
7585c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath
7595c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath    /**
7605c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath     * Creates a directory with name {@code name} under an existing directory {@code baseDir}.
7615c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath     * Returns a {@code File} object representing the directory on success, {@code null} on
7625c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath     * failure.
7635c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath     */
7645c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath    public static @Nullable File createDir(File baseDir, String name) {
7655c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath        final File dir = new File(baseDir, name);
7665c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath
7675c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath        if (dir.exists()) {
7685c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath            return dir.isDirectory() ? dir : null;
7695c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath        }
7705c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath
7715c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath        return dir.mkdir() ? dir : null;
7725c50e8630164d7d9a1a097f70d2f8bcbf1bd854fNarayan Kamath    }
773373d01766f27476e81a174727dcfeee406742417Jeff Sharkey
774373d01766f27476e81a174727dcfeee406742417Jeff Sharkey    /**
775373d01766f27476e81a174727dcfeee406742417Jeff Sharkey     * Round the given size of a storage device to a nice round power-of-two
776373d01766f27476e81a174727dcfeee406742417Jeff Sharkey     * value, such as 256MB or 32GB. This avoids showing weird values like
777373d01766f27476e81a174727dcfeee406742417Jeff Sharkey     * "29.5GB" in UI.
778373d01766f27476e81a174727dcfeee406742417Jeff Sharkey     */
779373d01766f27476e81a174727dcfeee406742417Jeff Sharkey    public static long roundStorageSize(long size) {
780373d01766f27476e81a174727dcfeee406742417Jeff Sharkey        long res = 1;
781373d01766f27476e81a174727dcfeee406742417Jeff Sharkey        while (res < size) {
782373d01766f27476e81a174727dcfeee406742417Jeff Sharkey            res <<= 1;
783373d01766f27476e81a174727dcfeee406742417Jeff Sharkey        }
784373d01766f27476e81a174727dcfeee406742417Jeff Sharkey        return res;
785373d01766f27476e81a174727dcfeee406742417Jeff Sharkey    }
7869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project}
787