1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.bugreport; 17 18 import android.content.Context; 19 import android.os.UserManager; 20 import android.util.Log; 21 22 import com.google.common.base.Preconditions; 23 24 import java.io.File; 25 import java.io.IOException; 26 import java.nio.file.Files; 27 28 /** 29 * File utilities. 30 * 31 * Thread safety and file operations: All file operations should happen on the same worker 32 * thread for thread safety. This is provided by running both bugreport service and file upload 33 * service on a asynctask. Asynctasks are by default executed on the same worker thread in serial. 34 * 35 * There is one exception to the rule above: 36 * Voice recorder works on main thread, however this is not a thread safety problem because: 37 * i. voice recorder always works before starting to collect rest of the bugreport 38 * ii. a bug report cannot be moved to upload (pending) directory before it is completely 39 * collected. 40 */ 41 public class FileUtils { 42 private static final String TAG = FileUtils.class.getSimpleName(); 43 private static final String PREFIX = "bugreport-"; 44 /** A directory under the system user; contains bugreport zip files and audio files. */ 45 private static final String PENDING_DIR = "bug_reports_pending"; 46 // Temporary directory under the current user, used for zipping files. 47 private static final String TEMP_DIR = "bug_reports_temp"; 48 49 private static final String FS = "@"; 50 getPendingDir(Context context)51 static File getPendingDir(Context context) { 52 Preconditions.checkArgument(context.getSystemService(UserManager.class).isSystemUser(), 53 "Must be called from the system user."); 54 File dir = new File(context.getDataDir(), PENDING_DIR); 55 dir.mkdirs(); 56 return dir; 57 } 58 59 /** 60 * Returns path to the directory for storing bug report files before they are zipped into a 61 * single file. 62 */ getTempDir(Context context, String timestamp)63 static File getTempDir(Context context, String timestamp) { 64 Preconditions.checkArgument(!context.getSystemService(UserManager.class).isSystemUser(), 65 "Must be called from the current user."); 66 File dir = new File(context.getDataDir(), TEMP_DIR + "/" + timestamp); 67 dir.mkdirs(); 68 return dir; 69 } 70 71 /** 72 * Constructs a bugreport zip file name. 73 * 74 * <p>Add lookup code to the filename to allow matching audio file and bugreport file in USB. 75 */ getZipFileName(MetaBugReport bug)76 static String getZipFileName(MetaBugReport bug) { 77 String lookupCode = extractLookupCode(bug); 78 return PREFIX + bug.getUserName() + FS + bug.getTimestamp() + "-" + lookupCode + ".zip"; 79 } 80 81 /** 82 * Constructs a audio message file name. 83 * 84 * <p>Add lookup code to the filename to allow matching audio file and bugreport file in USB. 85 * 86 * @param timestamp - current timestamp, when audio was created. 87 * @param bug - a bug report. 88 * @param extension - an extension of audio file. 89 */ getAudioFileName(String timestamp, MetaBugReport bug, String extension)90 static String getAudioFileName(String timestamp, MetaBugReport bug, String extension) { 91 String lookupCode = extractLookupCode(bug); 92 return (PREFIX + bug.getUserName() + FS + timestamp 93 + "-" + lookupCode + "-message." + extension); 94 } 95 extractLookupCode(MetaBugReport bug)96 public static String extractLookupCode(MetaBugReport bug) { 97 Preconditions.checkArgument(bug.getTitle().startsWith("["), 98 "Invalid bugreport title, doesn't contain lookup code. "); 99 return bug.getTitle().substring(1, BugReportActivity.LOOKUP_STRING_LENGTH + 1); 100 } 101 102 /** 103 * Returns a {@link File} object pointing to a path in a temp directory under current users 104 * {@link Context#getDataDir}. 105 * 106 * @param context - an application context. 107 * @param timestamp - generates file for this timestamp 108 * @param suffix - a filename suffix. 109 * @return A file. 110 */ getFileWithSuffix(Context context, String timestamp, String suffix)111 static File getFileWithSuffix(Context context, String timestamp, String suffix) { 112 return new File(getTempDir(context, timestamp), timestamp + suffix); 113 } 114 115 /** 116 * Returns a {@link File} object pointing to a path in a temp directory under current users 117 * {@link Context#getDataDir}. 118 * 119 * @param context - an application context. 120 * @param timestamp - generates file for this timestamp. 121 * @param name - a filename 122 * @return A file. 123 */ getFile(Context context, String timestamp, String name)124 static File getFile(Context context, String timestamp, String name) { 125 return new File(getTempDir(context, timestamp), name); 126 } 127 128 /** 129 * Deletes a directory and its contents recursively 130 * 131 * @param directory to delete 132 */ deleteDirectory(File directory)133 static void deleteDirectory(File directory) { 134 File[] files = directory.listFiles(); 135 if (files != null) { 136 for (File file : files) { 137 if (!file.isDirectory()) { 138 file.delete(); 139 } else { 140 deleteDirectory(file); 141 } 142 } 143 } 144 directory.delete(); 145 } 146 147 /** 148 * Deletes a file quietly by ignoring exceptions. 149 * 150 * @param file The file to delete. 151 */ deleteFileQuietly(File file)152 public static void deleteFileQuietly(File file) { 153 try { 154 Files.delete(file.toPath()); 155 } catch (IOException | SecurityException e) { 156 Log.w(TAG, "Failed to delete " + file + ". Ignoring the error.", e); 157 } 158 } 159 } 160