DataExporter.java revision c43a8d4c928b0d362339cd418486e2aa91769b70
18a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki/*
28a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * Copyright (C) 2012 The Android Open Source Project
38a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki *
48a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * Licensed under the Apache License, Version 2.0 (the "License");
58a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * you may not use this file except in compliance with the License.
68a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * You may obtain a copy of the License at
78a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki *
88a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki *      http://www.apache.org/licenses/LICENSE-2.0
98a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki *
108a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * Unless required by applicable law or agreed to in writing, software
118a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * distributed under the License is distributed on an "AS IS" BASIS,
128a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * See the License for the specific language governing permissions and
148a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * limitations under the License.
158a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki */
168a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
178a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukipackage com.android.providers.contacts.debug;
188a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
19623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onukiimport com.android.providers.contacts.util.Hex;
20623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onukiimport com.google.common.io.Closeables;
21623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
228a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport android.content.Context;
23623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onukiimport android.net.Uri;
248a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport android.util.Log;
258a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
268a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.File;
278a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.FileInputStream;
288a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.FileOutputStream;
298a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.IOException;
308a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.InputStream;
31623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onukiimport java.security.SecureRandom;
32a6ec38053a00fb399ca174931c149e3740c7420aMakoto Onukiimport java.util.zip.Deflater;
338a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.util.zip.ZipEntry;
348a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.util.zip.ZipOutputStream;
358a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
368a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki/**
378a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * Compress all files under the app data dir into a single zip file.
38623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki *
39623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki * Make sure not to output dump filenames anywhere, including logcat.
408a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki */
418a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukipublic class DataExporter {
428a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    private static String TAG = "DataExporter";
438a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
448a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    public static final String ZIP_MIME_TYPE = "application/zip";
458a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
46623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static final String DUMP_FILE_DIRECTORY_NAME = "dumpedfiles";
47623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
48623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static final String OUT_FILE_SUFFIX = "-contacts-db.zip";
49c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng    public static final String VALID_FILE_NAME_REGEX = "[0-9A-Fa-f]+-contacts-db\\.zip";
50623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
518a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    /**
52623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki     * Compress all files under the app data dir into a single zip file, and return the content://
53623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki     * URI to the file, which can be read via {@link DumpFileProvider}.
548a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     */
55623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static Uri exportData(Context context) throws IOException {
56623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final String fileName = generateRandomName() + OUT_FILE_SUFFIX;
57623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final File outFile = getOutputFile(context, fileName);
58623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
59623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        // Remove all existing ones.
60623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        removeDumpFiles(context);
618a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
62623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        Log.i(TAG, "Dump started...");
63623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
64623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        ensureOutputDirectory(context);
658a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        final ZipOutputStream os = new ZipOutputStream(new FileOutputStream(outFile));
66a6ec38053a00fb399ca174931c149e3740c7420aMakoto Onuki        os.setLevel(Deflater.BEST_COMPRESSION);
678a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        try {
68623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            addDirectory(context, os, context.getFilesDir().getParentFile(), "contacts-files");
698a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        } finally {
708a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            Closeables.closeQuietly(os);
718a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        }
72623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        Log.i(TAG, "Dump finished.");
73623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return DumpFileProvider.AUTHORITY_URI.buildUpon().appendPath(fileName).build();
74623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
75623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
76623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    /** @return long random string for a file name */
77623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static String generateRandomName() {
78623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final SecureRandom rng = new SecureRandom();
79623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final byte[] random = new byte[256 / 8];
80623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        rng.nextBytes(random);
81623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
82623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return Hex.encodeHex(random, true);
83623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
84623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
85c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng    public static void ensureValidFileName(String fileName) {
86c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        // Do not allow queries to use relative paths to leave the root directory. Otherwise they
87c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        // can gain access to other files such as the contacts database.
88c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        if (fileName.contains("..")) {
89c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng            throw new IllegalArgumentException(".. path specifier not allowed. Bad file name: " +
90c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng                    fileName);
91c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        }
92c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        // White list dump files.
93c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        if (!fileName.matches(VALID_FILE_NAME_REGEX)) {
94c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng            throw new IllegalArgumentException("Only " + VALID_FILE_NAME_REGEX +
95c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng                    " files are supported. Bad file name: " + fileName);
96c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        }
97c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng    }
98c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng
99623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static File getOutputDirectory(Context context) {
100623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return new File(context.getCacheDir(), DUMP_FILE_DIRECTORY_NAME);
101623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
102623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
103623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static void ensureOutputDirectory(Context context) {
104623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final File directory = getOutputDirectory(context);
105623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (!directory.exists()) {
106623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            directory.mkdir();
107623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        }
108623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
109623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
110623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static File getOutputFile(Context context, String fileName) {
111623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return new File(getOutputDirectory(context), fileName);
112623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
113623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
114623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static boolean dumpFileExists(Context context) {
115623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return getOutputDirectory(context).exists();
116623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
117623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
118623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static void removeDumpFiles(Context context) {
119623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        removeFileOrDirectory(getOutputDirectory(context));
120623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
121623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
122623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static void removeFileOrDirectory(File file) {
123623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (!file.exists()) return;
124623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
125623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (file.isFile()) {
126623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            Log.i(TAG, "Removing " + file);
127623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            file.delete();
128623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            return;
129623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        }
130623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
131623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (file.isDirectory()) {
132623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            for (File child : file.listFiles()) {
133623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                removeFileOrDirectory(child);
134623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            }
135623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            Log.i(TAG, "Removing " + file);
136623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            file.delete();
137623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        }
1388a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    }
1398a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1408a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    /**
1418a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     * Add all files under {@code current} to {@code os} zip stream
1428a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     */
143623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static void addDirectory(Context context, ZipOutputStream os, File current,
144623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            String storedPath) throws IOException {
1458a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        for (File child : current.listFiles()) {
1468a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            final String childStoredPath = storedPath + "/" + child.getName();
1478a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1488a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            if (child.isDirectory()) {
149623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                // Don't need the cache directory, which also contains the dump files.
150623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                if (child.equals(context.getCacheDir())) {
151623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                    continue;
152623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                }
153623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                // This check is redundant as the output directory should be in the cache dir,
154623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                // but just in case...
155623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                if (child.getName().equals(DUMP_FILE_DIRECTORY_NAME)) {
156623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                    continue;
157623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                }
158623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                addDirectory(context, os, child, childStoredPath);
1598a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            } else if (child.isFile()) {
1608a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                addFile(os, child, childStoredPath);
1618a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            } else {
1628a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                // Shouldn't happen; skip.
1638a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            }
1648a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        }
1658a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    }
1668a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1678a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    /**
1688a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     * Add a single file {@code current} to {@code os} zip stream using the file name
1698a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     * {@code storedPath}.
1708a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     */
1718a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    private static void addFile(ZipOutputStream os, File current, String storedPath)
1728a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            throws IOException {
173623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        Log.i(TAG, "Adding " + current.getAbsolutePath() + " ...");
1748a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        final InputStream is = new FileInputStream(current);
1758a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        os.putNextEntry(new ZipEntry(storedPath));
1768a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1778a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        final byte[] buf = new byte[32 * 1024];
1788a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        int totalLen = 0;
1798a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        while (true) {
1808a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            int len = is.read(buf);
1818a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            if (len <= 0) {
1828a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                break;
1838a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            }
1848a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            os.write(buf, 0, len);
1858a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            totalLen += len;
1868a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        }
1878a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        os.closeEntry();
1888a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        Log.i(TAG, "Added " + current.getAbsolutePath() + " as " + storedPath +
1898a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                " (" + totalLen + " bytes)");
1908a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    }
1918a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki}
192