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