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