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 17 package com.android.tradefed.util; 18 19 import com.android.tradefed.config.GlobalConfiguration; 20 import com.android.tradefed.error.HarnessException; 21 import com.android.tradefed.log.ITestLogger; 22 import com.android.tradefed.result.FileInputStreamSource; 23 import com.android.tradefed.result.InputStreamSource; 24 import com.android.tradefed.result.LogDataType; 25 import com.android.tradefed.result.error.InfraErrorIdentifier; 26 27 import com.google.common.annotations.VisibleForTesting; 28 import com.google.common.base.Splitter; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.time.Duration; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.List; 36 import java.util.Optional; 37 38 import javax.annotation.Nullable; 39 40 /** A Utility class to execute device actions. */ 41 public class DeviceActionUtil { 42 43 private static final String BUNDLETOOL_FLAG = "--da_bundletool"; 44 private static final String CREDENTIAL_FLAG = "--da_cred_file"; 45 private static final String ADB_FLAG = "--adb"; 46 private static final String AAPT_FLAG = "--aapt"; 47 private static final String JAVA_BIN_FLAG = "--java_command_path"; 48 private static final String GEN_DIR_FLAG = "--da_gen_file_dir"; 49 private static final String TMP_DIR_FLAG = "--tmp_dir_root"; 50 private static final String GOOGLE_APPLICATION_CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS"; 51 private static final String PATH = "PATH"; 52 private static final String HOST_LOG = "host_log_"; 53 private static final String DA_TMP_DIR = "da_tmp_dir"; 54 private static final String DA_GEN_DIR = "da_gen_dir"; 55 private static final String ANY_TXT_FILE_PATTERN = ".*\\.txt"; 56 private static final long EXECUTION_TIMEOUT = Duration.ofHours(1).toMillis(); 57 58 private final File mDeviceActionMainJar; 59 private final String[] mConfigFlags; 60 private final IRunUtil mRunUtil; 61 private final File mGenDir; 62 63 /** Commands for device action. */ 64 public enum Command { 65 INSTALL_MAINLINE("install_mainline"), 66 RESET("reset"); 67 68 private final String cmdName; 69 Command(String cmdName)70 Command(String cmdName) { 71 this.cmdName = cmdName; 72 } 73 } 74 75 /** Exception for config error. */ 76 public static class DeviceActionConfigError extends HarnessException { 77 private static final long serialVersionUID = 2202987086655357212L; 78 DeviceActionConfigError(String message, @Nullable Throwable cause)79 public DeviceActionConfigError(String message, @Nullable Throwable cause) { 80 super(message, cause, InfraErrorIdentifier.INTERNAL_CONFIG_ERROR); 81 } 82 DeviceActionConfigError(String message)83 public DeviceActionConfigError(String message) { 84 super(message, InfraErrorIdentifier.INTERNAL_CONFIG_ERROR); 85 } 86 } 87 88 @VisibleForTesting DeviceActionUtil( File deviceActionMainJar, String[] configFlags, IRunUtil runUtil, File genDir)89 DeviceActionUtil( 90 File deviceActionMainJar, String[] configFlags, IRunUtil runUtil, File genDir) { 91 mDeviceActionMainJar = deviceActionMainJar; 92 mConfigFlags = configFlags; 93 mRunUtil = runUtil; 94 mGenDir = genDir; 95 } 96 97 /** Creates an instance. */ create(File deviceActionMainJar, File bundletoolJar)98 public static DeviceActionUtil create(File deviceActionMainJar, File bundletoolJar) 99 throws DeviceActionConfigError { 100 checkFile(deviceActionMainJar); 101 checkFile(bundletoolJar); 102 File genDir = createDir(DA_GEN_DIR); 103 File tmpDir = createDir(DA_TMP_DIR); 104 final String[] flags = 105 createConfigFlags( 106 bundletoolJar, 107 getCredential(), 108 genDir, 109 tmpDir, 110 getAdb(), 111 getAapt(), 112 SystemUtil.getRunningJavaBinaryPath()); 113 return new DeviceActionUtil(deviceActionMainJar, flags, new RunUtil(), genDir); 114 } 115 116 /** 117 * Executes a device action command. 118 * 119 * @param command to execute. 120 * @param deviceId of the device. 121 * @param actionArgs action args for the {@code command}. 122 */ execute(Command command, String deviceId, List<String> actionArgs)123 public CommandResult execute(Command command, String deviceId, List<String> actionArgs) { 124 return execute(command, createActionFlags(deviceId, actionArgs)); 125 } 126 127 /** Generates the host log file. */ generateLogFile(CommandResult result)128 public void generateLogFile(CommandResult result) throws IOException { 129 File logFile = FileUtil.createTempFile(HOST_LOG, ".txt", mGenDir); 130 FileUtil.writeToFile(result.getStdout(), logFile); 131 FileUtil.writeToFile(result.getStderr(), logFile, /* append= */ true); 132 } 133 134 /** Saves all generated files to test logs. */ saveToLogs(Command cmd, ITestLogger testLogger)135 public void saveToLogs(Command cmd, ITestLogger testLogger) throws IOException { 136 for (String filePath : FileUtil.findFiles(mGenDir, ANY_TXT_FILE_PATTERN)) { 137 File file = new File(filePath); 138 try (InputStreamSource inputStreamSource = new FileInputStreamSource(file)) { 139 String dataName = 140 String.format("da_%s_%s", cmd.name(), file.getName().split("\\.", 2)[0]); 141 testLogger.testLog(dataName, LogDataType.TEXT, inputStreamSource); 142 } 143 } 144 } 145 146 /** Executes a device action command. */ execute(Command command, List<String> args)147 private CommandResult execute(Command command, List<String> args) { 148 ArrayList<String> cmd = 149 new ArrayList<>( 150 Arrays.asList( 151 SystemUtil.getRunningJavaBinaryPath().getAbsolutePath(), 152 "-Djava.util.logging.config.class=com.google.common.logging.GoogleConsoleLogConfig", 153 "-Dgoogle.debug_log_levels=*=INFO", 154 "-jar", 155 mDeviceActionMainJar.getAbsolutePath(), 156 command.cmdName)); 157 cmd.addAll(Arrays.asList(mConfigFlags)); 158 cmd.addAll(args); 159 return mRunUtil.runTimedCmd(EXECUTION_TIMEOUT, cmd.toArray(new String[0])); 160 } 161 162 @VisibleForTesting createConfigFlags( File bundletoolJar, File credential, File genDir, File tmpDir, File adb, File aapt, File java)163 static String[] createConfigFlags( 164 File bundletoolJar, 165 File credential, 166 File genDir, 167 File tmpDir, 168 File adb, 169 File aapt, 170 File java) { 171 return new String[] { 172 BUNDLETOOL_FLAG, 173 bundletoolJar.getAbsolutePath(), 174 CREDENTIAL_FLAG, 175 credential.getAbsolutePath(), 176 GEN_DIR_FLAG, 177 genDir.getAbsolutePath(), 178 TMP_DIR_FLAG, 179 tmpDir.getAbsolutePath(), 180 ADB_FLAG, 181 adb.getAbsolutePath(), 182 AAPT_FLAG, 183 aapt.getAbsolutePath(), 184 JAVA_BIN_FLAG, 185 java.getAbsolutePath() 186 }; 187 } 188 createActionFlags(String deviceId, List<String> args)189 private static List<String> createActionFlags(String deviceId, List<String> args) { 190 ArrayList<String> flags = new ArrayList<>(Arrays.asList("--device1", "serial=" + deviceId)); 191 for (String arg : args) { 192 flags.addAll(Arrays.asList("--action", arg)); 193 } 194 return flags; 195 } 196 checkFile(File file)197 private static void checkFile(File file) throws DeviceActionConfigError { 198 if (file == null || !file.exists() || !file.isFile()) { 199 throw new DeviceActionConfigError("Missing file " + file); 200 } 201 } 202 getAdb()203 private static File getAdb() throws DeviceActionConfigError { 204 File adbFile = new File(GlobalConfiguration.getDeviceManagerInstance().getAdbPath()); 205 if (!adbFile.exists()) { 206 // Assume adb is in the PATH. 207 adbFile = findExecutableOnPath("adb"); 208 } 209 return adbFile; 210 } 211 getAapt()212 private static File getAapt() throws DeviceActionConfigError { 213 // Assume aapt2 is in the PATH. @see doc for {@link AaptParser}. 214 return findExecutableOnPath("aapt2"); 215 } 216 getCredential()217 private static File getCredential() throws DeviceActionConfigError { 218 File credentialFile = new File(System.getenv(GOOGLE_APPLICATION_CREDENTIALS)); 219 return Optional.of(credentialFile) 220 .filter(File::exists) 221 .orElseThrow( 222 () -> 223 new DeviceActionConfigError( 224 "Missing GOOGLE_APPLICATION_CREDENTIALS")); 225 } 226 createDir(String prefix)227 private static File createDir(String prefix) throws DeviceActionConfigError { 228 try { 229 return FileUtil.createTempDir(prefix); 230 } catch (IOException e) { 231 throw new DeviceActionConfigError("Failed to create " + prefix, e); 232 } 233 } 234 findExecutableOnPath(String name)235 private static File findExecutableOnPath(String name) throws DeviceActionConfigError { 236 return Splitter.on(File.pathSeparator).splitToList(System.getenv(PATH)).stream() 237 .map(dir -> new File(dir, name)) 238 .filter(f -> f.isFile() && f.canExecute()) 239 .findFirst() 240 .orElseThrow( 241 () -> new DeviceActionConfigError("Failed to find the executable " + name)); 242 } 243 } 244