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.tests.ui.libs; 17 18 import static android.Manifest.permission.POST_NOTIFICATIONS; 19 20 import static com.android.adservices.AdServicesCommon.BINDER_TIMEOUT_SYSTEM_PROPERTY_NAME; 21 import static com.android.adservices.service.DebugFlagsConstants.KEY_CONSENT_MANAGER_DEBUG_MODE; 22 import static com.android.adservices.service.DebugFlagsConstants.KEY_CONSENT_MANAGER_OTA_DEBUG_MODE; 23 import static com.android.adservices.service.DebugFlagsConstants.KEY_CONSENT_NOTIFICATION_DEBUG_MODE; 24 import static com.android.adservices.service.FlagsConstants.KEY_CONSENT_NOTIFICATION_RESET_TOKEN; 25 import static com.android.adservices.service.FlagsConstants.KEY_ENABLE_AD_SERVICES_SYSTEM_API; 26 import static com.android.adservices.service.FlagsConstants.KEY_GA_UX_FEATURE_ENABLED; 27 import static com.android.adservices.service.FlagsConstants.KEY_GET_ADSERVICES_COMMON_STATES_ALLOW_LIST; 28 import static com.android.adservices.service.FlagsConstants.KEY_IS_EEA_DEVICE; 29 import static com.android.adservices.service.FlagsConstants.KEY_IS_GET_ADSERVICES_COMMON_STATES_API_ENABLED; 30 import static com.android.adservices.service.FlagsConstants.KEY_PAS_UX_ENABLED; 31 import static com.android.adservices.service.FlagsConstants.KEY_U18_UX_ENABLED; 32 import static com.android.adservices.service.FlagsConstants.KEY_UI_OTA_STRINGS_FEATURE_ENABLED; 33 import static com.android.adservices.tests.ui.libs.UiConstants.SYSTEM_UI_NAME; 34 import static com.android.adservices.tests.ui.libs.UiConstants.SYSTEM_UI_RESOURCE_ID; 35 36 import static com.google.common.truth.Truth.assertThat; 37 38 import android.adservices.common.AdServicesCommonManager; 39 import android.adservices.common.AdServicesStates; 40 import android.content.Context; 41 import android.graphics.Point; 42 import android.os.OutcomeReceiver; 43 44 import androidx.test.platform.app.InstrumentationRegistry; 45 import androidx.test.uiautomator.By; 46 import androidx.test.uiautomator.BySelector; 47 import androidx.test.uiautomator.Direction; 48 import androidx.test.uiautomator.SearchCondition; 49 import androidx.test.uiautomator.UiDevice; 50 import androidx.test.uiautomator.UiObject2; 51 import androidx.test.uiautomator.Until; 52 53 import com.android.adservices.LogUtil; 54 import com.android.adservices.api.R; 55 import com.android.adservices.common.AdServicesFlagsSetterRule; 56 import com.android.adservices.shared.testing.common.FileHelper; 57 import com.android.compatibility.common.util.ShellUtils; 58 59 import com.google.common.util.concurrent.SettableFuture; 60 61 import java.io.File; 62 import java.text.SimpleDateFormat; 63 import java.time.Instant; 64 import java.util.Date; 65 import java.util.List; 66 import java.util.Locale; 67 import java.util.UUID; 68 import java.util.concurrent.Executors; 69 import java.util.regex.Pattern; 70 71 public class UiUtils { 72 public static final int LAUNCH_TIMEOUT = 5000; 73 public static final int PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT = 500; 74 public static final int SCROLL_WAIT_TIME = 1000; 75 76 private static final int DEFAULT_BINDER_CONNECTION_TIMEOUT_MS = 10000; 77 78 private static final String ANDROID_WIDGET_SCROLLVIEW = "android.widget.ScrollView"; 79 80 /** Refreshes the consent reset token to a new randomly-generated value */ refreshConsentResetToken(AdServicesFlagsSetterRule flags)81 public static void refreshConsentResetToken(AdServicesFlagsSetterRule flags) { 82 flags.setFlag(KEY_CONSENT_NOTIFICATION_RESET_TOKEN, UUID.randomUUID().toString()); 83 } 84 85 /** Enables consent manager debug mode */ enableConsentDebugMode(AdServicesFlagsSetterRule flags)86 public static void enableConsentDebugMode(AdServicesFlagsSetterRule flags) { 87 flags.setDebugFlag(KEY_CONSENT_NOTIFICATION_DEBUG_MODE, true); 88 } 89 90 /** Disables consent manager debug mode */ disableConsentDebugMode(AdServicesFlagsSetterRule flags)91 public static void disableConsentDebugMode(AdServicesFlagsSetterRule flags) { 92 flags.setDebugFlag(KEY_CONSENT_NOTIFICATION_DEBUG_MODE, false); 93 } 94 95 /** Sets the flag to mimic the behavior of an EEA device */ setAsEuDevice(AdServicesFlagsSetterRule flags)96 public static void setAsEuDevice(AdServicesFlagsSetterRule flags) { 97 flags.setFlag(KEY_IS_EEA_DEVICE, true); 98 } 99 100 /** Sets the flag to mimic the behavior of a non-EEA device */ setAsRowDevice(AdServicesFlagsSetterRule flags)101 public static void setAsRowDevice(AdServicesFlagsSetterRule flags) { 102 flags.setFlag(KEY_IS_EEA_DEVICE, false); 103 } 104 105 /** Enables Under-18 UX */ enableU18(AdServicesFlagsSetterRule flags)106 public static void enableU18(AdServicesFlagsSetterRule flags) { 107 flags.setFlag(KEY_U18_UX_ENABLED, true); 108 } 109 110 /** Sets the flag to enable GA UX */ enableGa(AdServicesFlagsSetterRule flags)111 public static void enableGa(AdServicesFlagsSetterRule flags) throws Exception { 112 flags.setFlag(KEY_GA_UX_FEATURE_ENABLED, true); 113 } 114 115 /** Enables the enableAdServices system API. */ turnOnAdServicesSystemApi(AdServicesFlagsSetterRule flags)116 public static void turnOnAdServicesSystemApi(AdServicesFlagsSetterRule flags) { 117 flags.setFlag(KEY_ENABLE_AD_SERVICES_SYSTEM_API, true); 118 } 119 120 /** Sets the flag to enable Beta UX */ enableBeta(AdServicesFlagsSetterRule flags)121 public static void enableBeta(AdServicesFlagsSetterRule flags) { 122 flags.setFlag(KEY_GA_UX_FEATURE_ENABLED, false); 123 } 124 125 /** Sets the flag to disable OTA String download feature */ disableOtaStrings(AdServicesFlagsSetterRule flags)126 public static void disableOtaStrings(AdServicesFlagsSetterRule flags) throws Exception { 127 flags.setFlag(KEY_UI_OTA_STRINGS_FEATURE_ENABLED, false); 128 } 129 130 /** Restarts the AdServices process */ restartAdservices()131 public static void restartAdservices() { 132 ShellUtils.runShellCommand("am force-stop com.google.android.adservices.api"); 133 ShellUtils.runShellCommand("am force-stop com.android.adservices.api"); 134 } 135 136 /** Sets flag consent_manager_debug_mode to true in tests */ setConsentManagerDebugMode(AdServicesFlagsSetterRule flags)137 public static void setConsentManagerDebugMode(AdServicesFlagsSetterRule flags) { 138 flags.setDebugFlag(KEY_CONSENT_MANAGER_DEBUG_MODE, true); 139 } 140 141 /** Sets flag consent_manager_ota_debug_mode to true in tests */ setConsentManagerOtaDebugMode(AdServicesFlagsSetterRule flags)142 public static void setConsentManagerOtaDebugMode(AdServicesFlagsSetterRule flags) { 143 flags.setDebugFlag(KEY_CONSENT_MANAGER_OTA_DEBUG_MODE, true); 144 } 145 146 /** Sets flag consent_manager_debug_mode to false in tests */ resetConsentManagerDebugMode(AdServicesFlagsSetterRule flags)147 public static void resetConsentManagerDebugMode(AdServicesFlagsSetterRule flags) { 148 flags.setDebugFlag(KEY_CONSENT_MANAGER_DEBUG_MODE, false); 149 } 150 enableNotificationPermission()151 public static void enableNotificationPermission() { 152 InstrumentationRegistry.getInstrumentation() 153 .getUiAutomation() 154 .grantRuntimePermission("com.android.adservices.api", POST_NOTIFICATIONS); 155 } 156 157 /** Sets the flag to disable the V2 notification flow */ disableNotificationFlowV2(AdServicesFlagsSetterRule flags)158 public static void disableNotificationFlowV2(AdServicesFlagsSetterRule flags) throws Exception { 159 flags.setFlag("eu_notif_flow_change_enabled", false); 160 } 161 162 /** Sets the binder time for cts test */ setBinderTimeout(AdServicesFlagsSetterRule flags)163 public static void setBinderTimeout(AdServicesFlagsSetterRule flags) { 164 flags.setDebugFlag( 165 BINDER_TIMEOUT_SYSTEM_PROPERTY_NAME, DEFAULT_BINDER_CONNECTION_TIMEOUT_MS); 166 } 167 168 /** Enables pas */ enablePas(AdServicesFlagsSetterRule flags)169 public static void enablePas(AdServicesFlagsSetterRule flags) { 170 flags.setFlag(KEY_PAS_UX_ENABLED, true); 171 } 172 173 /** Disables pas */ disablePas(AdServicesFlagsSetterRule flags)174 public static void disablePas(AdServicesFlagsSetterRule flags) { 175 flags.setFlag(KEY_PAS_UX_ENABLED, false); 176 } 177 verifyNotification( Context context, UiDevice device, boolean isDisplayed, boolean isEuTest, UiConstants.UX ux)178 public static void verifyNotification( 179 Context context, 180 UiDevice device, 181 boolean isDisplayed, 182 boolean isEuTest, 183 UiConstants.UX ux) 184 throws Exception { 185 int notificationTitle = -1; 186 int notificationHeader = -1; 187 switch (ux) { 188 case GA_UX: 189 // Should match the contentTitle string in ConsentNotificationTrigger.java. 190 notificationTitle = 191 isEuTest 192 ? R.string.notificationUI_notification_ga_title_eu_v2 193 : R.string.notificationUI_notification_ga_title_v2; 194 // Should match the text in consent_notification_screen_1_ga_v2_eu.xml and 195 // consent_notification_screen_1_ga_v2_row.xml, respectively. 196 notificationHeader = 197 isEuTest 198 ? R.string.notificationUI_fledge_measurement_title_v2 199 : R.string.notificationUI_header_ga_title_v2; 200 break; 201 case BETA_UX: 202 notificationTitle = 203 isEuTest 204 ? R.string.notificationUI_notification_title_eu 205 : R.string.notificationUI_notification_title; 206 notificationHeader = 207 isEuTest 208 ? R.string.notificationUI_header_title_eu 209 : R.string.notificationUI_header_title; 210 break; 211 case U18_UX: 212 notificationTitle = R.string.notificationUI_u18_notification_title; 213 notificationHeader = R.string.notificationUI_u18_header_title; 214 break; 215 } 216 217 verifyNotification(context, device, isDisplayed, notificationTitle, notificationHeader); 218 } 219 verifyNotification( Context context, UiDevice device, boolean isDisplayed, int notificationTitle, int notificationHeader)220 public static void verifyNotification( 221 Context context, 222 UiDevice device, 223 boolean isDisplayed, 224 int notificationTitle, 225 int notificationHeader) 226 throws Exception { 227 device.openNotification(); 228 device.waitForIdle(LAUNCH_TIMEOUT); 229 // Wait few seconds for Adservices notification to show, waitForIdle is not enough. 230 Thread.sleep(LAUNCH_TIMEOUT); 231 UiObject2 scroller = device.findObject(By.res(SYSTEM_UI_RESOURCE_ID)); 232 233 BySelector notificationTitleSelector = By.text(getString(context, notificationTitle)); 234 if (!isDisplayed) { 235 assertThat(scroller.hasObject(notificationTitleSelector)).isFalse(); 236 return; 237 } 238 assertThat(scroller.hasObject(notificationTitleSelector)).isTrue(); 239 UiObject2 notificationCard = 240 scroller.findObject(By.text(getString(context, notificationTitle))); 241 242 notificationCard.click(); 243 device.waitForIdle(LAUNCH_TIMEOUT); 244 Thread.sleep(LAUNCH_TIMEOUT); 245 UiObject2 title = getElement(context, device, notificationHeader); 246 assertThat(title).isNotNull(); 247 } 248 249 /** 250 * Swipes through the screen to show elements on the button of the page but hidden by the 251 * navigation bar. 252 * 253 * @param device the UiDevice to manipulate 254 */ gentleSwipe(UiDevice device)255 public static void gentleSwipe(UiDevice device) { 256 UiObject2 scrollView = 257 device.findObject(By.scrollable(true).clazz(ANDROID_WIDGET_SCROLLVIEW)); 258 if (scrollView != null) { 259 scrollView.scroll(Direction.DOWN, /* percent */ 0.25F); 260 } 261 } 262 263 /** Sets the flag to enable or disable the flip flow */ setFlipFlow(AdServicesFlagsSetterRule flags, boolean isFlip)264 public static void setFlipFlow(AdServicesFlagsSetterRule flags, boolean isFlip) { 265 flags.setFlag("eu_notif_flow_change_enabled", isFlip); 266 } 267 268 /** Sets get adservices common states services enabled */ setGetAdservicesCommonStatesServiceEnable( AdServicesFlagsSetterRule flags, boolean enable)269 public static void setGetAdservicesCommonStatesServiceEnable( 270 AdServicesFlagsSetterRule flags, boolean enable) { 271 flags.setFlag(KEY_IS_GET_ADSERVICES_COMMON_STATES_API_ENABLED, enable); 272 } 273 274 /** Sets get adservices common states services enabled */ setGetAdservicesCommonStatesAllowList( AdServicesFlagsSetterRule flags, String list)275 public static void setGetAdservicesCommonStatesAllowList( 276 AdServicesFlagsSetterRule flags, String list) { 277 flags.setFlag(KEY_GET_ADSERVICES_COMMON_STATES_ALLOW_LIST, list); 278 } 279 getString(Context context, int resourceId)280 public static String getString(Context context, int resourceId) { 281 return context.getResources().getString(resourceId); 282 } 283 scrollToAndClick(Context context, UiDevice device, int resId)284 public static void scrollToAndClick(Context context, UiDevice device, int resId) { 285 scrollTo(context, device, resId); 286 UiObject2 consentPageButton = 287 device.wait( 288 getSearchCondByResId(context, resId), PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT); 289 clickTopLeft(consentPageButton); 290 } 291 getSearchCondByResId(Context context, int resId)292 public static SearchCondition<UiObject2> getSearchCondByResId(Context context, int resId) { 293 String targetStr = getString(context, resId); 294 return Until.findObject(By.text(Pattern.compile(targetStr, Pattern.CASE_INSENSITIVE))); 295 } 296 getPageElement(Context context, UiDevice device, int resId)297 public static UiObject2 getPageElement(Context context, UiDevice device, int resId) { 298 return device.findObject(By.text(getString(context, resId))); 299 } 300 scrollTo(Context context, UiDevice device, int resId)301 public static UiObject2 scrollTo(Context context, UiDevice device, int resId) { 302 UiObject2 scrollView = 303 device.findObject(By.scrollable(true).clazz(ANDROID_WIDGET_SCROLLVIEW)); 304 if (scrollView != null) { 305 String targetStr = getString(context, resId); 306 scrollView.scrollUntil( 307 Direction.DOWN, 308 Until.findObject( 309 By.text(Pattern.compile(targetStr, Pattern.CASE_INSENSITIVE)))); 310 } 311 return getElement(context, device, resId); 312 } 313 getElement(Context context, UiDevice device, int resId)314 public static UiObject2 getElement(Context context, UiDevice device, int resId) { 315 return getElement(context, device, resId, 0); 316 } 317 getElement(Context context, UiDevice device, int resId, int index)318 public static UiObject2 getElement(Context context, UiDevice device, int resId, int index) { 319 String targetStr = getString(context, resId); 320 List<UiObject2> objList = 321 device.findObjects(By.text(Pattern.compile(targetStr, Pattern.CASE_INSENSITIVE))); 322 if (objList.size() <= index) { 323 return null; 324 } 325 return objList.get(index); 326 } 327 clickTopLeft(UiObject2 obj)328 public static void clickTopLeft(UiObject2 obj) { 329 assertThat(obj).isNotNull(); 330 obj.click(new Point(obj.getVisibleBounds().top, obj.getVisibleBounds().left)); 331 } 332 takeScreenshot(UiDevice device, String methodName)333 public static void takeScreenshot(UiDevice device, String methodName) { 334 try { 335 String timeStamp = 336 new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US) 337 .format(Date.from(Instant.now())); 338 339 File screenshotFile = 340 new File( 341 FileHelper.getAdServicesTestsOutputDir(), 342 methodName + timeStamp + ".png"); 343 device.takeScreenshot(screenshotFile); 344 } catch (RuntimeException e) { 345 LogUtil.e("Failed to take screenshot: " + e.getMessage()); 346 } 347 } 348 349 /** Resets AdServices consent data */ resetAdServicesConsentData(Context context, AdServicesFlagsSetterRule flags)350 public static void resetAdServicesConsentData(Context context, AdServicesFlagsSetterRule flags) 351 throws Exception { 352 // Neeed to disable debug mode since it takes precedence over reset channel. 353 disableConsentDebugMode(flags); 354 turnOnAdServicesSystemApi(flags); 355 356 AdServicesCommonManager mCommonManager = AdServicesCommonManager.get(context); 357 358 // Reset consent and thereby AdServices data before each test. 359 UiUtils.refreshConsentResetToken(flags); 360 361 SettableFuture<Boolean> responseFuture = SettableFuture.create(); 362 363 mCommonManager.enableAdServices( 364 new AdServicesStates.Builder() 365 .setAdIdEnabled(true) 366 .setAdultAccount(true) 367 .setU18Account(true) 368 .setPrivacySandboxUiEnabled(true) 369 .setPrivacySandboxUiRequest(false) 370 .build(), 371 Executors.newCachedThreadPool(), 372 new OutcomeReceiver<>() { 373 @Override 374 public void onResult(Boolean result) { 375 responseFuture.set(result); 376 } 377 378 @Override 379 public void onError(Exception exception) { 380 responseFuture.setException(exception); 381 } 382 }); 383 384 Boolean response = responseFuture.get(); 385 assertThat(response).isTrue(); 386 } 387 388 /** Returns a [BySelector] of a resource in sysui package. */ sysuiResSelector(String resourceId)389 public static BySelector sysuiResSelector(String resourceId) { 390 return By.pkg(SYSTEM_UI_NAME).res(SYSTEM_UI_NAME, resourceId); 391 } 392 } 393