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 android.ondevicepersonalization.test.scenario.ondevicepersonalization;
17 
18 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
19 
20 import static org.junit.Assert.assertNotNull;
21 
22 import android.os.RemoteException;
23 import android.os.SystemClock;
24 import android.util.Log;
25 
26 import androidx.test.uiautomator.By;
27 import androidx.test.uiautomator.UiDevice;
28 import androidx.test.uiautomator.UiObject2;
29 import androidx.test.uiautomator.UiObjectNotFoundException;
30 import androidx.test.uiautomator.UiScrollable;
31 import androidx.test.uiautomator.UiSelector;
32 import androidx.test.uiautomator.Until;
33 
34 import org.junit.Assert;
35 
36 import java.io.IOException;
37 
38 /** Helper class for interacting with OdpClient test app in perf tests. */
39 public class TestAppHelper {
40     private static final String TAG = TestAppHelper.class.getSimpleName();
41     private static final UiDevice sUiDevice = UiDevice.getInstance(getInstrumentation());
42     private static final DownloadHelper sDownloadHelper = new DownloadHelper();
43     private static UiScrollable sUiScrollable;
44     private static final long UI_FIND_RESOURCE_TIMEOUT = 15_000;
45     private static final long UI_ROTATE_IDLE_TIMEOUT = 5000;
46     private static final String ODP_CLIENT_TEST_APP_PACKAGE_NAME = "com.example.odpclient";
47     private static final String GET_AD_BUTTON_RESOURCE_ID = "get_ad_button";
48     private static final String RENDERED_VIEW_RESOURCE_ID = "rendered_view";
49     private static final String REPORT_CONVERSION_TEXT_BOX_RESOURCE_ID =
50             "report_conversion_text_box";
51     private static final String REPORT_CONVERSION_BUTTON_RESOURCE_ID = "report_conversion_button";
52     private static final String REPORT_CONVERSION_SUCCESS_LOG = "execute() success";
53     private static final long REPORT_CONVERSION_TIMEOUT = 10_000;
54 
55     /** Commands to prepare the device and odp module before testing. */
initialize()56     public static void initialize() throws IOException {
57         executeShellCommand(
58                 "device_config set_sync_disabled_for_tests persistent");
59         executeShellCommand(
60                 "device_config put on_device_personalization global_kill_switch false");
61         executeShellCommand(
62                 "device_config put on_device_personalization "
63                     + "enable_ondevicepersonalization_apis true");
64         executeShellCommand(
65                 "device_config put on_device_personalization "
66                     + "enable_personalization_status_override true");
67         executeShellCommand(
68                 "device_config put on_device_personalization "
69                     + "personalization_status_override_value true");
70         executeShellCommand("setprop log.tag.ondevicepersonalization VERBOSE");
71         executeShellCommand(
72                 "am broadcast -a android.intent.action.BOOT_COMPLETED -p "
73                     + "com.google.android.ondevicepersonalization.services");
74         executeShellCommand(
75                 "cmd jobscheduler run -f "
76                     + "com.google.android.ondevicepersonalization.services 1000");
77         SystemClock.sleep(5000);
78         executeShellCommand(
79                 "cmd jobscheduler run -f "
80                     + "com.google.android.ondevicepersonalization.services 1006");
81         SystemClock.sleep(5000);
82         try {
83             sDownloadHelper.downloadVendorData();
84             SystemClock.sleep(5000);
85             sDownloadHelper.processExistingOrNewDownloadedVendorData();
86         } catch (AssertionError e) {
87             Log.w(TAG, "Failed to find logs for download during initialize().", e);
88             // TODO(321095374): DownloadHelper scheduling is slightly flaky and may fail due to
89             //  scheduling delays from JobScheduler, but the data may be downloaded from previous
90             //  runs. As these tests are only using download as part of initialize(), don't
91             //  assert in initialize() and let it proceed and fail post-initialize instead.
92         }
93     }
94 
95     /** Kill running processes to get performance measurement under cold start */
killRunningProcess()96     public static void killRunningProcess() throws IOException {
97         executeShellCommand("am kill com.google.android.ondevicepersonalization.services");
98         executeShellCommand("am kill com.google.android.ondevicepersonalization.services:"
99                 + "com.android.ondevicepersonalization."
100                 + "libraries.plugin.internal.PluginExecutorService");
101         executeShellCommand("am kill com.google.android.ondevicepersonalization.services:"
102                 + "plugin_disable_art_image_:"
103                 + "com.android.ondevicepersonalization."
104                 + "libraries.plugin.internal.PluginExecutorService");
105         SystemClock.sleep(2000);
106     }
107 
108     /** Commands to return device to original state */
wrapUp()109     public static void wrapUp() throws IOException {
110         executeShellCommand(
111                 "device_config set_sync_disabled_for_tests none");
112     }
113 
executeShellCommand(String cmd)114     private static void executeShellCommand(String cmd) {
115         try {
116             sUiDevice.executeShellCommand(cmd);
117         } catch (IOException e) {
118             Assert.fail("Failed to execute shell command: " + cmd + ". error: " + e);
119         }
120     }
121 
122     /** Open ODP client test app. */
openApp()123     public static void openApp() throws IOException {
124         sUiDevice.executeShellCommand(
125                 "am start " + ODP_CLIENT_TEST_APP_PACKAGE_NAME + "/.MainActivity");
126     }
127 
128     /** Go back to home screen. */
goToHomeScreen()129     public static void goToHomeScreen() throws IOException {
130         sUiDevice.pressHome();
131     }
132 
133     /** Rotate screen to landscape orientation */
setOrientationLandscape()134     public void setOrientationLandscape() throws RemoteException {
135         Log.d(TAG, "Rotating screen to landscape orientation");
136         sUiDevice.unfreezeRotation();
137         sUiDevice.setOrientationLandscape();
138         SystemClock.sleep(UI_ROTATE_IDLE_TIMEOUT);
139     }
140 
141     /** Rotate screen to portrait orientation */
setOrientationPortrait()142     public void setOrientationPortrait() throws RemoteException {
143         Log.d(TAG, "Rotating screen to portrait orientation");
144         sUiDevice.unfreezeRotation();
145         sUiDevice.setOrientationPortrait();
146         SystemClock.sleep(UI_ROTATE_IDLE_TIMEOUT);
147     }
148 
149     /** Click Get Ad button. */
clickGetAd()150     public void clickGetAd() {
151         UiObject2 getAdButton = getGetAdButton();
152         assertNotNull("Get Ad button not found", getAdButton);
153         Log.d(TAG, "Clicking Get Ad button");
154         getAdButton.click();
155     }
156 
157     /** Verify view is correctly displayed after clicking Get Ad. */
verifyRenderedView()158     public void verifyRenderedView() {
159         UiObject2 renderedView = getRenderedView();
160         assertNotNull("Rendered view not found", renderedView);
161 
162         SystemClock.sleep(UI_FIND_RESOURCE_TIMEOUT);
163         if (renderedView.getChildCount() == 0) {
164             Assert.fail("Failed to render child surface view");
165         } else {
166             Log.d(TAG, "Verified child view is rendered");
167         }
168     }
169 
170     /** Click text on rendered Ad */
clickAd(final String text)171     public void clickAd(final String text) {
172         UiObject2 adUiObject = getUiObjectByText(text);
173         assertNotNull("Could not find Ad UiObject by given text " + text, adUiObject);
174         adUiObject.click();
175         SystemClock.sleep(5000);
176 
177         if (sUiDevice.getCurrentPackageName() == null
178                 || !sUiDevice.getCurrentPackageName().contains("com.android.chrome")) {
179             Assert.fail("Failed to click ad and jump to landing page in the default browser");
180         }
181     }
182 
183     /** Put sourceAdId name down to report conversion */
inputSourceAdId(final String sourceAdId)184     public void inputSourceAdId(final String sourceAdId) {
185         UiObject2 reportConversionTextBox = getReportConversionTextBox();
186         assertNotNull("Report Conversion text box not found", reportConversionTextBox);
187         reportConversionTextBox.setText(sourceAdId);
188     }
189 
190     /** Click Report Conversion button */
clickReportConversion()191     public void clickReportConversion() {
192         UiObject2 reportConversionButton = getReportConversionButton();
193         assertNotNull("Report Conversion button not found", reportConversionButton);
194         reportConversionButton.click();
195         SystemClock.sleep(3000);
196     }
197 
198     /** Verify conversion is reported */
verifyConversionReported()199     public void verifyConversionReported() throws IOException {
200         boolean foundReportConversionSuccessLog = findLog(
201                 REPORT_CONVERSION_SUCCESS_LOG,
202                 REPORT_CONVERSION_TIMEOUT,
203                 2000);
204 
205         if (!foundReportConversionSuccessLog) {
206             Assert.fail(String.format(
207                     "Failed to find report conversion success log within test window %d ms",
208                     REPORT_CONVERSION_TIMEOUT));
209         }
210     }
211 
212     /** Clean log buffer and set a high buffer limit to capture logs for test verification */
resetLogBuffer()213     public void resetLogBuffer() {
214         executeShellCommand("logcat -c"); // Cleans the log buffer
215         executeShellCommand("logcat -G 32M"); // Set log buffer to 32MB
216     }
217 
218     /** Attempt to find a specific log entry within the timeout window */
findLog(final String targetLog, long timeoutMillis, long queryIntervalMillis)219     private boolean findLog(final String targetLog, long timeoutMillis,
220                             long queryIntervalMillis) throws IOException {
221 
222         long startTime = System.currentTimeMillis();
223         while (System.currentTimeMillis() - startTime < timeoutMillis) {
224             if (sUiDevice.executeShellCommand("logcat -d").contains(targetLog)) {
225                 return true;
226             }
227             SystemClock.sleep(queryIntervalMillis);
228         }
229         return false;
230     }
231 
getGetAdButton()232     private UiObject2 getGetAdButton() {
233         return sUiDevice.wait(
234             Until.findObject(By.res(ODP_CLIENT_TEST_APP_PACKAGE_NAME, GET_AD_BUTTON_RESOURCE_ID)),
235             UI_FIND_RESOURCE_TIMEOUT);
236     }
237 
238     /** Locate the rendered UI element in the scrollable view */
getRenderedView()239     private UiObject2 getRenderedView() {
240         for (int i = 0; i < 2; i++) {
241             // Try finding the renderedView on current screen
242             UiObject2 renderedView = sUiDevice.wait(
243                     Until.findObject(
244                             By.res(ODP_CLIENT_TEST_APP_PACKAGE_NAME, RENDERED_VIEW_RESOURCE_ID)),
245                     UI_FIND_RESOURCE_TIMEOUT);
246             if (renderedView != null) {
247                 return renderedView;
248             }
249 
250             // Try scroll to the end
251             try {
252                 getUiScrollable().scrollToEnd(5);
253             } catch (UiObjectNotFoundException e) {
254                 throw new RuntimeException(e);
255             }
256         }
257         return null;
258     }
259 
getUiObjectByText(final String text)260     private UiObject2 getUiObjectByText(final String text) {
261         return sUiDevice.wait(
262             Until.findObject(By.desc(text)),
263             UI_FIND_RESOURCE_TIMEOUT);
264     }
265 
getReportConversionTextBox()266     private UiObject2 getReportConversionTextBox() {
267         return sUiDevice.wait(
268                 Until.findObject(By.res(
269                         ODP_CLIENT_TEST_APP_PACKAGE_NAME, REPORT_CONVERSION_TEXT_BOX_RESOURCE_ID)),
270                 UI_FIND_RESOURCE_TIMEOUT);
271     }
272 
getReportConversionButton()273     private UiObject2 getReportConversionButton() {
274         return sUiDevice.wait(
275                 Until.findObject(By.res(
276                         ODP_CLIENT_TEST_APP_PACKAGE_NAME, REPORT_CONVERSION_BUTTON_RESOURCE_ID)),
277                 UI_FIND_RESOURCE_TIMEOUT);
278     }
279 
280     /** Get a UiScrollable instance configured for vertical scrolling */
getUiScrollable()281     private static UiScrollable getUiScrollable() {
282         if (sUiScrollable == null) {
283             sUiScrollable = new UiScrollable(new UiSelector().scrollable(true));
284             sUiScrollable.setAsVerticalList();
285         }
286         return sUiScrollable;
287     }
288 }
289