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 
21 import android.app.Instrumentation;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.graphics.Point;
25 import android.util.Log;
26 
27 import androidx.test.core.app.ApplicationProvider;
28 import androidx.test.platform.app.InstrumentationRegistry;
29 import androidx.test.uiautomator.By;
30 import androidx.test.uiautomator.BySelector;
31 import androidx.test.uiautomator.Direction;
32 import androidx.test.uiautomator.UiDevice;
33 import androidx.test.uiautomator.UiObject;
34 import androidx.test.uiautomator.UiObject2;
35 import androidx.test.uiautomator.UiSelector;
36 import androidx.test.uiautomator.Until;
37 
38 import com.android.adservices.LogUtil;
39 import com.android.adservices.shared.testing.common.FileHelper;
40 
41 import java.io.File;
42 import java.text.SimpleDateFormat;
43 import java.time.Instant;
44 import java.util.Date;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.regex.Pattern;
48 
49 /** Util class for APK tests. */
50 public class ApkTestUtil {
51 
52     private static final String PRIVACY_SANDBOX_UI = "android.adservices.ui.SETTINGS";
53     private static final String ANDROID_WIDGET_SCROLLVIEW = "android.widget.ScrollView";
54 
55     private static final String TAG = "ApkTestUtil";
56     private static final int WINDOW_LAUNCH_TIMEOUT = 2_000;
57     public static final int PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS = 1_000;
58 
59     /**
60      * Check whether the device is supported. Adservices doesn't support non-phone device.
61      *
62      * @return if the device is supported.
63      */
isDeviceSupported()64     public static boolean isDeviceSupported() {
65         final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
66         PackageManager pm = inst.getContext().getPackageManager();
67         return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
68                 && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
69                 && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
70     }
71 
getConsentSwitch(UiDevice device)72     public static UiObject2 getConsentSwitch(UiDevice device) {
73         UiObject2 consentSwitch = scrollToFindElement(device, By.clazz("android.widget.Switch"));
74         // Swipe the screen by the width of the toggle so it's not blocked by the nav bar on AOSP
75         // devices.
76         if (device.getDisplayHeight() - consentSwitch.getVisibleBounds().centerY() < 100) {
77             device.swipe(
78                     consentSwitch.getVisibleBounds().centerX(),
79                     500,
80                     consentSwitch.getVisibleBounds().centerX(),
81                     0,
82                     100);
83         }
84 
85         return consentSwitch;
86     }
87 
88     /** Returns the string corresponding to a resource ID. */
getString(int resourceId)89     public static String getString(int resourceId) {
90         return ApplicationProvider.getApplicationContext().getResources().getString(resourceId);
91     }
92 
scrollToAndClick(UiDevice device, int resId)93     public static void scrollToAndClick(UiDevice device, int resId) {
94         UiObject2 obj = scrollTo(device, resId);
95         clickTopLeft(obj);
96     }
97 
click(UiDevice device, int resId)98     public static void click(UiDevice device, int resId) {
99         UiObject2 obj = device.findObject(By.text(getString(resId)));
100         // objects may be partially hidden by the status bar and nav bars.
101         clickTopLeft(obj);
102     }
103 
clickTopLeft(UiObject2 obj)104     public static void clickTopLeft(UiObject2 obj) {
105         assertThat(obj).isNotNull();
106         obj.clickAndWait(
107                 new Point(obj.getVisibleBounds().top, obj.getVisibleBounds().left),
108                 Until.newWindow(),
109                 WINDOW_LAUNCH_TIMEOUT);
110     }
111 
gentleSwipe(UiDevice device)112     public static void gentleSwipe(UiDevice device) {
113         device.waitForWindowUpdate(null, WINDOW_LAUNCH_TIMEOUT);
114         UiObject2 scrollView = device.wait(
115                 Until.findObject(By.scrollable(true).clazz(ANDROID_WIDGET_SCROLLVIEW)),
116                 PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS);
117         scrollView.scroll(Direction.DOWN, /* percent */ 0.25F);
118     }
119 
scrollTo(UiDevice device, int resId)120     public static UiObject2 scrollTo(UiDevice device, int resId) {
121         String targetStr = getString(resId);
122         return scrollToFindElement(
123                 device, By.text(Pattern.compile(targetStr, Pattern.CASE_INSENSITIVE)));
124     }
125 
scrollTo(UiDevice device, String regexStr)126     public static UiObject2 scrollTo(UiDevice device, String regexStr) {
127         return scrollToFindElement(
128                 device, By.res(Pattern.compile(regexStr, Pattern.CASE_INSENSITIVE)));
129     }
130 
scrollToFindElement(UiDevice device, BySelector selector)131     public static UiObject2 scrollToFindElement(UiDevice device, BySelector selector) {
132         device.waitForWindowUpdate(null, WINDOW_LAUNCH_TIMEOUT);
133         UiObject2 scrollView =
134                 device.wait(
135                         Until.findObject(By.scrollable(true).clazz(ANDROID_WIDGET_SCROLLVIEW)),
136                         PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS);
137         if (scrollView == null) {
138             return null;
139         }
140         UiObject2 element = scrollView.scrollUntil(Direction.DOWN, Until.findObject(selector));
141 
142         return element != null
143                 ? element
144                 : scrollView.scrollUntil(Direction.UP, Until.findObject(selector));
145     }
146 
147     /** Returns the UiObject corresponding to a resource ID. */
getElement(UiDevice device, int resId)148     public static UiObject2 getElement(UiDevice device, int resId) {
149         String targetStr = getString(resId);
150         Log.d(
151                 TAG,
152                 "Waiting for object using target string "
153                         + targetStr
154                         + " until a timeout of "
155                         + PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS
156                         + " ms");
157         UiObject2 obj =
158                 device.wait(
159                         Until.findObject(By.text(targetStr)),
160                         PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS);
161         if (obj == null) {
162             obj = device.findObject(By.text(targetStr.toUpperCase(Locale.getDefault())));
163         }
164         return obj;
165     }
166 
167     /** Returns the string corresponding to a resource ID and index. */
getElement(UiDevice device, int resId, int index)168     public static UiObject2 getElement(UiDevice device, int resId, int index) {
169         String targetStr = getString(resId);
170         List<UiObject2> objs =
171                 device.wait(
172                         Until.findObjects(By.text(targetStr)),
173                         PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS);
174         if (objs == null) {
175             return device.wait(
176                     Until.findObjects(By.text(targetStr.toUpperCase(Locale.getDefault()))),
177                     PRIMITIVE_UI_OBJECTS_LAUNCH_TIMEOUT_MS).get(index);
178         }
179         return objs.get(index);
180     }
181 
182     /** Returns the UiObject corresponding to a resource ID. */
getPageElement(UiDevice device, int resId)183     public static UiObject getPageElement(UiDevice device, int resId) {
184         return device.findObject(new UiSelector().text(getString(resId)));
185     }
186 
187     /** Launch Privacy Sandbox Setting View. */
launchSettingView(UiDevice device, int launchTimeout)188     public static void launchSettingView(UiDevice device, int launchTimeout) {
189         // Launch the setting view.
190         Intent intent = new Intent(PRIVACY_SANDBOX_UI);
191         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
192         ApplicationProvider.getApplicationContext().startActivity(intent);
193 
194         // Wait for the view to appear
195         device.wait(Until.hasObject(By.pkg(PRIVACY_SANDBOX_UI).depth(0)), launchTimeout);
196     }
197 
198     /** Launch Privacy Sandbox Setting View with UX extra. */
launchSettingViewGivenUx(UiDevice device, int launchTimeout, String ux)199     public static void launchSettingViewGivenUx(UiDevice device, int launchTimeout, String ux) {
200         // Launch the setting view.
201         Intent intent = new Intent(PRIVACY_SANDBOX_UI);
202         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
203         intent.putExtra("ux", ux);
204 
205         ApplicationProvider.getApplicationContext().startActivity(intent);
206 
207         // Wait for the view to appear
208         device.wait(Until.hasObject(By.pkg(PRIVACY_SANDBOX_UI).depth(0)), launchTimeout);
209     }
210 
211     /** Takes the screenshot at the end of each test for debugging. */
takeScreenshot(UiDevice device, String methodName)212     public static void takeScreenshot(UiDevice device, String methodName) {
213         try {
214             String timeStamp =
215                     new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US)
216                             .format(Date.from(Instant.now()));
217 
218             File screenshotFile =
219                     new File(
220                             FileHelper.getAdServicesTestsOutputDir(),
221                             methodName + timeStamp + ".png");
222             device.takeScreenshot(screenshotFile);
223         } catch (RuntimeException e) {
224             LogUtil.e("Failed to take screenshot: " + e.getMessage());
225         }
226     }
227 }
228