FileUtils.java revision 15447798a38d2b5acb1998731340255f4203f294
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;
240cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkeyimport android.text.TextUtils;
25d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkeyimport android.util.Log;
26184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkeyimport android.util.Slog;
2762539a220c6810f66b63060326bd1668f7d6b029Ben Kwaimport android.webkit.MimeTypeMap;
28184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
294f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkeyimport com.android.internal.annotations.VisibleForTesting;
304f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey
3190619816d99154d504a14774c6f2d5f4254ff780Guang Zhuimport java.io.BufferedInputStream;
329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.ByteArrayOutputStream;
339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.File;
34184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkeyimport java.io.FileDescriptor;
359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileInputStream;
366d25a990afffd5eb385aba3043d5dfad36f1539aWink Savilleimport java.io.FileNotFoundException;
379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileOutputStream;
38da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwoodimport java.io.FileWriter;
399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.IOException;
409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.InputStream;
414f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkeyimport java.nio.charset.StandardCharsets;
42d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkeyimport java.util.Arrays;
43d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkeyimport java.util.Comparator;
4462539a220c6810f66b63060326bd1668f7d6b029Ben Kwaimport java.util.Objects;
459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.regex.Pattern;
466d25a990afffd5eb385aba3043d5dfad36f1539aWink Savilleimport java.util.zip.CRC32;
476d25a990afffd5eb385aba3043d5dfad36f1539aWink Savilleimport java.util.zip.CheckedInputStream;
489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/**
509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Tools for managing files.  Not for public consumption.
519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @hide
529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
536d25a990afffd5eb385aba3043d5dfad36f1539aWink Savillepublic class FileUtils {
54d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey    private static final String TAG = "FileUtils";
55d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRWXU = 00700;
579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRUSR = 00400;
589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IWUSR = 00200;
599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IXUSR = 00100;
609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRWXG = 00070;
629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRGRP = 00040;
639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IWGRP = 00020;
649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IXGRP = 00010;
659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IRWXO = 00007;
679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IROTH = 00004;
689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IWOTH = 00002;
699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final int S_IXOTH = 00001;
709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /** Regular expression for safe filenames: no spaces or metacharacters */
729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
7485ced632680642fce680d141ddd10299ff849233Jeff Sharkey    private static final File[] EMPTY = new File[0];
7585ced632680642fce680d141ddd10299ff849233Jeff Sharkey
76184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    /**
77184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * Set owner and mode of of given {@link File}.
78184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     *
79184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param mode to apply through {@code chmod}
80184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param uid to apply through {@code chown}, or -1 to leave unchanged
81184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param gid to apply through {@code chown}, or -1 to leave unchanged
82184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @return 0 on success, otherwise errno.
83184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     */
84184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    public static int setPermissions(File path, int mode, int uid, int gid) {
85184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        return setPermissions(path.getAbsolutePath(), mode, uid, gid);
86184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    }
87184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
88184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    /**
89184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * Set owner and mode of of given path.
90184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     *
91184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param mode to apply through {@code chmod}
92184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param uid to apply through {@code chown}, or -1 to leave unchanged
93184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param gid to apply through {@code chown}, or -1 to leave unchanged
94184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @return 0 on success, otherwise errno.
95184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     */
96184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    public static int setPermissions(String path, int mode, int uid, int gid) {
97184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        try {
9834385d352da19805ae948215e2edbeedd16b7941Elliott Hughes            Os.chmod(path, mode);
99184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        } catch (ErrnoException e) {
100184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
101184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            return e.errno;
102184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
103184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
104184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        if (uid >= 0 || gid >= 0) {
105184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            try {
10634385d352da19805ae948215e2edbeedd16b7941Elliott Hughes                Os.chown(path, uid, gid);
107184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            } catch (ErrnoException e) {
108184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey                Slog.w(TAG, "Failed to chown(" + path + "): " + e);
109184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey                return e.errno;
110184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            }
111184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
112184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
113184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        return 0;
114184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    }
1159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
116184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    /**
117184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * Set owner and mode of of given {@link FileDescriptor}.
118184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     *
119184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param mode to apply through {@code chmod}
120184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param uid to apply through {@code chown}, or -1 to leave unchanged
121184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @param gid to apply through {@code chown}, or -1 to leave unchanged
122184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * @return 0 on success, otherwise errno.
123184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     */
124184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
125184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        try {
12634385d352da19805ae948215e2edbeedd16b7941Elliott Hughes            Os.fchmod(fd, mode);
127184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        } catch (ErrnoException e) {
128184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            Slog.w(TAG, "Failed to fchmod(): " + e);
129184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            return e.errno;
130184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
131184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
132184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        if (uid >= 0 || gid >= 0) {
133184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            try {
13434385d352da19805ae948215e2edbeedd16b7941Elliott Hughes                Os.fchown(fd, uid, gid);
135184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            } catch (ErrnoException e) {
136184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey                Slog.w(TAG, "Failed to fchown(): " + e);
137184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey                return e.errno;
138184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            }
139184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
140184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
141184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        return 0;
142184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    }
143184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey
144184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    /**
145184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     * Return owning UID of given path, otherwise -1.
146184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey     */
147184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    public static int getUid(String path) {
148184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        try {
14934385d352da19805ae948215e2edbeedd16b7941Elliott Hughes            return Os.stat(path).st_uid;
150184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        } catch (ErrnoException e) {
151184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey            return -1;
152184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey        }
153184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey    }
154053f61d6a6e23825e680dc49982e55c5b4299d61Dianne Hackborn
1558bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn    /**
1568bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn     * Perform an fsync on the given FileOutputStream.  The stream at this
1578bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn     * point must be flushed but not yet closed.
1588bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn     */
1598bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn    public static boolean sync(FileOutputStream stream) {
1608bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn        try {
1618bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            if (stream != null) {
1628bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn                stream.getFD().sync();
1638bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            }
1648bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            return true;
1658bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn        } catch (IOException e) {
1668bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn        }
1678bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn        return false;
1688bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn    }
1698bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn
1709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    // copy a file from srcFile to destFile, return true if succeed, return
1719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    // false if fail
1729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static boolean copyFile(File srcFile, File destFile) {
1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        boolean result = false;
1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            InputStream in = new FileInputStream(srcFile);
1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            try {
1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                result = copyToFile(in, destFile);
1789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } finally  {
1799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                in.close();
1809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
1819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (IOException e) {
1829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            result = false;
1839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
1849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        return result;
1859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
18690619816d99154d504a14774c6f2d5f4254ff780Guang Zhu
1879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
1889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Copy data from a source stream to destFile.
1899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Return true if succeed, return false if failed.
1909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
1919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static boolean copyToFile(InputStream inputStream, File destFile) {
1929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
1931afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn            if (destFile.exists()) {
1941afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn                destFile.delete();
1951afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn            }
1968bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn            FileOutputStream out = new FileOutputStream(destFile);
1979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            try {
1989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                byte[] buffer = new byte[4096];
1999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                int bytesRead;
2009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                while ((bytesRead = inputStream.read(buffer)) >= 0) {
2019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    out.write(buffer, 0, bytesRead);
2029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
2039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } finally {
2048bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn                out.flush();
2058bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn                try {
2068bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn                    out.getFD().sync();
2078bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn                } catch (IOException e) {
2088bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn                }
2099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                out.close();
2109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
2119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return true;
2129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } catch (IOException e) {
2139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            return false;
2149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
2169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
2189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Check if a filename is "safe" (no metacharacters or spaces).
2199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param file  The file to check
2209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
2219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static boolean isFilenameSafe(File file) {
2229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // Note, we check whether it matches what's known to be safe,
2239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // rather than what's known to be unsafe.  Non-ASCII, control
2249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        // characters, etc. are all unsafe by default.
2259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        return SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches();
2269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
2279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    /**
2299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * Read a text file into a String, optionally limiting the length.
2309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param file to read (will not seek, so things like /proc files are OK)
2319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param max length (positive for head, negative of tail, 0 for no limit)
2329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @param ellipsis to add of the file was truncated (can be null)
2339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @return the contents of the file, possibly truncated
2349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     * @throws IOException if something goes wrong reading the file
2359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project     */
2369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static String readTextFile(File file, int max, String ellipsis) throws IOException {
2379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        InputStream input = new FileInputStream(file);
23890619816d99154d504a14774c6f2d5f4254ff780Guang Zhu        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
23990619816d99154d504a14774c6f2d5f4254ff780Guang Zhu        // input stream, bytes read not equal to buffer size is not necessarily the correct
24090619816d99154d504a14774c6f2d5f4254ff780Guang Zhu        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
24190619816d99154d504a14774c6f2d5f4254ff780Guang Zhu        BufferedInputStream bis = new BufferedInputStream(input);
2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        try {
24342471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor            long size = file.length();
24442471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
24542471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor                if (size > 0 && (max == 0 || size < max)) max = (int) size;
2469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                byte[] data = new byte[max + 1];
24790619816d99154d504a14774c6f2d5f4254ff780Guang Zhu                int length = bis.read(data);
2489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (length <= 0) return "";
2499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (length <= max) return new String(data, 0, length);
2509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (ellipsis == null) return new String(data, 0, max);
2519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                return new String(data, 0, max) + ellipsis;
25242471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor            } else if (max < 0) {  // "tail" mode: keep the last N
2539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                int len;
2549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                boolean rolled = false;
255d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey                byte[] last = null;
256d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey                byte[] data = null;
2579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                do {
2589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (last != null) rolled = true;
2599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    byte[] tmp = last; last = data; data = tmp;
2609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (data == null) data = new byte[-max];
26190619816d99154d504a14774c6f2d5f4254ff780Guang Zhu                    len = bis.read(data);
2629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                } while (len == data.length);
2639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (last == null && len <= 0) return "";
2659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (last == null) return new String(data, 0, len);
2669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (len > 0) {
2679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    rolled = true;
2689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    System.arraycopy(last, len, last, 0, last.length - len);
2699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    System.arraycopy(data, 0, last, last.length - len, len);
2709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
2719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                if (ellipsis == null || !rolled) return new String(last);
2729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                return ellipsis + new String(last);
27342471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor            } else {  // "cat" mode: size unknown, read it all in streaming fashion
2749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                ByteArrayOutputStream contents = new ByteArrayOutputStream();
2759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                int len;
2769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                byte[] data = new byte[1024];
2779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                do {
27890619816d99154d504a14774c6f2d5f4254ff780Guang Zhu                    len = bis.read(data);
2799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                    if (len > 0) contents.write(data, 0, len);
2809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                } while (len == data.length);
2819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                return contents.toString();
2829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
2839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } finally {
28490619816d99154d504a14774c6f2d5f4254ff780Guang Zhu            bis.close();
2859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            input.close();
2869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
2879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
288da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood
289da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood   /**
290da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     * Writes string to file. Basically same as "echo -n $string > $filename"
291da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     *
292da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     * @param filename
293da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     * @param string
294da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     * @throws IOException
295da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood     */
296da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood    public static void stringToFile(String filename, String string) throws IOException {
297da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood        FileWriter out = new FileWriter(filename);
298da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood        try {
299da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood            out.write(string);
300da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood        } finally {
301da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood            out.close();
302da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood        }
303da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood    }
3041b9a6a6e58fd73b5d1b6a434d17f0a69806858ecWink Saville
3056d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville    /**
3066d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     * Computes the checksum of a file using the CRC32 checksum routine.
3076d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     * The value of the checksum is returned.
3086d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     *
3096d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     * @param file  the file to checksum, must not be null
3106d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     * @return the checksum value or an exception is thrown.
3116d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville     */
3126d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville    public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
3136d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        CRC32 checkSummer = new CRC32();
3146d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        CheckedInputStream cis = null;
3156d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville
3166d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        try {
3176d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            cis = new CheckedInputStream( new FileInputStream(file), checkSummer);
3186d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            byte[] buf = new byte[128];
3196d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            while(cis.read(buf) >= 0) {
3206d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                // Just read for checksum to get calculated.
3216d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            }
3226d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            return checkSummer.getValue();
3236d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        } finally {
3246d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            if (cis != null) {
3256d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                try {
3266d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                    cis.close();
3276d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                } catch (IOException e) {
3286d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville                }
3296d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville            }
3306d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville        }
3316d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville    }
332d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
333d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey    /**
334d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     * Delete older files in a directory until only those matching the given
335d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     * constraints remain.
336d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     *
337d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     * @param minCount Always keep at least this many files.
338d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     * @param minAge Always keep files younger than this age.
339ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey     * @return if any files were deleted.
340d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey     */
341ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey    public static boolean deleteOlderFiles(File dir, int minCount, long minAge) {
342d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        if (minCount < 0 || minAge < 0) {
343d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            throw new IllegalArgumentException("Constraints must be positive or 0");
344d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        }
345d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
346d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        final File[] files = dir.listFiles();
347ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey        if (files == null) return false;
348d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
349d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        // Sort with newest files first
350d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        Arrays.sort(files, new Comparator<File>() {
351d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            @Override
352d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            public int compare(File lhs, File rhs) {
353d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey                return (int) (rhs.lastModified() - lhs.lastModified());
354d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            }
355d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        });
356d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
357d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        // Keep at least minCount files
358ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey        boolean deleted = false;
359d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        for (int i = minCount; i < files.length; i++) {
360d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            final File file = files[i];
361d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey
362d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            // Keep files newer than minAge
363d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            final long age = System.currentTimeMillis() - file.lastModified();
364d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            if (age > minAge) {
365ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey                if (file.delete()) {
366ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey                    Log.d(TAG, "Deleted old file " + file);
367ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey                    deleted = true;
368ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey                }
369d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey            }
370d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey        }
371ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey        return deleted;
372d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey    }
3734ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey
3744ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey    /**
3754ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * Test if a file lives under the given directory, either as a direct child
3764ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * or a distant grandchild.
3774ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * <p>
3784ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * Both files <em>must</em> have been resolved using
3794ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
3804ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     * attacks.
3814ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey     */
3824887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey    public static boolean contains(File[] dirs, File file) {
3834887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey        for (File dir : dirs) {
3844887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey            if (contains(dir, file)) {
3854887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey                return true;
3864887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey            }
3874887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey        }
3884887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey        return false;
3894887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey    }
3904887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey
3914887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey    /**
3924887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * Test if a file lives under the given directory, either as a direct child
3934887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * or a distant grandchild.
3944887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * <p>
3954887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * Both files <em>must</em> have been resolved using
3964887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
3974887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     * attacks.
3984887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey     */
3994ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey    public static boolean contains(File dir, File file) {
40050a05454795c93ac483f5cb6819e74cb17be1b5bJeff Sharkey        if (dir == null || file == null) return false;
401d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey
40221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        String dirPath = dir.getAbsolutePath();
40321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        String filePath = file.getAbsolutePath();
4044ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey
4054ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        if (dirPath.equals(filePath)) {
4064ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey            return true;
4074ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        }
4084ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey
4094ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        if (!dirPath.endsWith("/")) {
4104ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey            dirPath += "/";
4114ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        }
4124ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey        return filePath.startsWith(dirPath);
4134ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey    }
4143a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey
41557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    public static boolean deleteContents(File dir) {
4163a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        File[] files = dir.listFiles();
41757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        boolean success = true;
4183a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        if (files != null) {
4193a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey            for (File file : files) {
4203a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey                if (file.isDirectory()) {
42157dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey                    success &= deleteContents(file);
4223a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey                }
42373767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey                if (!file.delete()) {
42473767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey                    Log.w(TAG, "Failed to delete " + file);
42573767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey                    success = false;
42673767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey                }
4273a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey            }
4283a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        }
42957dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        return success;
4303a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey    }
4313a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey
4320cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    private static boolean isValidExtFilenameChar(char c) {
4330cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        switch (c) {
4340cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '\0':
4350cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '/':
4360cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                return false;
4370cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            default:
4380cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                return true;
4390cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
4400cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
4410cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
4423a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey    /**
4430cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * Check if given filename is valid for an ext4 filesystem.
4443a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey     */
4453a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey    public static boolean isValidExtFilename(String name) {
4460cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        return (name != null) && name.equals(buildValidExtFilename(name));
4470cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
4480cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
4490cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    /**
4500cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * Mutate the given filename to make it valid for an ext4 filesystem,
4510cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * replacing any invalid characters with "_".
4520cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     */
4530cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    public static String buildValidExtFilename(String name) {
4543a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
4550cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            return "(invalid)";
4563a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        }
4570cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        final StringBuilder res = new StringBuilder(name.length());
4583a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        for (int i = 0; i < name.length(); i++) {
4593a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey            final char c = name.charAt(i);
4600cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            if (isValidExtFilenameChar(c)) {
4610cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                res.append(c);
4620cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            } else {
4630cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                res.append('_');
4640cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            }
4650cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
4664f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        trimFilename(res, 255);
4670cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        return res.toString();
4680cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
4690cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
4700cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    private static boolean isValidFatFilenameChar(char c) {
4710cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        if ((0x00 <= c && c <= 0x1f)) {
4720cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            return false;
4730cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
4740cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        switch (c) {
4750cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '"':
4760cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '*':
4770cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '/':
4780cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case ':':
4790cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '<':
4800cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '>':
4810cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '?':
4820cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '\\':
4830cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case '|':
4840cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            case 0x7F:
4853a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey                return false;
4860cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            default:
4870cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                return true;
4880cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
4890cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
4900cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
4910cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    /**
4920cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * Check if given filename is valid for a FAT filesystem.
4930cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     */
4940cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    public static boolean isValidFatFilename(String name) {
4950cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        return (name != null) && name.equals(buildValidFatFilename(name));
4960cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    }
4970cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
4980cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    /**
4990cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * Mutate the given filename to make it valid for a FAT filesystem,
5000cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     * replacing any invalid characters with "_".
5010cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey     */
5020cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey    public static String buildValidFatFilename(String name) {
5030cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
5040cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            return "(invalid)";
5050cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        }
5060cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        final StringBuilder res = new StringBuilder(name.length());
5070cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        for (int i = 0; i < name.length(); i++) {
5080cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            final char c = name.charAt(i);
5090cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            if (isValidFatFilenameChar(c)) {
5100cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                res.append(c);
5110cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey            } else {
5120cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey                res.append('_');
5133a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey            }
5143a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey        }
5154f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        // Even though vfat allows 255 UCS-2 chars, we might eventually write to
5164f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        // ext4 through a FUSE layer, so use that limit.
5174f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        trimFilename(res, 255);
5184f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        return res.toString();
5194f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    }
5204f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey
5214f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    @VisibleForTesting
5224f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    public static String trimFilename(String str, int maxBytes) {
5234f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        final StringBuilder res = new StringBuilder(str);
5244f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        trimFilename(res, maxBytes);
5250cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        return res.toString();
5263a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey    }
52757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey
5284f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    private static void trimFilename(StringBuilder res, int maxBytes) {
5294f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
5304f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        if (raw.length > maxBytes) {
5314f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey            maxBytes -= 3;
5324f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey            while (raw.length > maxBytes) {
5334f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey                res.deleteCharAt(res.length() / 2);
5344f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey                raw = res.toString().getBytes(StandardCharsets.UTF_8);
5354f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey            }
5364f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey            res.insert(res.length() / 2, "...");
5374f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey        }
5384f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey    }
5394f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey
54057dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
541d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        if (path == null) return null;
54257dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
54357dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        return (result != null) ? result.getAbsolutePath() : null;
54457dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    }
54557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey
546d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey    public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
547d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        if (paths == null) return null;
548d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        final String[] result = new String[paths.length];
549d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        for (int i = 0; i < paths.length; i++) {
550d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey            result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]);
551d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        }
552d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey        return result;
553d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey    }
554d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey
55557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    /**
55657dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey     * Given a path under the "before" directory, rewrite it to live under the
55757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey     * "after" directory. For example, {@code /before/foo/bar.txt} would become
55857dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey     * {@code /after/foo/bar.txt}.
55957dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey     */
56057dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
56141be35dd011dbd7ed3a9d08e8fe3eb85ea0e5b64Jeff Sharkey        if (file == null || beforeDir == null || afterDir == null) return null;
56257dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        if (contains(beforeDir, file)) {
56357dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey            final String splice = file.getAbsolutePath().substring(
56457dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey                    beforeDir.getAbsolutePath().length());
56557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey            return new File(afterDir, splice);
56657dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        }
56757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey        return null;
56857dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey    }
56962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
57062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    /**
57162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * Generates a unique file name under the given parent directory. If the display name doesn't
57262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * have an extension that matches the requested MIME type, the default extension for that MIME
57362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * type is appended. If a file already exists, the name is appended with a numerical value to
57462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * make it unique.
57562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     *
57662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * For example, the display name 'example' with 'text/plain' MIME might produce
57762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * 'example.txt' or 'example (1).txt', etc.
57862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     *
57962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     * @throws FileNotFoundException
58062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa     */
58162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    public static File buildUniqueFile(File parent, String mimeType, String displayName)
58262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            throws FileNotFoundException {
58362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        String name;
58462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        String ext;
58562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
58662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
58762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            name = displayName;
58862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            ext = null;
58962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        } else {
59062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            String mimeTypeFromExt;
59162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
59262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            // Extract requested extension from display name
59362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            final int lastDot = displayName.lastIndexOf('.');
59462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            if (lastDot >= 0) {
59562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                name = displayName.substring(0, lastDot);
59662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                ext = displayName.substring(lastDot + 1);
59762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
59862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                        ext.toLowerCase());
59962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            } else {
60062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                name = displayName;
60162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                ext = null;
60262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                mimeTypeFromExt = null;
60362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            }
60462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
60562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            if (mimeTypeFromExt == null) {
60662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                mimeTypeFromExt = "application/octet-stream";
60762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            }
60862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
60962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            final String extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(
61062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                    mimeType);
61162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) {
61262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                // Extension maps back to requested MIME type; allow it
61362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            } else {
61462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                // No match; insist that create file matches requested MIME
61562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                name = displayName;
61662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                ext = extFromMimeType;
61762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            }
61862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        }
61962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
62062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        File file = buildFile(parent, name, ext);
62162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
62262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        // If conflicting file, try adding counter suffix
62362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        int n = 0;
62462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        while (file.exists()) {
62562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            if (n++ >= 32) {
62662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa                throw new FileNotFoundException("Failed to create unique file");
62762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            }
62862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            file = buildFile(parent, name + " (" + n + ")", ext);
62962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        }
63062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
63162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        return file;
63262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    }
63362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa
63462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    private static File buildFile(File parent, String name, String ext) {
63562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        if (TextUtils.isEmpty(ext)) {
63662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            return new File(parent, name);
63762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        } else {
63862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa            return new File(parent, name + "." + ext);
63962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        }
64062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa    }
64185ced632680642fce680d141ddd10299ff849233Jeff Sharkey
64285ced632680642fce680d141ddd10299ff849233Jeff Sharkey    public static @NonNull File[] listFilesOrEmpty(File dir) {
64385ced632680642fce680d141ddd10299ff849233Jeff Sharkey        File[] res = dir.listFiles();
64485ced632680642fce680d141ddd10299ff849233Jeff Sharkey        if (res != null) {
64585ced632680642fce680d141ddd10299ff849233Jeff Sharkey            return res;
64685ced632680642fce680d141ddd10299ff849233Jeff Sharkey        } else {
64785ced632680642fce680d141ddd10299ff849233Jeff Sharkey            return EMPTY;
64885ced632680642fce680d141ddd10299ff849233Jeff Sharkey        }
64985ced632680642fce680d141ddd10299ff849233Jeff Sharkey    }
65015447798a38d2b5acb1998731340255f4203f294Jeff Sharkey
65115447798a38d2b5acb1998731340255f4203f294Jeff Sharkey    public static @Nullable File newFileOrNull(@Nullable String path) {
65215447798a38d2b5acb1998731340255f4203f294Jeff Sharkey        return (path != null) ? new File(path) : null;
65315447798a38d2b5acb1998731340255f4203f294Jeff Sharkey    }
6549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project}
655