DataExporter.java revision 623659ebf4875e63bf4fef1e0b00096e09121853
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;
328a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.util.zip.ZipEntry;
338a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.util.zip.ZipOutputStream;
348a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
358a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki/**
368a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * Compress all files under the app data dir into a single zip file.
37623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki *
38623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki * Make sure not to output dump filenames anywhere, including logcat.
398a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki */
408a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukipublic class DataExporter {
418a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    private static String TAG = "DataExporter";
428a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
438a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    public static final String ZIP_MIME_TYPE = "application/zip";
448a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
45623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static final String DUMP_FILE_DIRECTORY_NAME = "dumpedfiles";
46623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
47623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static final String OUT_FILE_SUFFIX = "-contacts-db.zip";
48623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
498a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    /**
50623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki     * Compress all files under the app data dir into a single zip file, and return the content://
51623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki     * URI to the file, which can be read via {@link DumpFileProvider}.
528a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     */
53623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static Uri exportData(Context context) throws IOException {
54623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final String fileName = generateRandomName() + OUT_FILE_SUFFIX;
55623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final File outFile = getOutputFile(context, fileName);
56623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
57623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        // Remove all existing ones.
58623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        removeDumpFiles(context);
598a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
60623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        Log.i(TAG, "Dump started...");
61623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
62623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        ensureOutputDirectory(context);
638a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        final ZipOutputStream os = new ZipOutputStream(new FileOutputStream(outFile));
648a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        try {
65623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            addDirectory(context, os, context.getFilesDir().getParentFile(), "contacts-files");
668a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        } finally {
678a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            Closeables.closeQuietly(os);
688a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        }
69623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        Log.i(TAG, "Dump finished.");
70623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return DumpFileProvider.AUTHORITY_URI.buildUpon().appendPath(fileName).build();
71623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
72623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
73623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    /** @return long random string for a file name */
74623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static String generateRandomName() {
75623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final SecureRandom rng = new SecureRandom();
76623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final byte[] random = new byte[256 / 8];
77623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        rng.nextBytes(random);
78623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
79623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return Hex.encodeHex(random, true);
80623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
81623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
82623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static File getOutputDirectory(Context context) {
83623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return new File(context.getCacheDir(), DUMP_FILE_DIRECTORY_NAME);
84623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
85623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
86623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static void ensureOutputDirectory(Context context) {
87623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final File directory = getOutputDirectory(context);
88623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (!directory.exists()) {
89623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            directory.mkdir();
90623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        }
91623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
92623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
93623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static File getOutputFile(Context context, String fileName) {
94623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return new File(getOutputDirectory(context), fileName);
95623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
96623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
97623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static boolean dumpFileExists(Context context) {
98623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return getOutputDirectory(context).exists();
99623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
100623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
101623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static void removeDumpFiles(Context context) {
102623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        removeFileOrDirectory(getOutputDirectory(context));
103623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
104623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
105623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static void removeFileOrDirectory(File file) {
106623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (!file.exists()) return;
107623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
108623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (file.isFile()) {
109623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            Log.i(TAG, "Removing " + file);
110623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            file.delete();
111623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            return;
112623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        }
113623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
114623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (file.isDirectory()) {
115623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            for (File child : file.listFiles()) {
116623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                removeFileOrDirectory(child);
117623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            }
118623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            Log.i(TAG, "Removing " + file);
119623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            file.delete();
120623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        }
1218a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    }
1228a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1238a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    /**
1248a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     * Add all files under {@code current} to {@code os} zip stream
1258a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     */
126623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static void addDirectory(Context context, ZipOutputStream os, File current,
127623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            String storedPath) throws IOException {
1288a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        for (File child : current.listFiles()) {
1298a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            final String childStoredPath = storedPath + "/" + child.getName();
1308a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1318a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            if (child.isDirectory()) {
132623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                // Don't need the cache directory, which also contains the dump files.
133623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                if (child.equals(context.getCacheDir())) {
134623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                    continue;
135623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                }
136623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                // This check is redundant as the output directory should be in the cache dir,
137623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                // but just in case...
138623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                if (child.getName().equals(DUMP_FILE_DIRECTORY_NAME)) {
139623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                    continue;
140623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                }
141623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                addDirectory(context, os, child, childStoredPath);
1428a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            } else if (child.isFile()) {
1438a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                addFile(os, child, childStoredPath);
1448a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            } else {
1458a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                // Shouldn't happen; skip.
1468a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            }
1478a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        }
1488a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    }
1498a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1508a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    /**
1518a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     * Add a single file {@code current} to {@code os} zip stream using the file name
1528a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     * {@code storedPath}.
1538a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     */
1548a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    private static void addFile(ZipOutputStream os, File current, String storedPath)
1558a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            throws IOException {
156623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        Log.i(TAG, "Adding " + current.getAbsolutePath() + " ...");
1578a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        final InputStream is = new FileInputStream(current);
1588a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        os.putNextEntry(new ZipEntry(storedPath));
1598a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1608a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        final byte[] buf = new byte[32 * 1024];
1618a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        int totalLen = 0;
1628a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        while (true) {
1638a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            int len = is.read(buf);
1648a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            if (len <= 0) {
1658a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                break;
1668a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            }
1678a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            os.write(buf, 0, len);
1688a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            totalLen += len;
1698a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        }
1708a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        os.closeEntry();
1718a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        Log.i(TAG, "Added " + current.getAbsolutePath() + " as " + storedPath +
1728a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                " (" + totalLen + " bytes)");
1738a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    }
1748a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki}
175