1 /* 2 * Copyright (C) 2021 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.server.policy; 18 19 import static android.view.KeyEvent.ACTION_DOWN; 20 import static android.view.KeyEvent.ACTION_UP; 21 import static android.view.KeyEvent.KEYCODE_BACK; 22 import static android.view.KeyEvent.KEYCODE_POWER; 23 24 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 25 26 import static org.junit.Assert.assertEquals; 27 import static org.junit.Assert.assertFalse; 28 import static org.junit.Assert.assertNotNull; 29 import static org.junit.Assert.assertTrue; 30 31 import android.app.Instrumentation; 32 import android.content.Context; 33 import android.os.Handler; 34 import android.os.HandlerThread; 35 import android.os.Looper; 36 import android.os.Process; 37 import android.os.SystemClock; 38 import android.view.KeyEvent; 39 40 import org.junit.Before; 41 import org.junit.Test; 42 43 import java.util.concurrent.BlockingQueue; 44 import java.util.concurrent.CountDownLatch; 45 import java.util.concurrent.LinkedBlockingQueue; 46 import java.util.concurrent.TimeUnit; 47 48 /** 49 * Test class for {@link SingleKeyGestureDetector}. 50 * 51 * Build/Install/Run: 52 * atest WmTests:SingleKeyGestureTests 53 */ 54 public class SingleKeyGestureTests { 55 private SingleKeyGestureDetector mDetector; 56 57 private int mMaxMultiPressCount = 3; 58 private int mExpectedMultiPressCount = 2; 59 60 private CountDownLatch mShortPressed = new CountDownLatch(1); 61 private CountDownLatch mLongPressed = new CountDownLatch(1); 62 private CountDownLatch mVeryLongPressed = new CountDownLatch(1); 63 private CountDownLatch mMultiPressed = new CountDownLatch(1); 64 private BlockingQueue<KeyUpData> mKeyUpQueue = new LinkedBlockingQueue<>(); 65 66 private final Instrumentation mInstrumentation = getInstrumentation(); 67 private final Context mContext = mInstrumentation.getTargetContext(); 68 private long mWaitTimeout; 69 private long mLongPressTime; 70 private long mVeryLongPressTime; 71 72 // Allow press from non interactive mode. 73 private boolean mAllowNonInteractiveForPress = true; 74 private boolean mAllowNonInteractiveForLongPress = true; 75 76 private boolean mLongPressOnPowerBehavior = true; 77 private boolean mVeryLongPressOnPowerBehavior = true; 78 private boolean mLongPressOnBackBehavior = false; 79 80 @Before setUp()81 public void setUp() { 82 mInstrumentation.runOnMainSync( 83 () -> { 84 mDetector = SingleKeyGestureDetector.get(mContext, Looper.myLooper()); 85 initSingleKeyGestureRules(); 86 }); 87 88 mWaitTimeout = SingleKeyGestureDetector.MULTI_PRESS_TIMEOUT + 50; 89 mLongPressTime = SingleKeyGestureDetector.sDefaultLongPressTimeout + 50; 90 mVeryLongPressTime = SingleKeyGestureDetector.sDefaultVeryLongPressTimeout + 50; 91 } 92 initSingleKeyGestureRules()93 private void initSingleKeyGestureRules() { 94 mDetector.addRule( 95 new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER) { 96 @Override 97 boolean supportLongPress() { 98 return mLongPressOnPowerBehavior; 99 } 100 101 @Override 102 boolean supportVeryLongPress() { 103 return mVeryLongPressOnPowerBehavior; 104 } 105 106 @Override 107 int getMaxMultiPressCount() { 108 return mMaxMultiPressCount; 109 } 110 111 @Override 112 public void onPress(long downTime, int displayId) { 113 if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { 114 return; 115 } 116 mShortPressed.countDown(); 117 } 118 119 @Override 120 void onLongPress(long downTime) { 121 if (mDetector.beganFromNonInteractive() 122 && !mAllowNonInteractiveForLongPress) { 123 return; 124 } 125 mLongPressed.countDown(); 126 } 127 128 @Override 129 void onVeryLongPress(long downTime) { 130 mVeryLongPressed.countDown(); 131 } 132 133 @Override 134 void onMultiPress(long downTime, int count, int displayId) { 135 if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { 136 return; 137 } 138 mMultiPressed.countDown(); 139 assertTrue(mMaxMultiPressCount >= count); 140 assertEquals(mExpectedMultiPressCount, count); 141 } 142 143 @Override 144 void onKeyUp(long eventTime, int multiPressCount, int displayId) { 145 mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount)); 146 } 147 }); 148 149 mDetector.addRule( 150 new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_BACK) { 151 @Override 152 boolean supportLongPress() { 153 return mLongPressOnBackBehavior; 154 } 155 156 @Override 157 int getMaxMultiPressCount() { 158 return mMaxMultiPressCount; 159 } 160 161 @Override 162 public void onPress(long downTime, int displayId) { 163 if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { 164 return; 165 } 166 mShortPressed.countDown(); 167 } 168 169 @Override 170 void onMultiPress(long downTime, int count, int displayId) { 171 if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { 172 return; 173 } 174 mMultiPressed.countDown(); 175 assertTrue(mMaxMultiPressCount >= count); 176 assertEquals(mExpectedMultiPressCount, count); 177 } 178 179 @Override 180 void onKeyUp(long eventTime, int multiPressCount, int displayId) { 181 mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount)); 182 } 183 184 @Override 185 void onLongPress(long downTime) { 186 mLongPressed.countDown(); 187 } 188 }); 189 } 190 191 private static class KeyUpData { 192 public final int keyCode; 193 public final int pressCount; 194 KeyUpData(int keyCode, int pressCount)195 KeyUpData(int keyCode, int pressCount) { 196 this.keyCode = keyCode; 197 this.pressCount = pressCount; 198 } 199 } 200 pressKey(int keyCode, long pressTime)201 private void pressKey(int keyCode, long pressTime) { 202 pressKey(keyCode, pressTime, true /* interactive */); 203 } 204 pressKey(int keyCode, long pressTime, boolean interactive)205 private void pressKey(int keyCode, long pressTime, boolean interactive) { 206 pressKey(keyCode, pressTime, interactive, false /* defaultDisplayOn */); 207 } 208 pressKey( int keyCode, long pressTime, boolean interactive, boolean defaultDisplayOn)209 private void pressKey( 210 int keyCode, long pressTime, boolean interactive, boolean defaultDisplayOn) { 211 long eventTime = SystemClock.uptimeMillis(); 212 final KeyEvent keyDown = 213 new KeyEvent( 214 eventTime, 215 eventTime, 216 ACTION_DOWN, 217 keyCode, 218 0 /* repeat */, 219 0 /* metaState */); 220 mDetector.interceptKey(keyDown, interactive, defaultDisplayOn); 221 222 // keep press down. 223 try { 224 Thread.sleep(pressTime); 225 } catch (InterruptedException e) { 226 e.printStackTrace(); 227 } 228 229 eventTime += pressTime; 230 final KeyEvent keyUp = 231 new KeyEvent( 232 eventTime, 233 eventTime, 234 ACTION_UP, 235 keyCode, 236 0 /* repeat */, 237 0 /* metaState */); 238 239 mDetector.interceptKey(keyUp, interactive, defaultDisplayOn); 240 } 241 242 @Test testShortPress()243 public void testShortPress() throws InterruptedException { 244 pressKey(KEYCODE_POWER, 0 /* pressTime */); 245 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 246 } 247 248 @Test testLongPress()249 public void testLongPress() throws InterruptedException { 250 pressKey(KEYCODE_POWER, mLongPressTime); 251 assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 252 } 253 254 @Test testVeryLongPress()255 public void testVeryLongPress() throws InterruptedException { 256 pressKey(KEYCODE_POWER, mVeryLongPressTime); 257 assertTrue(mVeryLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 258 } 259 260 @Test testMultiPress()261 public void testMultiPress() throws InterruptedException { 262 // Double presses. 263 mExpectedMultiPressCount = 2; 264 pressKey(KEYCODE_POWER, 0 /* pressTime */); 265 pressKey(KEYCODE_POWER, 0 /* pressTime */); 266 assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 267 268 // Triple presses. 269 mExpectedMultiPressCount = 3; 270 mMultiPressed = new CountDownLatch(1); 271 pressKey(KEYCODE_POWER, 0 /* pressTime */); 272 pressKey(KEYCODE_POWER, 0 /* pressTime */); 273 pressKey(KEYCODE_POWER, 0 /* pressTime */); 274 assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 275 } 276 277 @Test testOnKeyUp()278 public void testOnKeyUp() throws InterruptedException { 279 pressKey(KEYCODE_POWER, 0 /* pressTime */); 280 281 verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */); 282 } 283 verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount)284 private void verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount) 285 throws InterruptedException { 286 KeyUpData keyUpData = mKeyUpQueue.poll(mWaitTimeout, TimeUnit.MILLISECONDS); 287 assertNotNull(keyUpData); 288 assertEquals(expectedKeyCode, keyUpData.keyCode); 289 assertEquals(expectedMultiPressCount, keyUpData.pressCount); 290 } 291 292 @Test testNonInteractive()293 public void testNonInteractive() throws InterruptedException { 294 // Disallow short press behavior from non interactive. 295 mAllowNonInteractiveForPress = false; 296 pressKey(KEYCODE_POWER, 0 /* pressTime */, false /* interactive */); 297 assertFalse(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 298 299 // Allow long press behavior from non interactive. 300 pressKey(KEYCODE_POWER, mLongPressTime, false /* interactive */); 301 assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 302 } 303 304 @Test testShortPress_Pressure()305 public void testShortPress_Pressure() throws InterruptedException { 306 final HandlerThread handlerThread = 307 new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY); 308 handlerThread.start(); 309 Handler newHandler = new Handler(handlerThread.getLooper()); 310 mMaxMultiPressCount = 1; // Will trigger short press when event up. 311 try { 312 // To make sure we won't get any crash while panic pressing keys. 313 for (int i = 0; i < 100; i++) { 314 mShortPressed = new CountDownLatch(2); 315 newHandler.runWithScissors( 316 () -> { 317 pressKey(KEYCODE_POWER, 0 /* pressTime */); 318 pressKey(KEYCODE_BACK, 0 /* pressTime */); 319 }, 320 mWaitTimeout); 321 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 322 } 323 } finally { 324 handlerThread.quitSafely(); 325 } 326 } 327 328 @Test testMultiPress_Pressure()329 public void testMultiPress_Pressure() throws InterruptedException { 330 final HandlerThread handlerThread = 331 new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY); 332 handlerThread.start(); 333 Handler newHandler = new Handler(handlerThread.getLooper()); 334 try { 335 // To make sure we won't get any unexpected multi-press count. 336 for (int i = 0; i < 5; i++) { 337 mMultiPressed = new CountDownLatch(1); 338 mShortPressed = new CountDownLatch(1); 339 newHandler.runWithScissors( 340 () -> { 341 pressKey(KEYCODE_POWER, 0 /* pressTime */); 342 pressKey(KEYCODE_POWER, 0 /* pressTime */); 343 }, 344 mWaitTimeout); 345 assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 346 347 newHandler.runWithScissors( 348 () -> pressKey(KEYCODE_POWER, 0 /* pressTime */), mWaitTimeout); 349 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 350 } 351 } finally { 352 handlerThread.quitSafely(); 353 } 354 } 355 356 @Test testOnKeyUp_Pressure()357 public void testOnKeyUp_Pressure() throws InterruptedException { 358 final HandlerThread handlerThread = 359 new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY); 360 handlerThread.start(); 361 Handler newHandler = new Handler(handlerThread.getLooper()); 362 try { 363 // To make sure we won't get any unexpected multi-press count. 364 for (int i = 0; i < 5; i++) { 365 newHandler.runWithScissors( 366 () -> { 367 pressKey(KEYCODE_POWER, 0 /* pressTime */); 368 pressKey(KEYCODE_POWER, 0 /* pressTime */); 369 }, 370 mWaitTimeout); 371 newHandler.runWithScissors( 372 () -> pressKey(KEYCODE_BACK, 0 /* pressTime */), mWaitTimeout); 373 374 verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */); 375 verifyKeyUpData(KEYCODE_POWER, 2 /* expectedMultiPressCount */); 376 verifyKeyUpData(KEYCODE_BACK, 1 /* expectedMultiPressCount */); 377 } 378 } finally { 379 handlerThread.quitSafely(); 380 } 381 } 382 383 @Test testUpdateRule()384 public void testUpdateRule() throws InterruptedException { 385 // Power key rule doesn't allow the long press gesture. 386 mLongPressOnPowerBehavior = false; 387 pressKey(KEYCODE_POWER, mLongPressTime); 388 assertFalse(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 389 390 // Back key rule allows the long press gesture. 391 mLongPressOnBackBehavior = true; 392 pressKey(KEYCODE_BACK, mLongPressTime); 393 assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 394 } 395 396 @Test testAddRemove()397 public void testAddRemove() throws InterruptedException { 398 final SingleKeyGestureDetector.SingleKeyRule rule = 399 new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER) { 400 @Override 401 void onPress(long downTime, int displayId) { 402 mShortPressed.countDown(); 403 } 404 }; 405 406 mDetector.removeRule(rule); 407 pressKey(KEYCODE_POWER, 0 /* pressTime */); 408 assertFalse(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 409 410 mDetector.addRule(rule); 411 pressKey(KEYCODE_POWER, 0 /* pressTime */); 412 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 413 } 414 415 // Verify short press should not be triggered if no very long press behavior defined but the 416 // press time exceeded the very long press timeout. 417 @Test testTimeoutExceedVeryLongPress()418 public void testTimeoutExceedVeryLongPress() throws InterruptedException { 419 mVeryLongPressOnPowerBehavior = false; 420 421 pressKey(KEYCODE_POWER, mVeryLongPressTime + 50); 422 assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 423 assertEquals(mVeryLongPressed.getCount(), 1); 424 assertEquals(mShortPressed.getCount(), 1); 425 } 426 } 427