• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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