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; 17 18 import static com.google.common.truth.Truth.assertWithMessage; 19 20 import com.android.adservices.shared.testing.shell.CommandResult; 21 import com.android.tradefed.device.DeviceNotAvailableException; 22 import com.android.tradefed.device.INativeDevice; 23 import com.android.tradefed.device.ITestDevice; 24 25 import com.google.errorprone.annotations.FormatMethod; 26 import com.google.errorprone.annotations.FormatString; 27 28 import java.util.Objects; 29 30 /** 31 * Provides static that is statically shared between the test artifacts. 32 * 33 * <p>This class is mostly needed because often the {@code ITestDevice} is not available when such 34 * artifacts are instantiated, but it also provides other {@code ITestDevice}-related helpers. 35 */ 36 public final class TestDeviceHelper { 37 38 /** Same as android.content.Intent */ 39 public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; 40 41 private static final Logger sLogger = 42 new Logger(ConsoleLogger.getInstance(), TestDeviceHelper.class); 43 44 private static final ThreadLocal<ITestDevice> sDevice = new ThreadLocal<>(); 45 46 /** Sets the singleton. */ setTestDevice(ITestDevice device)47 public static void setTestDevice(ITestDevice device) { 48 Objects.requireNonNull(device, "device cannot be null"); 49 sLogger.i("Setting singleton as %s", device); 50 sDevice.set(device); 51 } 52 53 /** Gets the singleton. */ getTestDevice()54 public static ITestDevice getTestDevice() { 55 ITestDevice device = sDevice.get(); 56 if (device == null) { 57 throw new IllegalStateException( 58 "setTestDevice() not set yet - test must either explicitly call it @Before, or" 59 + " extend AdServicesHostSideTestCase"); 60 } 61 return device; 62 } 63 64 // cmdFmt must be final because it's being passed to a method taking @FormatString 65 66 /** 67 * Executes AdServices shell command and returns the standard output, using the singleton 68 * device. 69 */ 70 @FormatMethod runShellCommand( @ormatString final String cmdFmt, @Nullable Object... cmdArgs)71 public static String runShellCommand( 72 @FormatString final String cmdFmt, @Nullable Object... cmdArgs) { 73 return runShellCommand(getTestDevice(), cmdFmt, cmdArgs); 74 } 75 76 /** 77 * Executes AdServices shell command and returns the standard output, using the given device. 78 */ 79 @FormatMethod runShellCommand( ITestDevice device, @FormatString String cmdFmt, @Nullable Object... cmdArgs)80 public static String runShellCommand( 81 ITestDevice device, @FormatString String cmdFmt, @Nullable Object... cmdArgs) { 82 String cmd = String.format(cmdFmt, cmdArgs); 83 String result; 84 try { 85 result = device.executeShellCommand(cmd); 86 } catch (DeviceNotAvailableException e) { 87 throw new DeviceUnavailableException(e); 88 } 89 sLogger.d("runShellCommand(%s): %s", cmd, result); 90 return result; 91 } 92 93 // cmdFmt must be final because it's being passed to a method taking @FormatString 94 95 /** 96 * Executes AdServices shell command and returns the standard output and standard error wrapped 97 * in a {@link CommandResult}. 98 */ 99 @FormatMethod runShellCommandRwe( @ormatString final String cmdFmt, @Nullable Object... cmdArgs)100 public static CommandResult runShellCommandRwe( 101 @FormatString final String cmdFmt, @Nullable Object... cmdArgs) { 102 return runShellCommandRwe(TestDeviceHelper.getTestDevice(), cmdFmt, cmdArgs); 103 } 104 105 /** 106 * Executes AdServices shell command and returns the standard output and standard error wrapped 107 * in a {@link CommandResult}. 108 */ 109 @FormatMethod runShellCommandRwe( ITestDevice device, @FormatString String cmdFmt, @Nullable Object... cmdArgs)110 public static CommandResult runShellCommandRwe( 111 ITestDevice device, @FormatString String cmdFmt, @Nullable Object... cmdArgs) { 112 String cmd = String.format(cmdFmt, cmdArgs); 113 com.android.tradefed.util.CommandResult result; 114 try { 115 result = device.executeShellV2Command(cmd); 116 } catch (DeviceNotAvailableException e) { 117 throw new DeviceUnavailableException(e); 118 } 119 sLogger.d("runShellCommandRwe(%s): %s", cmd, result); 120 return asCommandResult(result); 121 } 122 asCommandResult(com.android.tradefed.util.CommandResult input)123 private static CommandResult asCommandResult(com.android.tradefed.util.CommandResult input) { 124 String out = input.getStdout() != null ? input.getStdout().strip() : ""; 125 String err = input.getStderr() != null ? input.getStderr().strip() : ""; 126 return new CommandResult(out, err); 127 } 128 129 /** Gets the device API level. */ getApiLevel()130 public static int getApiLevel() { 131 int apiLevel = call(INativeDevice::getApiLevel); 132 133 if (apiLevel == INativeDevice.UNKNOWN_API_LEVEL) { 134 throw new DeviceUnavailableException("Unable to get API level from device"); 135 } 136 137 return apiLevel; 138 } 139 140 /** Gets the given system property. */ getProperty(String name)141 public static String getProperty(String name) { 142 return call(device -> device.getProperty(name)); 143 } 144 145 /** Sets the given system property. */ setProperty(String name, String value)146 public static void setProperty(String name, String value) { 147 run(device -> device.setProperty(name, value)); 148 } 149 150 /** Starts an activity (without specifying the user). */ startActivity(String intent)151 public static void startActivity(String intent) { 152 String startActivityMsg = runShellCommand("am start -a %s", intent); 153 assertWithMessage("result of starting %s", intent) 154 .that(startActivityMsg) 155 .doesNotContain("Error: Activity not started, unable to resolve Intent"); 156 } 157 158 /** 159 * Starts an activity and ensures that the activity has fully loaded and completed. (By using 160 * the flag {@code -w}) 161 * 162 * @param packageName the package name of the app to launch the activity on 163 * @param className the class name of the activity to launch 164 */ startActivityWaitUntilCompletion(String packageName, String className)165 public static void startActivityWaitUntilCompletion(String packageName, String className) { 166 runShellCommand("am start -W -n %s/.%s", packageName, className); 167 } 168 169 /** Enable the given component and return the result of the shell command */ enableComponent(String packageName, String className)170 public static String enableComponent(String packageName, String className) { 171 return runShellCommand("pm enable %s/%s", packageName, className); 172 } 173 174 /** 175 * Enable the given component for the given userId only and return the result of the shell 176 * command 177 */ enableComponent(String packageName, String className, int userId)178 public static String enableComponent(String packageName, String className, int userId) { 179 return runShellCommand("pm enable --user %d %s/%s", userId, packageName, className); 180 } 181 182 /** 183 * @return true if intent is in the pm list of active receivers, false otherwise 184 */ isActiveReceiver(String intent, String packageName, String className)185 public static boolean isActiveReceiver(String intent, String packageName, String className) { 186 String receivers = runShellCommand("pm query-receivers --components -a %s", intent); 187 188 String packageAndFullClass = packageName + "/" + className; 189 if (receivers.contains(packageAndFullClass)) { 190 return true; 191 } 192 193 // check for the abbreviated name, with the package name substring with the class name 194 // replaced by "." 195 if (className.startsWith(packageName)) { 196 String packageAndShortClass = 197 packageName + "/" + className.replaceFirst(packageName, "."); 198 return receivers.contains(packageAndShortClass); 199 } 200 201 return false; 202 } 203 204 public static final class DeviceUnavailableException extends IllegalStateException { DeviceUnavailableException(DeviceNotAvailableException cause)205 public DeviceUnavailableException(DeviceNotAvailableException cause) { 206 super(cause); 207 } 208 DeviceUnavailableException(String cause)209 public DeviceUnavailableException(String cause) { 210 super(cause); 211 } 212 } 213 214 /** 215 * Runs the given command without throwing a checked exception. 216 * 217 * @throws DeviceUnavailableException if device is not available. 218 */ call(Command<T> command)219 public static <T> T call(Command<T> command) { 220 try { 221 return command.run(getTestDevice()); 222 } catch (DeviceNotAvailableException e) { 223 throw new DeviceUnavailableException(e); 224 } 225 } 226 227 /** 228 * Runs the given command without throwing a checked exception. 229 * 230 * @throws DeviceUnavailableException if device is not available. 231 */ run(VoidCommand command)232 public static void run(VoidCommand command) { 233 try { 234 command.run(getTestDevice()); 235 } catch (DeviceNotAvailableException e) { 236 throw new DeviceUnavailableException(e); 237 } 238 } 239 240 /** 241 * Abstraction for a command that returns a result. 242 * 243 * @param <T> type of command 244 */ 245 public interface Command<T> { 246 /** Run Forrest, run! */ run(ITestDevice device)247 T run(ITestDevice device) throws DeviceNotAvailableException; 248 } 249 250 /** Abstraction for a command that does not return a result. */ 251 public interface VoidCommand { 252 /** Run Forrest, run! */ run(ITestDevice device)253 void run(ITestDevice device) throws DeviceNotAvailableException; 254 } 255 TestDeviceHelper()256 private TestDeviceHelper() { 257 throw new UnsupportedOperationException("Provides only static methods"); 258 } 259 } 260