1 /*
2  * Copyright (C) 2015 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.shell;
18 
19 import android.app.Instrumentation;
20 import android.app.StatusBarManager;
21 import android.os.SystemClock;
22 import android.text.format.DateUtils;
23 import android.util.Log;
24 
25 import androidx.test.uiautomator.By;
26 import androidx.test.uiautomator.UiDevice;
27 import androidx.test.uiautomator.UiObject;
28 import androidx.test.uiautomator.UiObject2;
29 import androidx.test.uiautomator.UiObjectNotFoundException;
30 import androidx.test.uiautomator.UiSelector;
31 import androidx.test.uiautomator.Until;
32 
33 import static junit.framework.Assert.assertFalse;
34 import static junit.framework.Assert.assertNotNull;
35 import static junit.framework.Assert.assertTrue;
36 
37 import java.util.List;
38 
39 /**
40  * A helper class for UI-related testing tasks.
41  */
42 final class UiBot {
43 
44     private static final String TAG = "UiBot";
45     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
46     private static final String ANDROID_PACKAGE = "android";
47 
48     private static final long SHORT_UI_TIMEOUT_MS = (3 * DateUtils.SECOND_IN_MILLIS);
49 
50     private final Instrumentation mInstrumentation;
51     private final UiDevice mDevice;
52     private final int mTimeout;
53 
UiBot(Instrumentation instrumentation, int timeout)54     public UiBot(Instrumentation instrumentation, int timeout) {
55         mInstrumentation = instrumentation;
56         mDevice = UiDevice.getInstance(instrumentation);
57         mTimeout = timeout;
58     }
59 
60     /**
61      * Opens the system notification and gets a UiObject with the text.
62      *
63      * @param text Notification's text as displayed by the UI.
64      * @return notification object.
65      */
getNotification(String text)66     public UiObject getNotification(String text) {
67         boolean opened = mDevice.openNotification();
68         Log.v(TAG, "openNotification(): " + opened);
69         boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGE)), mTimeout);
70         assertTrue("could not get system ui (" + SYSTEMUI_PACKAGE + ")", gotIt);
71 
72         return getObject(text);
73     }
74 
75     /**
76      * Opens the system notification and gets a notification containing the text.
77      *
78      * @param text Notification's text as displayed by the UI.
79      * @return notification object.
80      */
getNotification2(String text)81     public UiObject2 getNotification2(String text) {
82         boolean opened = mDevice.openNotification();
83         Log.v(TAG, "openNotification(): " + opened);
84         final UiObject2 notificationScroller = mDevice.wait(Until.findObject(
85                 By.res(SYSTEMUI_PACKAGE, "notification_stack_scroller")), mTimeout);
86         assertNotNull("could not get notification stack scroller", notificationScroller);
87         final List<UiObject2> notificationList = notificationScroller.getChildren();
88         for (UiObject2 notification: notificationList) {
89             final UiObject2 notificationText = notification.findObject(By.textContains(text));
90             if (notificationText != null) {
91                 return notification;
92             }
93         }
94         return null;
95     }
96 
97     /**
98      * Expands the notification.
99      *
100      * @param notification The notification object returned by {@link #getNotification2(String)}.
101      */
expandNotification(UiObject2 notification)102     public void expandNotification(UiObject2 notification) {
103         final UiObject2 expandBtn =  notification.findObject(
104                 By.res(ANDROID_PACKAGE, "expand_button"));
105         if (expandBtn.getContentDescription().equals("Collapse")) {
106             return;
107         }
108         expandBtn.click();
109         mDevice.waitForIdle();
110     }
111 
collapseStatusBar()112     public void collapseStatusBar() throws Exception {
113         // TODO: mDevice should provide such method..
114         StatusBarManager sbm =
115                 (StatusBarManager) mInstrumentation.getContext().getSystemService("statusbar");
116         sbm.collapsePanels();
117     }
118 
119     /**
120      * Opens the system notification and clicks a given notification.
121      *
122      * @param text Notificaton's text as displayed by the UI.
123      */
clickOnNotification(String text)124     public void clickOnNotification(String text) {
125         UiObject notification = getNotification(text);
126         click(notification, "bug report notification");
127     }
128 
129     /**
130      * Gets an object that might not yet be available in current UI.
131      *
132      * @param text Object's text as displayed by the UI.
133      */
getObject(String text)134     public UiObject getObject(String text) {
135         boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
136         assertTrue("object with text '(" + text + "') not visible yet", gotIt);
137         return getVisibleObject(text);
138     }
139 
140     /**
141      * Gets an object that might not yet be available in current UI.
142      *
143      * @param id Object's fully-qualified resource id (like {@code android:id/button1})
144      */
getObjectById(String id)145     public UiObject getObjectById(String id) {
146         boolean gotIt = mDevice.wait(Until.hasObject(By.res(id)), mTimeout);
147         assertTrue("object with id '(" + id + "') not visible yet", gotIt);
148         return getVisibleObjectById(id);
149     }
150 
151     /**
152      * Gets an object which is guaranteed to be present in the current UI.
153      *
154      * @param text Object's text as displayed by the UI.
155      */
getVisibleObject(String text)156     public UiObject getVisibleObject(String text) {
157         UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
158         assertTrue("could not find object with text '" + text + "'", uiObject.exists());
159         return uiObject;
160     }
161 
162     /**
163      * Gets an object which is guaranteed to be present in the current UI.
164      *
165      * @param text Object's text as displayed by the UI.
166      */
getVisibleObjectById(String id)167     public UiObject getVisibleObjectById(String id) {
168         UiObject uiObject = mDevice.findObject(new UiSelector().resourceId(id));
169         assertTrue("could not find object with id '" + id+ "'", uiObject.exists());
170         return uiObject;
171     }
172 
173     /**
174      * Asserts an object is not visible.
175      */
assertNotVisibleById(String id)176     public void assertNotVisibleById(String id) {
177         // TODO: not working when the bugreport dialog is shown, it hangs until the dialog is
178         // dismissed and hence always work.
179         boolean hasIt = mDevice.hasObject(By.res(id));
180         assertFalse("should not have found object with id '" + id+ "'", hasIt);
181     }
182 
183 
184     /**
185      * Clicks on a UI element.
186      *
187      * @param uiObject UI element to be clicked.
188      * @param description Elements's description used on logging statements.
189      */
click(UiObject uiObject, String description)190     public void click(UiObject uiObject, String description) {
191         try {
192             boolean clicked = uiObject.click();
193             // TODO: assertion below fails sometimes, even though the click succeeded,
194             // (specially when clicking the "Just Once" button), so it's currently just logged.
195             // assertTrue("could not click on object '" + description + "'", clicked);
196 
197             Log.v(TAG, "onClick for " + description + ": " + clicked);
198         } catch (UiObjectNotFoundException e) {
199             throw new IllegalStateException("exception when clicking on object '" + description
200                     + "'", e);
201         }
202     }
203 
204     /**
205      * Chooses a given activity to handle an Intent.
206      *
207      * @param name name of the activity as displayed in the UI (typically the value set by
208      *            {@code android:label} in the manifest).
209      */
chooseActivity(String name)210     public void chooseActivity(String name) {
211         // It uses an intent chooser now, so just getting the activity by text is enough...
212         final String share = mInstrumentation.getContext().getString(
213                 com.android.internal.R.string.share);
214         boolean gotIt = mDevice.wait(Until.hasObject(By.text(share)), mTimeout);
215         assertTrue("could not get share activity (" + share + ")", gotIt);
216         swipeUp();
217         SystemClock.sleep(SHORT_UI_TIMEOUT_MS);
218         UiObject activity = getObject(name);
219         click(activity, name);
220     }
221 
pressBack()222     public void pressBack() {
223         mDevice.pressBack();
224     }
225 
turnScreenOn()226     public void turnScreenOn() throws Exception {
227         mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
228         mDevice.executeShellCommand("wm dismiss-keyguard");
229         mDevice.waitForIdle();
230     }
231 
swipeUp()232     public void swipeUp() {
233         mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() * 3 / 4,
234                 mDevice.getDisplayWidth() / 2, 0, 30);
235     }
236 }
237