1 /*
2  * Copyright (C) 2017 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 android.preference.cts;
18 
19 import static org.junit.Assert.assertNotNull;
20 
21 import android.app.Activity;
22 import android.app.Instrumentation;
23 import android.app.UiAutomation;
24 import android.app.UiModeManager;
25 import android.content.Context;
26 import android.content.res.Configuration;
27 import android.graphics.Bitmap;
28 import android.graphics.Rect;
29 import android.util.DisplayMetrics;
30 
31 import androidx.test.platform.app.InstrumentationRegistry;
32 import androidx.test.rule.ActivityTestRule;
33 import androidx.test.uiautomator.By;
34 import androidx.test.uiautomator.UiDevice;
35 import androidx.test.uiautomator.UiObject2;
36 import androidx.test.uiautomator.UiObjectNotFoundException;
37 import androidx.test.uiautomator.UiScrollable;
38 import androidx.test.uiautomator.UiSelector;
39 import androidx.test.uiautomator.Until;
40 
41 /**
42  * Collection of helper utils for testing preferences.
43  */
44 public class TestUtils {
45 
46     public final UiDevice mDevice;
47 
48     private final Context mContext;
49     private final Instrumentation mInstrumentation;
50     private final String mPackageName;
51     private final UiAutomation mAutomation;
52     private int mStatusBarHeight = -1;
53     private int mNavigationBarHeight = -1;
54     private ActivityTestRule<?> mRule;
55 
TestUtils(ActivityTestRule<?> rule)56     TestUtils(ActivityTestRule<?> rule) {
57         mInstrumentation = InstrumentationRegistry.getInstrumentation();
58         mContext = mInstrumentation.getTargetContext();
59         mPackageName = mContext.getPackageName();
60         mDevice = UiDevice.getInstance(mInstrumentation);
61         mAutomation = mInstrumentation.getUiAutomation();
62         mRule = rule;
63     }
64 
waitForIdle()65     void waitForIdle() {
66         mDevice.waitForIdle(1000);
67     }
68 
takeScreenshot()69     Bitmap takeScreenshot() {
70         // Only take a screenshot once the screen is stable enough.
71         waitForIdle();
72 
73         Bitmap bt = mAutomation.takeScreenshot(mRule.getActivity().getWindow());
74         assertNotNull("Screenshot must not return null", bt);
75 
76         // Crop-out the status bar to avoid flakiness with changing notifications / time.
77         int statusBarHeight = getStatusBarHeight();
78 
79         // Crop-out the navigation bar to avoid flakiness with button animations.
80         int navigationBarHeight = getNavigationBarHeight();
81 
82         // Crop-out the right side for the scrollbar which may or may not be visible.
83         // On wearable devices the scroll bar is a curve and occupies 20% of the right side.
84         int xToCut = isOnWatchUiMode() ? bt.getWidth() / 5 : bt.getWidth() / 20;
85         int yToCut = statusBarHeight;
86 
87         if (hasVerticalNavBar()) {
88             xToCut += navigationBarHeight;
89         } else {
90             yToCut += navigationBarHeight;
91         }
92 
93         bt = Bitmap.createBitmap(
94             bt, 0, statusBarHeight, bt.getWidth() - xToCut, bt.getHeight() - yToCut);
95 
96         return bt;
97     }
98 
tapOnViewWithText(String text)99     void tapOnViewWithText(String text) {
100         UiObject2 object2 = getTextObject(text);
101         if (object2 != null) {
102             object2.click();
103         } else {
104             scrollToAndGetTextObject(text);
105             getTextObject(text).click();
106         }
107         waitForIdle();
108     }
109 
isTextShown(String text)110     boolean isTextShown(String text) {
111         if (getTextObject(text) != null) {
112             return true;
113         }
114 
115         return scrollToAndGetTextObject(text);
116     }
117 
isTextHidden(String text)118     boolean isTextHidden(String text) {
119         return getTextObject(text) == null;
120     }
121 
isTextFocused(String text)122     boolean isTextFocused(String text) {
123         UiObject2 object = getTextObject(text);
124         return object != null && object.isFocused();
125     }
126 
isOnWatchUiMode()127     boolean isOnWatchUiMode() {
128         UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
129         return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_WATCH;
130     }
131 
getStatusBarHeight()132     private int getStatusBarHeight() {
133         // Cache the result to keep it fast.
134         if (mStatusBarHeight >= 0) {
135             return mStatusBarHeight;
136         }
137 
138         int resourceId = mInstrumentation.getTargetContext().getResources()
139                 .getIdentifier("status_bar_height", "dimen", "android");
140         if (resourceId > 0) {
141             mStatusBarHeight = mInstrumentation.getTargetContext().getResources()
142                     .getDimensionPixelSize(resourceId);
143         } else {
144             mStatusBarHeight = 0;
145         }
146         return mStatusBarHeight;
147     }
148 
getNavigationBarHeight()149     private int getNavigationBarHeight() {
150         // Cache the result to keep it fast.
151         if (mNavigationBarHeight >= 0) {
152             return mNavigationBarHeight;
153         }
154 
155         int resourceId = mInstrumentation.getTargetContext().getResources()
156                 .getIdentifier("navigation_bar_height", "dimen", "android");
157         if (resourceId > 0) {
158             mNavigationBarHeight = mInstrumentation.getTargetContext().getResources()
159                     .getDimensionPixelSize(resourceId);
160         } else {
161             mNavigationBarHeight = 0;
162         }
163         return mNavigationBarHeight;
164     }
165 
hasVerticalNavBar()166     private boolean hasVerticalNavBar() {
167         Rect displayFrame = new Rect();
168         final Activity activity = mRule.getActivity();
169         activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(displayFrame);
170         DisplayMetrics dm = new DisplayMetrics();
171         activity.getDisplay().getRealMetrics(dm);
172         return dm.heightPixels == displayFrame.bottom;
173     }
174 
getTextObject(String text)175     private UiObject2 getTextObject(String text) {
176         // Wait for up to 1 second to find the object. Returns null if the object cannot be found.
177         return mDevice.wait(Until.findObject(By.text(text).pkg(mPackageName)), 1000);
178     }
179 
scrollToAndGetTextObject(String text)180     private boolean scrollToAndGetTextObject(String text) {
181         UiScrollable scroller = new UiScrollable(new UiSelector().scrollable(true));
182         try {
183             // Swipe far away from the edges to avoid triggering navigation gestures
184             scroller.setSwipeDeadZonePercentage(0.25);
185             return scroller.scrollTextIntoView(text);
186         } catch (UiObjectNotFoundException e) {
187             throw new AssertionError("View with text '" + text + "' was not found!", e);
188         }
189     }
190 }
191