1 /* 2 * Copyright (C) 2021 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.bedstead.nene.utils; 18 19 import static android.os.Build.VERSION_CODES.S; 20 21 import android.app.UiAutomation; 22 import android.os.ParcelFileDescriptor; 23 import android.provider.Settings; 24 import android.util.Log; 25 26 import androidx.test.platform.app.InstrumentationRegistry; 27 28 import com.android.bedstead.nene.TestApis; 29 import com.android.bedstead.nene.exceptions.AdbException; 30 import com.android.compatibility.common.util.FileUtils; 31 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.util.function.Function; 36 37 /** 38 * Utilities for interacting with adb shell commands. 39 * 40 * <p>To enable command logging use the adb command `adb shell settings put global nene_log 1`. 41 */ 42 public final class ShellCommandUtils { 43 44 private static final String LOG_TAG = ShellCommandUtils.class.getName(); 45 46 private static final int OUT_DESCRIPTOR_INDEX = 0; 47 private static final int IN_DESCRIPTOR_INDEX = 1; 48 private static final int ERR_DESCRIPTOR_INDEX = 2; 49 50 private static final TestApis sTestApis = new TestApis(); 51 52 private static final boolean SHOULD_LOG = shouldLog(); 53 shouldLog()54 private static boolean shouldLog() { 55 try { 56 return Settings.Global.getInt( 57 sTestApis.context().instrumentedContext().getContentResolver(), 58 "nene_log") == 1; 59 } catch (Settings.SettingNotFoundException e) { 60 return false; 61 } 62 } 63 ShellCommandUtils()64 private ShellCommandUtils() { } 65 66 /** 67 * Execute an adb shell command. 68 * 69 * <p>When running on S and above, any failures in executing the command will result in an 70 * {@link AdbException} being thrown. On earlier versions of Android, an {@link AdbException} 71 * will be thrown when the command returns no output (indicating that there is an error on 72 * stderr which cannot be read by this method) but some failures will return seemingly correctly 73 * but with an error in the returned string. 74 * 75 * <p>Callers should be careful to check the command's output is valid. 76 */ executeCommand(String command)77 static String executeCommand(String command) throws AdbException { 78 return executeCommand(command, /* allowEmptyOutput=*/ false, /* stdInBytes= */ null); 79 } 80 executeCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes)81 static String executeCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes) 82 throws AdbException { 83 logCommand(command, allowEmptyOutput, stdInBytes); 84 85 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 86 return executeCommandPreS(command, allowEmptyOutput, stdInBytes); 87 } 88 89 // TODO(scottjonathan): Add argument to force errors to stderr 90 try { 91 92 ParcelFileDescriptor[] fds = uiAutomation().executeShellCommandRwe(command); 93 ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX]; 94 ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX]; 95 ParcelFileDescriptor fdErr = fds[ERR_DESCRIPTOR_INDEX]; 96 97 writeStdInAndClose(fdIn, stdInBytes); 98 99 String out = new String(readStreamAndClose(fdOut)); 100 String err = new String(readStreamAndClose(fdErr)); 101 102 if (!err.isEmpty()) { 103 throw new AdbException("Error executing command", command, out, err); 104 } 105 106 if (SHOULD_LOG) { 107 Log.d(LOG_TAG, "Command result: " + out); 108 } 109 110 return out; 111 } catch (IOException e) { 112 throw new AdbException("Error executing command", command, e); 113 } 114 } 115 executeCommandForBytes(String command)116 static byte[] executeCommandForBytes(String command) throws AdbException { 117 return executeCommandForBytes(command, /* stdInBytes= */ null); 118 } 119 executeCommandForBytes(String command, byte[] stdInBytes)120 static byte[] executeCommandForBytes(String command, byte[] stdInBytes) throws AdbException { 121 logCommand(command, /* allowEmptyOutput= */ false, stdInBytes); 122 123 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 124 return executeCommandForBytesPreS(command, stdInBytes); 125 } 126 127 // TODO(scottjonathan): Add argument to force errors to stderr 128 try { 129 130 ParcelFileDescriptor[] fds = uiAutomation().executeShellCommandRwe(command); 131 ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX]; 132 ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX]; 133 ParcelFileDescriptor fdErr = fds[ERR_DESCRIPTOR_INDEX]; 134 135 writeStdInAndClose(fdIn, stdInBytes); 136 137 byte[] out = readStreamAndClose(fdOut); 138 String err = new String(readStreamAndClose(fdErr)); 139 140 if (!err.isEmpty()) { 141 throw new AdbException("Error executing command", command, err); 142 } 143 144 return out; 145 } catch (IOException e) { 146 throw new AdbException("Error executing command", command, e); 147 } 148 } 149 logCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes)150 private static void logCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes) { 151 if (!SHOULD_LOG) { 152 return; 153 } 154 155 StringBuilder logBuilder = new StringBuilder("Executing shell command "); 156 logBuilder.append(command); 157 if (allowEmptyOutput) { 158 logBuilder.append(" (allow empty output)"); 159 } 160 if (stdInBytes != null) { 161 logBuilder.append(" (writing to stdIn)"); 162 } 163 Log.d(LOG_TAG, logBuilder.toString()); 164 } 165 166 /** 167 * Execute an adb shell command and check that the output meets a given criteria. 168 * 169 * <p>On S and above, any output printed to standard error will result in an exception and the 170 * {@code outputSuccessChecker} not being called. Empty output will still be processed. 171 * 172 * <p>Prior to S, if there is no output on standard out, regardless of if there is output on 173 * standard error, {@code outputSuccessChecker} will not be called. 174 * 175 * <p>{@code outputSuccessChecker} should return {@code true} if the output indicates the 176 * command executed successfully. 177 */ executeCommandAndValidateOutput( String command, Function<String, Boolean> outputSuccessChecker)178 static String executeCommandAndValidateOutput( 179 String command, Function<String, Boolean> outputSuccessChecker) throws AdbException { 180 return executeCommandAndValidateOutput(command, 181 /* allowEmptyOutput= */ false, 182 /* stdInBytes= */ null, 183 outputSuccessChecker); 184 } 185 executeCommandAndValidateOutput( String command, boolean allowEmptyOutput, byte[] stdInBytes, Function<String, Boolean> outputSuccessChecker)186 static String executeCommandAndValidateOutput( 187 String command, 188 boolean allowEmptyOutput, 189 byte[] stdInBytes, 190 Function<String, Boolean> outputSuccessChecker) throws AdbException { 191 String output = executeCommand(command, allowEmptyOutput, stdInBytes); 192 if (!outputSuccessChecker.apply(output)) { 193 throw new AdbException("Command did not meet success criteria", command, output); 194 } 195 return output; 196 } 197 198 /** 199 * Return {@code true} if {@code output} starts with "success", case insensitive. 200 */ startsWithSuccess(String output)201 public static boolean startsWithSuccess(String output) { 202 return output.toUpperCase().startsWith("SUCCESS"); 203 } 204 205 /** 206 * Return {@code true} if {@code output} does not start with "error", case insensitive. 207 */ doesNotStartWithError(String output)208 public static boolean doesNotStartWithError(String output) { 209 return !output.toUpperCase().startsWith("ERROR"); 210 } 211 executeCommandPreS( String command, boolean allowEmptyOutput, byte[] stdIn)212 private static String executeCommandPreS( 213 String command, boolean allowEmptyOutput, byte[] stdIn) throws AdbException { 214 ParcelFileDescriptor[] fds = uiAutomation().executeShellCommandRw(command); 215 ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX]; 216 ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX]; 217 218 try { 219 writeStdInAndClose(fdIn, stdIn); 220 221 try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) { 222 String out = new String(FileUtils.readInputStreamFully(fis)); 223 224 if (!allowEmptyOutput && out.isEmpty()) { 225 throw new AdbException( 226 "No output from command. There's likely an error on stderr", 227 command, out); 228 } 229 230 if (SHOULD_LOG) { 231 Log.d(LOG_TAG, "Command result: " + out); 232 } 233 234 return out; 235 } 236 } catch (IOException e) { 237 throw new AdbException( 238 "Error reading command output", command, e); 239 } 240 } 241 242 // This is warned for executeShellCommandRw which did exist as TestApi 243 @SuppressWarnings("NewApi") executeCommandForBytesPreS( String command, byte[] stdInBytes)244 private static byte[] executeCommandForBytesPreS( 245 String command, byte[] stdInBytes) throws AdbException { 246 ParcelFileDescriptor[] fds = uiAutomation().executeShellCommandRw(command); 247 ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX]; 248 ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX]; 249 250 try { 251 writeStdInAndClose(fdIn, stdInBytes); 252 253 try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) { 254 return FileUtils.readInputStreamFully(fis); 255 } 256 } catch (IOException e) { 257 throw new AdbException( 258 "Error reading command output", command, e); 259 } 260 } 261 writeStdInAndClose(ParcelFileDescriptor fdIn, byte[] stdInBytes)262 private static void writeStdInAndClose(ParcelFileDescriptor fdIn, byte[] stdInBytes) 263 throws IOException { 264 if (stdInBytes != null) { 265 try (FileOutputStream fos = new ParcelFileDescriptor.AutoCloseOutputStream(fdIn)) { 266 fos.write(stdInBytes); 267 } 268 } else { 269 fdIn.close(); 270 } 271 } 272 readStreamAndClose(ParcelFileDescriptor fd)273 private static byte[] readStreamAndClose(ParcelFileDescriptor fd) throws IOException { 274 try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fd)) { 275 return FileUtils.readInputStreamFully(fis); 276 } 277 } 278 279 /** 280 * Get a {@link UiAutomation}. 281 */ uiAutomation()282 public static UiAutomation uiAutomation() { 283 return InstrumentationRegistry.getInstrumentation().getUiAutomation(); 284 } 285 } 286