1 /*
2  * Copyright (C) 2019 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.uibench.microbenchmark;
18 
19 import android.app.ActivityManager;
20 import android.app.HomeVisibilityListener;
21 import android.app.Instrumentation;
22 import android.app.UiAutomation;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.os.Bundle;
27 import android.os.SystemClock;
28 import android.platform.helpers.AbstractStandardAppHelper2;
29 import android.util.DisplayMetrics;
30 import android.view.KeyEvent;
31 import android.widget.EditText;
32 import android.widget.ListView;
33 
34 import androidx.test.uiautomator.By;
35 import androidx.test.uiautomator.Direction;
36 import androidx.test.uiautomator.UiObject2;
37 import androidx.test.uiautomator.Until;
38 
39 import com.android.compatibility.common.util.ShellIdentityUtils;
40 import com.android.compatibility.common.util.TestUtils;
41 
42 import junit.framework.Assert;
43 
44 import java.util.concurrent.atomic.AtomicBoolean;
45 
46 public class UiBenchJankHelper extends AbstractStandardAppHelper2 implements IUiBenchJankHelper {
47     public static final int LONG_TIMEOUT = 5000;
48     public static final int FULL_TEST_DURATION = 25000;
49     public static final int FIND_OBJECT_TIMEOUT = 250;
50     public static final int SHORT_TIMEOUT = 2000;
51     public static final int EXPECTED_FRAMES = 100;
52     public static final int KEY_DELAY = 1000;
53     public static final String EXTRA_BITMAP_UPLOAD = "extra_bitmap_upload";
54     public static final String EXTRA_SHOW_FAST_LANE = "extra_show_fast_lane";
55 
56     /**
57      * Only to be used for initial-fling tests, or similar cases where perf during brief experience
58      * is important.
59      */
60     public static final int SHORT_EXPECTED_FRAMES = 30;
61 
62     public static final String PACKAGE_NAME = "com.android.test.uibench";
63 
64     public static final String APP_LAUNCHER_NAME = "UiBench";
65 
66     private static final int SLOW_FLING_SPEED = 3000; // compare to UiObject2#DEFAULT_FLING_SPEED
67 
68     // Main UiObject2 exercised by the test.
69     private UiObject2 mContents, mNavigation;
70 
UiBenchJankHelper(Instrumentation instr)71     public UiBenchJankHelper(Instrumentation instr) {
72         super(instr);
73     }
74 
75     /** {@inheritDoc} */
76     @Override
getPackage()77     public String getPackage() {
78         return PACKAGE_NAME;
79     }
80 
81     /** {@inheritDoc} */
82     @Override
getLauncherName()83     public String getLauncherName() {
84         return APP_LAUNCHER_NAME;
85     }
86 
87     /** {@inheritDoc} */
88     @Override
dismissInitialDialogs()89     public void dismissInitialDialogs() {}
90 
91     /** Launch activity using intent */
launchActivity(String activityName, Bundle extras, String verifyText)92     void launchActivity(String activityName, Bundle extras, String verifyText) {
93         ComponentName cn =
94                 new ComponentName(PACKAGE_NAME, String.format("%s.%s", PACKAGE_NAME, activityName));
95         Intent intent = new Intent(Intent.ACTION_MAIN);
96         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
97         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
98         if (extras != null) {
99             intent.putExtras(extras);
100         }
101         intent.setComponent(cn);
102         // Launch the activity
103         mInstrumentation.getContext().startActivity(intent);
104         UiObject2 expectedTextCmp =
105                 mDevice.wait(Until.findObject(By.text(verifyText)), LONG_TIMEOUT);
106         Assert.assertNotNull(String.format("Issue in opening %s", activityName), expectedTextCmp);
107     }
108 
launchActivity(String activityName, String verifyText)109     void launchActivity(String activityName, String verifyText) {
110         final UiAutomation uiAutomation = mInstrumentation.getUiAutomation();
111         uiAutomation.adoptShellPermissionIdentity();
112         try {
113             launchActivity(activityName, null, verifyText);
114         } finally {
115             uiAutomation.dropShellPermissionIdentity();
116         }
117     }
118 
launchActivityAndAssert(String activityName, String verifyText)119     void launchActivityAndAssert(String activityName, String verifyText) {
120         launchActivity(activityName, verifyText);
121         mContents =
122                 mDevice.wait(Until.findObject(By.res("android", "content")), FIND_OBJECT_TIMEOUT);
123         Assert.assertNotNull(activityName + " isn't found", mContents);
124     }
125 
getEdgeSensitivity()126     int getEdgeSensitivity() {
127         int resId =
128                 mInstrumentation
129                         .getContext()
130                         .getResources()
131                         .getIdentifier("config_backGestureInset", "dimen", "android");
132         return mInstrumentation.getContext().getResources().getDimensionPixelSize(resId) + 1;
133     }
134 
135     /** To perform the fling down and up on given content for flingCount number of times */
136     @Override
flingUpDown(int flingCount)137     public void flingUpDown(int flingCount) {
138         flingUpDown(flingCount, false);
139     }
140 
141     @Override
flingDownUp(int flingCount)142     public void flingDownUp(int flingCount) {
143         flingUpDown(flingCount, true);
144     }
145 
flingUpDown(int flingCount, boolean reverse)146     void flingUpDown(int flingCount, boolean reverse) {
147         mContents.setGestureMargin(getEdgeSensitivity());
148         for (int count = 0; count < flingCount; count++) {
149             SystemClock.sleep(SHORT_TIMEOUT);
150             mContents.fling(reverse ? Direction.UP : Direction.DOWN);
151             SystemClock.sleep(SHORT_TIMEOUT);
152             mContents.fling(reverse ? Direction.DOWN : Direction.UP);
153         }
154     }
155 
156     /** To perform the swipe right and left on given content for swipeCount number of times */
157     @Override
swipeRightLeft(int swipeCount)158     public void swipeRightLeft(int swipeCount) {
159         mNavigation =
160                 mDevice.wait(
161                         Until.findObject(By.desc("Open navigation drawer")), FIND_OBJECT_TIMEOUT);
162         mContents.setGestureMargin(getEdgeSensitivity());
163         for (int count = 0; count < swipeCount; count++) {
164             SystemClock.sleep(SHORT_TIMEOUT);
165             mNavigation.click();
166             SystemClock.sleep(SHORT_TIMEOUT);
167             mContents.swipe(Direction.LEFT, 1);
168         }
169     }
170 
171     @Override
slowSingleFlingDown()172     public void slowSingleFlingDown() {
173         SystemClock.sleep(SHORT_TIMEOUT);
174         Context context = mInstrumentation.getContext();
175         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
176         mContents.setGestureMargin(getEdgeSensitivity());
177         mContents.fling(Direction.DOWN, (int) (SLOW_FLING_SPEED * displayMetrics.density));
178         mDevice.waitForIdle();
179     }
180 
181     @Override
openDialogList()182     public void openDialogList() {
183         launchActivity("DialogListActivity", "Dialog");
184         mContents = mDevice.wait(Until.findObject(By.clazz(ListView.class)), FIND_OBJECT_TIMEOUT);
185         Assert.assertNotNull("Dialog List View isn't found", mContents);
186     }
187 
188     @Override
openFullscreenOverdraw()189     public void openFullscreenOverdraw() {
190         launchActivity("FullscreenOverdrawActivity", "General/Fullscreen Overdraw");
191     }
192 
193     @Override
openGLTextureView()194     public void openGLTextureView() {
195         launchActivity("GlTextureViewActivity", "General/GL TextureView");
196     }
197 
198     @Override
openInvalidate()199     public void openInvalidate() {
200         launchActivity("InvalidateActivity", "General/Invalidate");
201     }
202 
203     @Override
openInvalidateTree()204     public void openInvalidateTree() {
205         launchActivity("InvalidateTreeActivity", "General/Invalidate Tree");
206     }
207 
208     @Override
openTrivialAnimation()209     public void openTrivialAnimation() {
210         launchActivity("TrivialAnimationActivity", "General/Trivial Animation");
211     }
212 
213     @Override
openTrivialListView()214     public void openTrivialListView() {
215         launchActivityAndAssert("TrivialListActivity", "General/Trivial ListView");
216     }
217 
218     @Override
openFadingEdgeListView()219     public void openFadingEdgeListView() {
220         launchActivityAndAssert("FadingEdgeListActivity", "General/Fading Edge ListView");
221     }
222 
223     @Override
openSaveLayerInterleaveActivity()224     public void openSaveLayerInterleaveActivity() {
225         launchActivityAndAssert("SaveLayerInterleaveActivity", "General/SaveLayer Animation");
226     }
227 
228     @Override
openTrivialRecyclerView()229     public void openTrivialRecyclerView() {
230         launchActivityAndAssert("TrivialRecyclerViewActivity", "General/Trivial RecyclerView");
231     }
232 
233     @Override
openSlowBindRecyclerView()234     public void openSlowBindRecyclerView() {
235         launchActivityAndAssert("SlowBindRecyclerViewActivity", "General/Slow Bind RecyclerView");
236     }
237 
238     @Override
openSlowNestedRecyclerView()239     public void openSlowNestedRecyclerView() {
240         launchActivityAndAssert(
241                 "SlowNestedRecyclerViewActivity", "General/Slow Nested RecyclerView");
242     }
243 
244     @Override
openInflatingListView()245     public void openInflatingListView() {
246         launchActivityAndAssert("InflatingListActivity", "Inflation/Inflating ListView");
247     }
248 
249     @Override
openInflatingEmojiListView()250     public void openInflatingEmojiListView() {
251         launchActivityAndAssert(
252                 "InflatingEmojiListActivity", "Inflation/Inflating ListView with Emoji");
253     }
254 
255     @Override
openInflatingHanListView()256     public void openInflatingHanListView() {
257         launchActivityAndAssert(
258                 "InflatingHanListActivity", "Inflation/Inflating ListView with Han Characters");
259     }
260 
261     @Override
openInflatingLongStringListView()262     public void openInflatingLongStringListView() {
263         launchActivityAndAssert(
264                 "InflatingLongStringListActivity", "Inflation/Inflating ListView with long string");
265     }
266 
267     @Override
openNavigationDrawerActivity()268     public void openNavigationDrawerActivity() {
269         launchActivityAndAssert("NavigationDrawerActivity", "Navigation Drawer Activity");
270         mContents.setGestureMargins(0, 0, 10, 0);
271     }
272 
273     @Override
openNotificationShade()274     public void openNotificationShade() {
275         launchActivityAndAssert("NotificationShadeActivity", "Notification Shade");
276     }
277 
278     @Override
openResizeHWLayer()279     public void openResizeHWLayer() {
280         launchActivity("ResizeHWLayerActivity", "General/Resize HW Layer");
281     }
282 
283     @Override
openClippedListView()284     public void openClippedListView() {
285         launchActivityAndAssert("ClippedListActivity", "General/Clipped ListView");
286     }
287 
288     @Override
openLeanbackActivity( boolean extraBitmapUpload, boolean extraShowFastLane, String activityName, String expectedText)289     public void openLeanbackActivity(
290             boolean extraBitmapUpload,
291             boolean extraShowFastLane,
292             String activityName,
293             String expectedText) {
294         Bundle extrasBundle = new Bundle();
295         extrasBundle.putBoolean(EXTRA_BITMAP_UPLOAD, extraBitmapUpload);
296         extrasBundle.putBoolean(EXTRA_SHOW_FAST_LANE, extraShowFastLane);
297         launchActivity(activityName, extrasBundle, expectedText);
298     }
299 
pressKeyCode(int keyCode)300     void pressKeyCode(int keyCode) {
301         SystemClock.sleep(KEY_DELAY);
302         mDevice.pressKeyCode(keyCode);
303     }
304 
305     @Override
scrollDownAndUp(int count)306     public void scrollDownAndUp(int count) {
307         for (int i = 0; i < count; i++) {
308             pressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN);
309         }
310         for (int i = 0; i < count; i++) {
311             pressKeyCode(KeyEvent.KEYCODE_DPAD_UP);
312         }
313     }
314 
315     // Open Bitmap Upload
316     @Override
openBitmapUpload()317     public void openBitmapUpload() {
318         launchActivity("BitmapUploadActivity", "Rendering/Bitmap Upload");
319     }
320 
321     // Open Shadow Grid
322     @Override
openRenderingList()323     public void openRenderingList() {
324         launchActivity("ShadowGridActivity", "Rendering/Shadow Grid");
325         mContents = mDevice.wait(Until.findObject(By.clazz(ListView.class)), FIND_OBJECT_TIMEOUT);
326         Assert.assertNotNull("Shadow Grid list isn't found", mContents);
327     }
328 
329     // Open EditText Typing
330     @Override
openEditTextTyping()331     public void openEditTextTyping() {
332         launchActivity("EditTextTypeActivity", "Text/EditText Typing");
333         mContents = mDevice.wait(Until.findObject(By.clazz(EditText.class)), FIND_OBJECT_TIMEOUT);
334     }
335 
336     // Open Layout Cache High Hitrate
337     @Override
openLayoutCacheHighHitrate()338     public void openLayoutCacheHighHitrate() {
339         launchActivity("TextCacheHighHitrateActivity", "Text/Layout Cache High Hitrate");
340         mContents = mDevice.wait(Until.findObject(By.clazz(ListView.class)), FIND_OBJECT_TIMEOUT);
341         Assert.assertNotNull("LayoutCacheHighHitrateContents isn't found", mContents);
342     }
343 
344     // Open Layout Cache Low Hitrate
345     @Override
openLayoutCacheLowHitrate()346     public void openLayoutCacheLowHitrate() {
347         launchActivity("TextCacheLowHitrateActivity", "Text/Layout Cache Low Hitrate");
348         mContents = mDevice.wait(Until.findObject(By.clazz(ListView.class)), FIND_OBJECT_TIMEOUT);
349         Assert.assertNotNull("LayoutCacheLowHitrateContents isn't found", mContents);
350     }
351 
352     // Open Transitions
353     @Override
openActivityTransition()354     public void openActivityTransition() {
355         launchActivity("ActivityTransition", "Transitions/Activity Transition");
356     }
357 
358     @Override
openWindowInsetsController()359     public void openWindowInsetsController() {
360         launchActivityAndAssert("WindowInsetsControllerActivity", "WindowInsetsControllerActivity");
361     }
362 
363     // Get the image to click
364     @Override
clickImage(String imageName)365     public void clickImage(String imageName) {
366         UiObject2 image =
367                 mDevice.wait(
368                         Until.findObject(By.res(PACKAGE_NAME, imageName)), FIND_OBJECT_TIMEOUT);
369         Assert.assertNotNull(imageName + "Image not found", image);
370         image.clickAndWait(Until.newWindow(), FIND_OBJECT_TIMEOUT);
371         mDevice.pressBack();
372     }
373 
374     // Open Scrollable WebView from WebView test
375     @Override
openScrollableWebView()376     public void openScrollableWebView() {
377         launchActivity("ScrollableWebViewActivity", "WebView/Scrollable WebView");
378         mContents =
379                 mDevice.wait(Until.findObject(By.res("android", "content")), FIND_OBJECT_TIMEOUT);
380     }
381 
382     // Exit the app and ensure going back to home successfully
383     @Override
exit()384     public void exit() {
385         final ActivityManager activityManager =
386                 mInstrumentation.getContext().getSystemService(ActivityManager.class);
387         final AtomicBoolean isHomeVisible = new AtomicBoolean();
388         mDevice.pressHome();
389         mDevice.waitForIdle();
390         HomeVisibilityListener homeVisibilityListener =
391                 new HomeVisibilityListener() {
392                     @Override
393                     public void onHomeVisibilityChanged(boolean isHomeActivityVisible) {
394                         isHomeVisible.set(isHomeActivityVisible);
395                     }
396                 };
397         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
398                 activityManager,
399                 (am) -> am.addHomeVisibilityListener(Runnable::run, homeVisibilityListener));
400         try {
401             TestUtils.waitUntil("Failed to exit the app to launcher", isHomeVisible::get);
402         } catch (Exception e) {
403             throw new RuntimeException(e);
404         } finally {
405             ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
406                     activityManager,
407                     (am) -> am.removeHomeVisibilityListener(homeVisibilityListener));
408         }
409     }
410 }
411