1 /*
2  * Copyright (C) 2020 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.view;
18 
19 import static android.view.Display.DEFAULT_DISPLAY;
20 
21 import static org.junit.Assert.assertTrue;
22 
23 import android.app.Activity;
24 import android.app.Instrumentation;
25 import android.content.Context;
26 import android.graphics.Rect;
27 import android.os.SystemClock;
28 import android.perftests.utils.BenchmarkState;
29 import android.perftests.utils.PerfStatusReporter;
30 import android.perftests.utils.PerfTestActivity;
31 import android.view.inputmethod.InputMethodManager;
32 import android.widget.EditText;
33 
34 import androidx.test.InstrumentationRegistry;
35 import androidx.test.filters.LargeTest;
36 import androidx.test.rule.ActivityTestRule;
37 
38 import org.junit.Before;
39 import org.junit.Rule;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.junit.runners.Parameterized;
43 
44 import java.util.Arrays;
45 import java.util.Collection;
46 import java.util.concurrent.CountDownLatch;
47 import java.util.concurrent.TimeUnit;
48 
49 @LargeTest
50 @RunWith(Parameterized.class)
51 public class InputStageBenchmark {
52     @Parameterized.Parameters(name = "mShowIme({0}), mHandlePreIme({1})")
cases()53     public static Collection cases() {
54         return Arrays.asList(new Object[][] {
55                 { false /* no ime */, false /* skip preime */},
56                 { true /* show ime */, false /* skip preime */},
57                 { true /* show ime */, true /* handle preime */}
58         });
59     }
60 
61     @Rule
62     public final ActivityTestRule<PerfTestActivity> mActivityRule =
63             new ActivityTestRule<>(PerfTestActivity.class);
64     @Rule
65     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
66 
67     @Parameterized.Parameter(0)
68     public boolean mShowIme;
69     @Parameterized.Parameter(1)
70     public boolean mHandlePreIme;
71 
72     private Instrumentation mInstrumentation;
73     private Window mWindow;
74     private CountDownLatch mWaitForReceiveInput;
75     private static final long TIMEOUT_MS = 5000;
76 
77     class InstrumentedView extends View {
InstrumentedView(Context context)78         InstrumentedView(Context context) {
79             super(context);
80             setFocusable(true);
81         }
82 
83         @Override
dispatchKeyEventPreIme(KeyEvent event)84         public boolean dispatchKeyEventPreIme(KeyEvent event) {
85             if (mHandlePreIme) {
86                 mWaitForReceiveInput.countDown();
87             }
88             return mHandlePreIme;
89         }
90 
91         @Override
dispatchTouchEvent(MotionEvent event)92         public boolean dispatchTouchEvent(MotionEvent event) {
93             mWaitForReceiveInput.countDown();
94             return true;
95         }
96 
97         @Override
dispatchKeyEvent(KeyEvent event)98         public boolean dispatchKeyEvent(KeyEvent event) {
99             mWaitForReceiveInput.countDown();
100             return true;
101         }
102     }
103 
104     class InstrumentedEditText extends EditText {
InstrumentedEditText(Context context)105         InstrumentedEditText(Context context) {
106             super(context);
107             setFocusable(true);
108         }
109 
110         @Override
dispatchKeyEventPreIme(KeyEvent event)111         public boolean dispatchKeyEventPreIme(KeyEvent event) {
112             if (mHandlePreIme) {
113                 mWaitForReceiveInput.countDown();
114             }
115             return mHandlePreIme;
116         }
117 
118         @Override
dispatchTouchEvent(MotionEvent event)119         public boolean dispatchTouchEvent(MotionEvent event) {
120             mWaitForReceiveInput.countDown();
121             return true;
122         }
123 
124         @Override
dispatchKeyEvent(KeyEvent event)125         public boolean dispatchKeyEvent(KeyEvent event) {
126             mWaitForReceiveInput.countDown();
127             return true;
128         }
129     }
130 
showSoftKeyboard(View view)131     private CountDownLatch showSoftKeyboard(View view) {
132         final CountDownLatch waitForIme = new CountDownLatch(1);
133         view.setOnApplyWindowInsetsListener((v, insets) -> {
134             if (insets.isVisible(WindowInsets.Type.ime())) {
135                 waitForIme.countDown();
136             }
137             return insets;
138         });
139 
140         assertTrue("Failed to request focus.", view.requestFocus());
141         final InputMethodManager imm =
142                 mActivityRule.getActivity().getSystemService(InputMethodManager.class);
143         imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
144 
145         return waitForIme;
146     }
147 
148     @Before
setUp()149     public void setUp() {
150         mInstrumentation = InstrumentationRegistry.getInstrumentation();
151         final Activity activity = mActivityRule.getActivity();
152 
153         final CountDownLatch[] waitForIme = new CountDownLatch[1];
154         mInstrumentation.runOnMainSync(() -> {
155             mWindow = mActivityRule.getActivity().getWindow();
156 
157             if (mShowIme) {
158                 final EditText edit = new InstrumentedEditText(activity);
159                 mWindow.setContentView(edit);
160                 waitForIme[0] = showSoftKeyboard(edit);
161             } else {
162                 final View v = new InstrumentedView(activity);
163                 // set FLAG_LOCAL_FOCUS_MODE to prevent delivering input events to the ime
164                 // in ImeInputStage.
165                 mWindow.addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
166                 mWindow.setContentView(v);
167                 assertTrue("Failed to request focus.", v.requestFocus());
168             }
169         });
170         if (waitForIme[0] != null) {
171             try {
172                 assertTrue("Failed to show InputMethod.",
173                         waitForIme[0].await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
174             } catch (InterruptedException e) {
175                 throw new RuntimeException(e);
176             }
177         }
178         mInstrumentation.waitForIdleSync();
179     }
180 
injectInputEvent(InputEvent event)181     private void injectInputEvent(InputEvent event) {
182         mWaitForReceiveInput = new CountDownLatch(1);
183         mInstrumentation.runOnMainSync(() -> mWindow.injectInputEvent(event));
184         try {
185             mWaitForReceiveInput.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
186         } catch (InterruptedException e) {
187             throw new RuntimeException(e);
188         }
189     }
190 
191     @Test
testKeyEvent()192     public void testKeyEvent() {
193         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
194         while (state.keepRunning()) {
195             final KeyEvent eventDown =
196                     new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACKSLASH);
197             injectInputEvent(eventDown);
198 
199             state.pauseTiming();
200             final KeyEvent eventUp =
201                     new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACKSLASH);
202             injectInputEvent(eventUp);
203             state.resumeTiming();
204         }
205     }
206 
207     @Test
testMotionEvent()208     public void testMotionEvent() {
209         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
210         final Rect contentFrame = new Rect();
211         mInstrumentation.runOnMainSync(() ->
212                 mWindow.getDecorView().getBoundsOnScreen(contentFrame));
213         final int x = contentFrame.centerX();
214         final int y = contentFrame.centerY();
215         final long eventTime = SystemClock.uptimeMillis();
216 
217         while (state.keepRunning()) {
218             final MotionEvent eventDown = MotionEvent.obtain(eventTime, eventTime,
219                     MotionEvent.ACTION_DOWN, x, y,
220                     1.0f /* pressure */, 1.0f /* size */, 0 /* metaState */,
221                     1.0f /* xPrecision */, 1.0f /* yPrecision */,
222                     0 /* deviceId */, 0 /* edgeFlags */,
223                     InputDevice.SOURCE_TOUCHSCREEN, DEFAULT_DISPLAY);
224             injectInputEvent(eventDown);
225 
226             state.pauseTiming();
227             final MotionEvent eventUp = MotionEvent.obtain(eventTime, eventTime,
228                     MotionEvent.ACTION_UP, x, y,
229                     1.0f /* pressure */, 1.0f /* size */, 0 /* metaState */,
230                     1.0f /* xPrecision */, 1.0f /* yPrecision */,
231                     0 /* deviceId */, 0 /* edgeFlags */,
232                     InputDevice.SOURCE_TOUCHSCREEN, DEFAULT_DISPLAY);
233             injectInputEvent(eventUp);
234             state.resumeTiming();
235         }
236     }
237 }
238