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