1 /*
2  * Copyright (C) 2023 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.adservices.shared.testing.common;
17 
18 import android.os.Environment;
19 
20 import com.android.adservices.shared.testing.AndroidLogger;
21 import com.android.adservices.shared.testing.Logger;
22 
23 import java.io.File;
24 import java.io.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.Paths;
28 import java.util.Objects;
29 
30 // TODO(b/381111873): add unit tests
31 /** Provides helpers for file-related operations. */
32 public final class FileHelper {
33 
34     private static final Logger sLog = new Logger(AndroidLogger.getInstance(), FileHelper.class);
35 
36     private static final String SD_CARD_DIR = "/sdcard";
37     private static final String ADSERVICES_TEST_DIR =
38             Environment.DIRECTORY_DOCUMENTS + "/adservices-tests";
39 
40     /** Writes a text file to {@link #getAdServicesTestsOutputDir()}. */
writeFile(String filename, String contents)41     public static void writeFile(String filename, String contents) {
42         String userFriendlyFilename = filename;
43         try {
44             File dir = getAdServicesTestsOutputDir();
45             Path filePath = Paths.get(dir.getAbsolutePath(), filename);
46             userFriendlyFilename = filePath.toString();
47             sLog.i("Creating file %s", userFriendlyFilename);
48             Files.createFile(filePath);
49             byte[] bytes = contents.getBytes();
50             sLog.v("Writing %s bytes to %s", bytes.length, filePath);
51             Files.write(filePath, bytes);
52             sLog.d("Saul Goodman!");
53         } catch (Exception e) {
54             sLog.e(e, "Failed to save %s", userFriendlyFilename);
55         }
56     }
57 
58     /**
59      * Writes a file to {@value #SD_CARD_DIR} under {@value #ADSERVICES_TEST_DIR}
60      *
61      * <p>NOTE: add a {@code com.android.tradefed.device.metric.FilePullerLogCollector} in the test
62      * manifest, pointing to {@code /sdcard/Documents/adservices-tests}, so tests written to this
63      * directory surface as artifacts when the test fails in the cloud.
64      */
getAdServicesTestsOutputDir()65     public static File getAdServicesTestsOutputDir() {
66         String path = SD_CARD_DIR + "/" + ADSERVICES_TEST_DIR;
67         File dir = new File(path);
68         if (dir.exists()) {
69             return dir;
70         }
71         sLog.d("Directory %s doesn't exist, creating it", path);
72         if (dir.mkdirs()) {
73             sLog.i("Created directory %s", path);
74             return dir;
75         }
76         throw new IllegalStateException("Could not create directory " + path);
77     }
78 
79     /**
80      * Deletes a file.
81      *
82      * <p>This is the same as {@code File.delete()}, but logging and throwing {@link IOException} if
83      * the file could not be removed (for example, if it's a non-empty directory).
84      *
85      * @return reference to the file
86      */
deleteFile(File file)87     public static File deleteFile(File file) throws IOException {
88         sLog.i("deleteFile(%s)", file);
89         Objects.requireNonNull(file, "file cannot be null");
90         String path = file.getAbsolutePath();
91         if (!file.exists()) {
92             sLog.d("deleteFile(%s): file doesn't exist", path);
93             return file;
94         }
95         if (file.delete()) {
96             sLog.i("%s deleted", path);
97             return file;
98         }
99         throw new IOException("File " + file + " was not deleted");
100     }
101 
102     /**
103      * Deletes a file.
104      *
105      * <p>This is the same as {@code File.delete()}, but logging and throwing {@link IOException} if
106      * the file could not be removed (for example, if it's a non-empty directory).
107      *
108      * @return reference to the file
109      */
deleteFile(String baseDir, String filename)110     public static File deleteFile(String baseDir, String filename) throws IOException {
111         sLog.i("createEmptyFile(%s, %s)", baseDir, filename);
112         Objects.requireNonNull(baseDir, "baseDir cannot be null");
113         Objects.requireNonNull(filename, "filename cannot be null");
114 
115         return deleteFile(new File(baseDir, filename));
116     }
117 
118     /** Recursively removes the contents of a directory. */
deleteDirectory(File dir)119     public static void deleteDirectory(File dir) throws IOException {
120         sLog.i("deleteDirectory(%s))", dir);
121         Objects.requireNonNull(dir, "dir cannot be null");
122 
123         deleteContents(dir);
124     }
125 
126     // Copied from android.os.FileUtil.deleteContents()
deleteContents(File dir)127     private static void deleteContents(File dir) throws IOException {
128         File[] files = dir.listFiles();
129         if (files != null) {
130             for (File file : files) {
131                 if (file.isDirectory()) {
132                     sLog.v("calling deleteContents() on %s", file);
133                     deleteContents(file);
134                 }
135                 sLog.v("calling File.delete() on %s)", file);
136                 if (!file.delete()) {
137                     throw new IOException("Failed to delete " + file);
138                 }
139             }
140         }
141     }
142 
FileHelper()143     private FileHelper() {
144         throw new UnsupportedOperationException("Provides only static methods");
145     }
146 }
147