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 17 package com.android.adservices.ui.util; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import android.app.Instrumentation; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.graphics.Point; 28 import android.util.Log; 29 30 import androidx.test.core.app.ApplicationProvider; 31 import androidx.test.platform.app.InstrumentationRegistry; 32 import androidx.test.uiautomator.By; 33 import androidx.test.uiautomator.BySelector; 34 import androidx.test.uiautomator.Direction; 35 import androidx.test.uiautomator.UiDevice; 36 import androidx.test.uiautomator.UiObject; 37 import androidx.test.uiautomator.UiObject2; 38 import androidx.test.uiautomator.UiSelector; 39 import androidx.test.uiautomator.Until; 40 41 import com.android.adservices.LogUtil; 42 import com.android.adservices.shared.testing.common.FileHelper; 43 44 import java.io.File; 45 import java.text.SimpleDateFormat; 46 import java.time.Instant; 47 import java.util.Date; 48 import java.util.List; 49 import java.util.Locale; 50 import java.util.regex.Pattern; 51 52 /** Util class for APK tests. */ 53 public class ApkTestUtil { 54 55 private static final String PRIVACY_SANDBOX_UI = "android.adservices.ui.SETTINGS"; 56 private static final String ANDROID_WIDGET_SCROLLVIEW = "android.widget.ScrollView"; 57 58 private static final String TAG = "ApkTestUtil"; 59 private static final int WINDOW_LAUNCH_TIMEOUT = 2_000; 60 public static final int PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS = 1_000; 61 62 /** 63 * Check whether the device is supported. Adservices doesn't support non-phone device. 64 * 65 * @return if the device is supported. 66 */ isDeviceSupported()67 public static boolean isDeviceSupported() { 68 final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); 69 PackageManager pm = inst.getContext().getPackageManager(); 70 return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH) 71 && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) 72 && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); 73 } 74 getConsentSwitch(UiDevice device)75 public static UiObject2 getConsentSwitch(UiDevice device) { 76 UiObject2 consentSwitch = scrollToFindElement(device, By.clazz("android.widget.Switch")); 77 // Swipe the screen by the width of the toggle so it's not blocked by the nav bar on AOSP 78 // devices. 79 if (device.getDisplayHeight() - consentSwitch.getVisibleBounds().centerY() < 100) { 80 device.swipe( 81 consentSwitch.getVisibleBounds().centerX(), 82 500, 83 consentSwitch.getVisibleBounds().centerX(), 84 0, 85 100); 86 } 87 88 return consentSwitch; 89 } 90 91 /** Returns the string corresponding to a resource ID. */ getString(int resourceId)92 public static String getString(int resourceId) { 93 return ApplicationProvider.getApplicationContext().getResources().getString(resourceId); 94 } 95 scrollToAndClick(UiDevice device, int resId)96 public static void scrollToAndClick(UiDevice device, int resId) { 97 UiObject2 obj = scrollTo(device, resId); 98 clickTopLeft(obj); 99 } 100 click(UiDevice device, int resId)101 public static void click(UiDevice device, int resId) { 102 UiObject2 obj = device.findObject(By.text(getString(resId))); 103 // objects may be partially hidden by the status bar and nav bars. 104 clickTopLeft(obj); 105 } 106 clickTopLeft(UiObject2 obj)107 public static void clickTopLeft(UiObject2 obj) { 108 assertThat(obj).isNotNull(); 109 obj.clickAndWait( 110 new Point(obj.getVisibleBounds().top, obj.getVisibleBounds().left), 111 Until.newWindow(), 112 WINDOW_LAUNCH_TIMEOUT); 113 } 114 gentleSwipe(UiDevice device)115 public static void gentleSwipe(UiDevice device) { 116 device.waitForWindowUpdate(null, WINDOW_LAUNCH_TIMEOUT); 117 UiObject2 scrollView = device.wait( 118 Until.findObject(By.scrollable(true).clazz(ANDROID_WIDGET_SCROLLVIEW)), 119 PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS); 120 scrollView.scroll(Direction.DOWN, /* percent */ 0.25F); 121 } 122 scrollTo(UiDevice device, int resId)123 public static UiObject2 scrollTo(UiDevice device, int resId) { 124 String targetStr = getString(resId); 125 if (targetStr == null) { 126 assertWithMessage("scrollTo() didn't find string with resource id %s)", resId).fail(); 127 } 128 UiObject2 uiObject2 = 129 scrollToFindElement( 130 device, By.text(Pattern.compile(targetStr, Pattern.CASE_INSENSITIVE))); 131 132 if (uiObject2 == null) { 133 assertWithMessage( 134 "scrollTo() didn't find element with text \"%s\" (resId=%s)", 135 targetStr, resId) 136 .fail(); 137 } 138 return uiObject2; 139 } 140 scrollTo(UiDevice device, String regexStr)141 public static UiObject2 scrollTo(UiDevice device, String regexStr) { 142 UiObject2 uiObject2 = 143 scrollToFindElement( 144 device, By.res(Pattern.compile(regexStr, Pattern.CASE_INSENSITIVE))); 145 if (uiObject2 == null) { 146 assertWithMessage( 147 "scrollTo() didn't find element whose text matches regex \"%s\")", 148 regexStr) 149 .fail(); 150 } 151 return uiObject2; 152 } 153 scrollToFindElement(UiDevice device, BySelector selector)154 public static UiObject2 scrollToFindElement(UiDevice device, BySelector selector) { 155 device.waitForWindowUpdate(null, WINDOW_LAUNCH_TIMEOUT); 156 UiObject2 scrollView = 157 device.wait( 158 Until.findObject(By.scrollable(true).clazz(ANDROID_WIDGET_SCROLLVIEW)), 159 PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS); 160 if (scrollView == null) { 161 return null; 162 } 163 UiObject2 element = scrollView.scrollUntil(Direction.DOWN, Until.findObject(selector)); 164 165 return element != null 166 ? element 167 : scrollView.scrollUntil(Direction.UP, Until.findObject(selector)); 168 } 169 170 /** Returns the UiObject corresponding to a resource ID. */ getElement(UiDevice device, int resId)171 public static UiObject2 getElement(UiDevice device, int resId) { 172 String targetStr = getString(resId); 173 Log.d( 174 TAG, 175 "Waiting for object using target string " 176 + targetStr 177 + " until a timeout of " 178 + PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS 179 + " ms"); 180 UiObject2 obj = 181 device.wait( 182 Until.findObject(By.text(targetStr)), 183 PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS); 184 if (obj == null) { 185 obj = device.findObject(By.text(targetStr.toUpperCase(Locale.getDefault()))); 186 } 187 return obj; 188 } 189 190 /** Returns the string corresponding to a resource ID and index. */ getElement(UiDevice device, int resId, int index)191 public static UiObject2 getElement(UiDevice device, int resId, int index) { 192 String targetStr = getString(resId); 193 List<UiObject2> objs = 194 device.wait( 195 Until.findObjects(By.text(targetStr)), 196 PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS); 197 if (objs == null) { 198 return device.wait( 199 Until.findObjects(By.text(targetStr.toUpperCase(Locale.getDefault()))), 200 PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS).get(index); 201 } 202 return objs.get(index); 203 } 204 205 /** Returns the UiObject corresponding to a resource ID. */ getPageElement(UiDevice device, int resId)206 public static UiObject getPageElement(UiDevice device, int resId) { 207 return device.findObject(new UiSelector().text(getString(resId))); 208 } 209 210 /** Launch Privacy Sandbox Setting View. */ launchSettingView(UiDevice device, int launchTimeout)211 public static void launchSettingView(UiDevice device, int launchTimeout) { 212 // Launch the setting view. 213 Intent intent = new Intent(PRIVACY_SANDBOX_UI); 214 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 215 ApplicationProvider.getApplicationContext().startActivity(intent); 216 217 // Wait for the view to appear 218 device.wait(Until.hasObject(By.pkg(PRIVACY_SANDBOX_UI).depth(0)), launchTimeout); 219 } 220 221 /** Launch Privacy Sandbox Setting View with UX extra. */ launchSettingViewGivenUx(UiDevice device, int launchTimeout, String ux)222 public static void launchSettingViewGivenUx(UiDevice device, int launchTimeout, String ux) { 223 // Launch the setting view. 224 Intent intent = new Intent(PRIVACY_SANDBOX_UI); 225 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 226 intent.putExtra("ux", ux); 227 228 ApplicationProvider.getApplicationContext().startActivity(intent); 229 230 // Wait for the view to appear 231 device.wait(Until.hasObject(By.pkg(PRIVACY_SANDBOX_UI).depth(0)), launchTimeout); 232 } 233 234 /** Takes the screenshot at the end of each test for debugging. */ takeScreenshot(UiDevice device, String methodName)235 public static void takeScreenshot(UiDevice device, String methodName) { 236 try { 237 String timeStamp = 238 new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US) 239 .format(Date.from(Instant.now())); 240 241 File screenshotFile = 242 new File( 243 FileHelper.getAdServicesTestsOutputDir(), 244 methodName + timeStamp + ".png"); 245 device.takeScreenshot(screenshotFile); 246 } catch (RuntimeException e) { 247 LogUtil.e("Failed to take screenshot: " + e.getMessage()); 248 } 249 } 250 251 /** Get the intent with the intent string pass in. */ getIntent(String intentString)252 public static Intent getIntent(String intentString) { 253 Intent intent = new Intent(intentString); 254 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 255 return intent; 256 } 257 258 /** Check if intent has package and activity installed. */ isIntentInstalled(Intent intent)259 public static boolean isIntentInstalled(Intent intent) { 260 ResolveInfo info = 261 ApplicationProvider.getApplicationContext() 262 .getPackageManager() 263 .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); 264 if (info != null) { 265 LogUtil.i( 266 "package %s and activity %s get for the intent %s", 267 info.activityInfo.applicationInfo.packageName, 268 info.activityInfo.name, 269 intent.getAction()); 270 } else { 271 LogUtil.e("no package and activity found for this intent %s", intent.getAction()); 272 } 273 return info != null; 274 } 275 276 /** Check if intent has package and activity installed with context provided. */ isIntentInstalled(Context context, Intent intent)277 public static boolean isIntentInstalled(Context context, Intent intent) { 278 ResolveInfo info = 279 context.getPackageManager() 280 .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); 281 if (info != null) { 282 LogUtil.i( 283 "package %s and activity %s get for the intent %s", 284 info.activityInfo.applicationInfo.packageName, 285 info.activityInfo.name, 286 intent.getAction()); 287 } else { 288 LogUtil.e("no package and activity found for this intent %s", intent.getAction()); 289 } 290 return info != null; 291 } 292 293 /** union format for assertion message that object is not null. */ assertNotNull(UiObject2 object, int resId)294 public static void assertNotNull(UiObject2 object, int resId) { 295 assertWithMessage("object with text %s ", getString(resId)).that(object).isNotNull(); 296 } 297 298 /** union format for assertion message of toggle state. */ assertToggleState(UiObject2 toggleSwitch, boolean checked)299 public static void assertToggleState(UiObject2 toggleSwitch, boolean checked) { 300 if (checked) { 301 assertWithMessage("Toggle switch checked").that(toggleSwitch.isChecked()).isTrue(); 302 } else { 303 assertWithMessage("Toggle switch checked").that(toggleSwitch.isChecked()).isFalse(); 304 } 305 } 306 } 307