• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.platform.systemui_tapl.ui;
18 
19 import static android.platform.systemui_tapl.utils.DeviceUtils.LONG_WAIT;
20 import static android.platform.systemui_tapl.utils.DeviceUtils.SHORT_WAIT;
21 import static android.platform.systemui_tapl.utils.DeviceUtils.sysuiResSelector;
22 import static android.platform.test.util.HealthTestingUtils.waitForValueCatchingStaleObjectExceptions;
23 import static android.platform.uiautomatorhelpers.DeviceHelpers.getUiDevice;
24 import static android.platform.uiautomatorhelpers.WaitUtils.ensureThat;
25 
26 import static androidx.test.uiautomator.Until.findObject;
27 
28 import static com.android.settingslib.flags.Flags.newStatusBarIcons;
29 import static com.android.systemui.Flags.statusBarChipsModernization;
30 
31 import static com.google.common.truth.Truth.assertThat;
32 import static com.google.common.truth.Truth.assertWithMessage;
33 
34 import static org.junit.Assume.assumeFalse;
35 
36 import android.app.Flags;
37 import android.graphics.Point;
38 import android.graphics.Rect;
39 import android.platform.helpers.foldable.UnfoldAnimationTestingUtils;
40 import android.platform.uiautomatorhelpers.DeviceHelpers;
41 
42 import androidx.test.uiautomator.By;
43 import androidx.test.uiautomator.BySelector;
44 import androidx.test.uiautomator.SearchCondition;
45 import androidx.test.uiautomator.UiObject2;
46 import androidx.test.uiautomator.Until;
47 
48 import java.util.Collections;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Set;
52 import java.util.regex.Pattern;
53 
54 /** System UI test automation object representing status bar. */
55 public class StatusBar {
56 
57     private static final BySelector PERCENTAGE_SELECTOR = By.text(Pattern.compile("\\d+%"));
58     private static final String BATTERY_ID = "battery";
59     static final String BATTERY_LEVEL_TEXT_ID = "battery_percentage_view";
60     static final String CLOCK_ID = "clock";
61     private static final String NOTIFICATION_ICON_CONTAINER_ID = "notificationIcons";
62     private static final BySelector NOTIFICATION_LIGHTS_OUT_DOT_SELECTOR =
63             sysuiResSelector("notification_lights_out");
64     private static final String UI_SYSTEM_ICONS_ID = "system_icons";
65     private static final String DATE_ID = "date";
66     static final String STATUS_ICON_CONTAINER_ID = "statusIcons";
67     private static final String ALARM_ICON_DESC_PREFIX_STRING = "Alarm";
68     private static final String AIRPLANE_MODE_ICON_DESC = "Airplane mode.";
69     private static final String DATA_SAVER_ICON_DESC = "Data Saver is on";
70     // https://hsv.googleplex.com/6227911158792192?node=18
71     static final String DOCK_DEFEND_ICON_SUFFIX_STRING = "charging paused for battery protection";
72     static final String DND_ICON_DESC = Flags.modesUi() ? "Do Not Disturb is on" : "Do Not Disturb";
73     private static final String WIFI_ICON_ID = "wifi_combo";
74     private static final String ONGOING_ACTIVITY_CHIP_ICON_ID = "ongoing_activity_chip_primary";
75     // Corresponds to ScreenRecordChipViewModel.KEY
76     private static final String SCREEN_RECORDING_CHIP_ID = "ScreenRecord";
77     static final String SCREEN_RECORD_DESC_STRING = "Recording screen";
78     static final String SILENT_ICON_DESC_PREFIX_STRING = "Ringer silent";
79     static final String VIBRATE_ICON_DESC_PREFIX_STRING = "Ringer vibrate";
80     private final List<String> mStatusBarViewIds =
81             List.of(DATE_ID, CLOCK_ID, WIFI_ICON_ID, BATTERY_ID, BATTERY_LEVEL_TEXT_ID);
82 
StatusBar()83     StatusBar() {
84         verifyClockIsVisible();
85     }
86 
getNotificationIconsObjects()87     private static List<UiObject2> getNotificationIconsObjects() {
88         // As the container for notifications can change between the moment we get it and we get its
89         // children, we retry several times in case of failure. This aims at reducing flakiness.
90         return waitForValueCatchingStaleObjectExceptions(
91                 () -> "Failed to get status bar icons.",
92                 () -> {
93                     UiObject2 container =
94                             getUiDevice()
95                                     .wait(
96                                             Until.findObject(
97                                                     sysuiResSelector(
98                                                             NOTIFICATION_ICON_CONTAINER_ID)),
99                                             10000);
100                     return container != null ? container.getChildren() : Collections.emptyList();
101                 });
102     }
103 
104     /** Returns the number of notification icons visible on the status bar. */
getNotificationIconCount()105     public int getNotificationIconCount() {
106         List<UiObject2> icons = getNotificationIconsObjects();
107 
108         // 2 icons are never ellipsized, let's return them.
109         if (icons.size() <= 2) {
110             return icons.size();
111         }
112 
113         // Notification icons don't change visibility when collapsed.
114         // The same icon can represent either the notification and the ellipsis.
115         //
116         // For this reason, UiAutomator thinks the icons are always visible, when we're
117         // actually stacking them up on top of each other and drawing transparent.
118         //
119         // Let's check for overlap instead of view visibility.
120         int iconPadding =
121                 icons.get(1).getVisibleBounds().left - icons.get(0).getVisibleBounds().left;
122         int lastIcon = icons.size() - 1;
123         for (int i = 1; i < icons.size(); i++) {
124             lastIcon = i;
125             int padding =
126                     icons.get(i).getVisibleBounds().left - icons.get(i - 1).getVisibleBounds().left;
127             if (padding != iconPadding) {
128                 break;
129             }
130         }
131         return lastIcon;
132     }
133 
134     /** Verifies visibility of the battery percentage indicator. */
verifyBatteryPercentageVisibility(boolean expectedVisible)135     public void verifyBatteryPercentageVisibility(boolean expectedVisible) {
136         assumeFalse(newStatusBarIcons());
137         UiObject2 batteryIndication = getBatteryIndication();
138         assertThat(batteryIndication).isNotNull();
139 
140         if (expectedVisible) {
141             assertThat(batteryIndication.wait(Until.hasObject(PERCENTAGE_SELECTOR), 10000))
142                     .isTrue();
143         } else {
144             assertThat(batteryIndication.wait(Until.gone(PERCENTAGE_SELECTOR), 10000)).isTrue();
145         }
146     }
147 
getBatteryIndication()148     private static UiObject2 getBatteryIndication() {
149         return getUiDevice().wait(findObject(sysuiResSelector(BATTERY_ID)), 1000);
150     }
151 
152     /** Assert that clock indicator is visible. */
verifyClockIsVisible()153     private void verifyClockIsVisible() {
154         // Matches 12h or 24h time format
155         Pattern timePattern = Pattern.compile("^(?:[01]?\\d|2[0-3]):[0-5]\\d");
156         DeviceHelpers.waitForObj(
157                 By.pkg("com.android.systemui").text(timePattern),
158                 SHORT_WAIT,
159                 () -> "Clock should be visible.");
160     }
161 
162     /** Assert that clock indicator is NOT visible. */
verifyClockIsNotVisible()163     public void verifyClockIsNotVisible() {
164         assertWithMessage("StatusBar clock is visible")
165                 .that(
166                         getUiDevice()
167                                 .wait(Until.gone(sysuiResSelector(CLOCK_ID)), LONG_WAIT.toMillis()))
168                 .isTrue();
169     }
170 
171     /** Assert that the lights out notification dot is visible. */
verifyLightsOutDotIsVisible()172     public void verifyLightsOutDotIsVisible() {
173         SearchCondition<Boolean> searchCondition =
174                 Until.hasObject(NOTIFICATION_LIGHTS_OUT_DOT_SELECTOR);
175         assertThat(getUiDevice().wait(searchCondition, LONG_WAIT.toMillis())).isTrue();
176     }
177 
178     /** Assert that the lights out notification dot is NOT visible. */
verifyLightsOutDotIsNotVisible()179     public void verifyLightsOutDotIsNotVisible() {
180         SearchCondition<Boolean> searchCondition = Until.gone(NOTIFICATION_LIGHTS_OUT_DOT_SELECTOR);
181         assertThat(getUiDevice().wait(searchCondition, LONG_WAIT.toMillis())).isTrue();
182     }
183 
184     /** Returns the visible user switcher chip, or fails if it's not visible. */
getUserSwitcherChip()185     public UserSwitcherChip getUserSwitcherChip() {
186         return new UserSwitcherChip();
187     }
188 
189     /** Gets the icons object on the right hand side StatusBar. This is for screenshot test. */
getBoundsOfRightHandSideStatusBarIconsForScreenshot()190     public Rect getBoundsOfRightHandSideStatusBarIconsForScreenshot() {
191         return DeviceHelpers.INSTANCE
192                 .waitForObj(
193                         /* UiDevice= */ getUiDevice(),
194                         /* selector= */ sysuiResSelector(STATUS_ICON_CONTAINER_ID),
195                         /* timeout= */ SHORT_WAIT,
196                         /* errorProvider= */ () ->
197                                 "StatusBar icons are not found on the right hand side.")
198                 .getVisibleBounds();
199     }
200 
201     /** Assert that alarm icon is visible. */
verifyAlarmIconIsVisible()202     public void verifyAlarmIconIsVisible() {
203         DeviceHelpers.waitForObj(
204                 By.pkg("com.android.systemui")
205                         .clazz("android.widget.ImageView")
206                         .descContains(ALARM_ICON_DESC_PREFIX_STRING),
207                 SHORT_WAIT,
208                 () -> "Alarm icon should be visible.");
209     }
210 
211     /** Assert that airplane mode icon is visible. */
verifyAirplaneModeIconIsVisible()212     public void verifyAirplaneModeIconIsVisible() {
213         assertThat(
214                         getUiDevice()
215                                 .wait(
216                                         Until.hasObject(
217                                                 sysuiResSelector(STATUS_ICON_CONTAINER_ID)
218                                                         .hasChild(
219                                                                 By.desc(AIRPLANE_MODE_ICON_DESC))),
220                                         SHORT_WAIT.toMillis()))
221                 .isTrue();
222     }
223 
224     /** Assert that data saver icon is visible. */
verifyDataSaverIconIsVisible()225     public void verifyDataSaverIconIsVisible() {
226         assertThat(
227                         getUiDevice()
228                                 .wait(
229                                         Until.hasObject(
230                                                 sysuiResSelector(STATUS_ICON_CONTAINER_ID)
231                                                         .hasChild(By.desc(DATA_SAVER_ICON_DESC))),
232                                         SHORT_WAIT.toMillis()))
233                 .isTrue();
234     }
235 
236     /** Assert that dock defend icon is visible. */
verifyDockDefendIconIsVisible()237     public void verifyDockDefendIconIsVisible() {
238         assertWithMessage("Dock Defend icon should be visible in status bar.")
239                 .that(
240                         getUiDevice()
241                                 .wait(
242                                         Until.hasObject(
243                                                 sysuiResSelector(UI_SYSTEM_ICONS_ID)
244                                                         .hasDescendant(
245                                                                 By.descContains(
246                                                                         DOCK_DEFEND_ICON_SUFFIX_STRING))),
247                                         SHORT_WAIT.toMillis()))
248                 .isTrue();
249     }
250 
251     /** Asserts that user switcher chip is invisible. */
assertUserSwitcherChipIsInvisible()252     public void assertUserSwitcherChipIsInvisible() {
253         DeviceHelpers.INSTANCE.assertInvisible(
254                 sysuiResSelector(UserSwitcherChip.USER_SWITCHER_CONTAINER_ID),
255                 SHORT_WAIT,
256                 () -> "User switcher chip should be invisible in status bar.");
257     }
258 
259     /** Returns the clock time value on StatusBar. Experimental. */
getClockTime()260     public String getClockTime() {
261         UiObject2 clockTime =
262                 DeviceHelpers.INSTANCE.waitForObj(
263                         /* UiDevice= */ getUiDevice(),
264                         /* selector= */ sysuiResSelector(CLOCK_ID),
265                         /* timeout= */ SHORT_WAIT,
266                         /* errorProvider= */ () -> "Clock not found.");
267         return clockTime.getText();
268     }
269 
270     /** Returns the position of views in StatusBar. */
getStatusBarViewPositions()271     public Set<UnfoldAnimationTestingUtils.Icon> getStatusBarViewPositions() {
272         Set<UnfoldAnimationTestingUtils.Icon> statusBarViewPositions = new HashSet<>();
273         mStatusBarViewIds.forEach(
274                 viewId -> {
275                     UiObject2 viewUiObject =
276                             DeviceHelpers.INSTANCE.waitForNullableObj(
277                                     sysuiResSelector(viewId), SHORT_WAIT);
278                     if (viewUiObject != null) {
279                         Rect iconPosition = viewUiObject.getVisibleBounds();
280                         statusBarViewPositions.add(
281                                 new UnfoldAnimationTestingUtils.Icon(
282                                         viewId,
283                                         new Point(iconPosition.centerX(), iconPosition.centerY())));
284                     }
285                 });
286         return statusBarViewPositions;
287     }
288 
289     /** Assert that DND icon is visible. */
verifyDndIconIsVisible()290     public void verifyDndIconIsVisible() {
291         assertWithMessage("DND icon should be visible in status bar.")
292                 .that(
293                         getUiDevice()
294                                 .wait(
295                                         Until.hasObject(
296                                                 sysuiResSelector(STATUS_ICON_CONTAINER_ID)
297                                                         .hasChild(By.descContains(DND_ICON_DESC))),
298                                         SHORT_WAIT.toMillis()))
299                 .isTrue();
300     }
301 
302     /** Returns the value of the battery level on StatusBar. Experimental. */
getBatteryLevel()303     public String getBatteryLevel() {
304         UiObject2 batteryPercentage =
305                 DeviceHelpers.INSTANCE.waitForObj(
306                         /* UiDevice= */ getUiDevice(),
307                         /* selector= */ sysuiResSelector(BATTERY_LEVEL_TEXT_ID),
308                         /* timeout= */ LONG_WAIT,
309                         /* errorProvider= */ () -> "Battery percentage not found.");
310         return batteryPercentage.getText();
311     }
312 
313     /** Assert that WiFi icon is visible. Experimental. */
verifyWifiIconIsVisible()314     public void verifyWifiIconIsVisible() {
315         DeviceHelpers.INSTANCE.assertVisible(
316                 sysuiResSelector(UI_SYSTEM_ICONS_ID).hasDescendant(sysuiResSelector(WIFI_ICON_ID)),
317                 LONG_WAIT,
318                 () -> "WiFi icon should be visible in status bar.");
319     }
320 
321     /** Assert that silent icon is visible. */
verifySilentIconIsVisible()322     public void verifySilentIconIsVisible() {
323         DeviceHelpers.INSTANCE.assertVisible(
324                 sysuiResSelector(STATUS_ICON_CONTAINER_ID)
325                         .hasChild(By.descContains(SILENT_ICON_DESC_PREFIX_STRING)),
326                 LONG_WAIT,
327                 () -> "Silent icon should be visible in status bar.");
328     }
329 
330     /** Assert that the screen record chip is visible. */
verifyScreenRecordChipIsVisible()331     public void verifyScreenRecordChipIsVisible() {
332         String resSelector;
333         if (statusBarChipsModernization()) {
334             resSelector = SCREEN_RECORDING_CHIP_ID;
335         } else {
336             resSelector = ONGOING_ACTIVITY_CHIP_ICON_ID;
337         }
338         DeviceHelpers.INSTANCE.assertVisible(
339                 sysuiResSelector(resSelector)
340                         .hasDescendant(By.descContains(SCREEN_RECORD_DESC_STRING)),
341                 LONG_WAIT,
342                 () -> "Recording chip should be visible in status bar.");
343     }
344 
345     /** Assert there is at least one status icon visible. */
verifyAtLeastOneStatusIconIsVisible()346     public void verifyAtLeastOneStatusIconIsVisible() {
347         UiObject2 statusBar = DeviceHelpers.waitForObj(sysuiResSelector(STATUS_ICON_CONTAINER_ID));
348         assertWithMessage("Status bar should have at least one icon visible")
349                 .that(statusBar.getChildCount())
350                 .isGreaterThan(0);
351     }
352 
353     /** Verifies visibility of the vibrate icon. */
assertVibrateIconVisibility(boolean visible)354     public void assertVibrateIconVisibility(boolean visible) {
355         DeviceHelpers.INSTANCE.assertVisibility(
356                 /* UiDevice= */ getUiDevice(),
357                 /* selector= */ sysuiResSelector(STATUS_ICON_CONTAINER_ID)
358                         .hasChild(By.descContains(VIBRATE_ICON_DESC_PREFIX_STRING)),
359                 /* visible= */ visible,
360                 /* timeout= */ LONG_WAIT);
361     }
362 
363     /**
364      * Fails when {@link #getNotificationIconCount()} doesn't become the expected value within a
365      * timeout.
366      */
assertNotificationIconCount(int expected)367     public void assertNotificationIconCount(int expected) {
368         ensureThat(
369                 "Visible StatusBar icon count should be " + expected,
370                 () -> getNotificationIconCount() == expected);
371     }
372 }
373