• 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 
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