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.google.android.car.bugreport;
17 
18 import android.content.Context;
19 
20 import com.google.common.base.Preconditions;
21 
22 import java.io.File;
23 
24 /**
25  * File utilities.
26  *
27  * Thread safety and file operations: All file operations should happen on the same worker
28  * thread for thread safety. This is provided by running both bugreport service and file upload
29  * service on a asynctask. Asynctasks are by default executed on the same worker thread in serial.
30  *
31  * There is one exception to the rule above:
32  * Voice recorder works on main thread, however this is not a thread safety problem because:
33  * i. voice recorder always works before starting to collect rest of the bugreport
34  * ii. a bug report cannot be moved to upload (pending) directory before it is completely
35  * collected.
36  */
37 public class FileUtils {
38     private static final String PREFIX = "bugreport-";
39     /** A directory under the system user; contains bugreport zip files and audio files. */
40     private static final String PENDING_DIR = "bug_reports_pending";
41     // Temporary directory under the current user, used for zipping files.
42     private static final String TEMP_DIR = "bug_reports_temp";
43 
44     private static final String FS = "@";
45 
getPendingDir(Context context)46     static File getPendingDir(Context context) {
47         Preconditions.checkArgument(context.getUser().isSystem(),
48                 "Must be called from the system user.");
49         File dir = new File(context.getDataDir(), PENDING_DIR);
50         dir.mkdirs();
51         return dir;
52     }
53 
54     /**
55      * Creates and returns file directory for storing bug report files before they are zipped into
56      * a single file.
57      */
createTempDir(Context context, String timestamp)58     static File createTempDir(Context context, String timestamp) {
59         File dir = getTempDir(context, timestamp);
60         dir.mkdirs();
61         return dir;
62     }
63 
64     /**
65      * Returns path to the directory for storing bug report files before they are zipped into a
66      * single file.
67      */
getTempDir(Context context, String timestamp)68     static File getTempDir(Context context, String timestamp) {
69         Preconditions.checkArgument(!context.getUser().isSystem(),
70                 "Must be called from the current user.");
71         return new File(context.getDataDir(), TEMP_DIR + "/" + timestamp);
72     }
73 
74     /**
75      * Constructs a bugreport zip file name.
76      *
77      * <p>Add lookup code to the filename to allow matching audio file and bugreport file in USB.
78      */
getZipFileName(MetaBugReport bug)79     static String getZipFileName(MetaBugReport bug) {
80         String lookupCode = extractLookupCode(bug);
81         return PREFIX + bug.getUserName() + FS + bug.getTimestamp() + "-" + lookupCode + ".zip";
82     }
83 
84     /**
85      * Constructs a audio message file name.
86      *
87      * <p>Add lookup code to the filename to allow matching audio file and bugreport file in USB.
88      *
89      * @param timestamp - current timestamp, when audio was created.
90      * @param bug       - a bug report.
91      */
getAudioFileName(String timestamp, MetaBugReport bug)92     static String getAudioFileName(String timestamp, MetaBugReport bug) {
93         String lookupCode = extractLookupCode(bug);
94         return PREFIX + bug.getUserName() + FS + timestamp + "-" + lookupCode + "-message.3gp";
95     }
96 
extractLookupCode(MetaBugReport bug)97     private static String extractLookupCode(MetaBugReport bug) {
98         Preconditions.checkArgument(bug.getTitle().startsWith("["),
99                 "Invalid bugreport title, doesn't contain lookup code. ");
100         return bug.getTitle().substring(1, BugReportActivity.LOOKUP_STRING_LENGTH + 1);
101     }
102 
103     /**
104      * Returns a {@link File} object pointing to a path in a temp directory under current users
105      * {@link Context#getDataDir}.
106      *
107      * @param context       - an application context.
108      * @param timestamp     - generates file for this timestamp
109      * @param suffix        - a filename suffix.
110      * @return A file.
111      */
getFileWithSuffix(Context context, String timestamp, String suffix)112     static File getFileWithSuffix(Context context, String timestamp, String suffix) {
113         return new File(createTempDir(context, timestamp), timestamp + suffix);
114     }
115 
116     /**
117      * Returns a {@link File} object pointing to a path in a temp directory under current users
118      * {@link Context#getDataDir}.
119      *
120      * @param context     - an application context.
121      * @param timestamp   - generates file for this timestamp.
122      * @param name        - a filename
123      * @return A file.
124      */
getFile(Context context, String timestamp, String name)125     static File getFile(Context context, String timestamp, String name) {
126         return new File(getTempDir(context, timestamp), name);
127     }
128 
129     /**
130      * Deletes a directory and its contents recursively
131      *
132      * @param directory to delete
133      */
deleteDirectory(File directory)134     static void deleteDirectory(File directory) {
135         File[] files = directory.listFiles();
136         if (files != null) {
137             for (File file : files) {
138                 if (!file.isDirectory()) {
139                     file.delete();
140                 } else {
141                     deleteDirectory(file);
142                 }
143             }
144         }
145         directory.delete();
146     }
147 }
148