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.assertTrue; 29 30 import android.app.Instrumentation; 31 import android.content.Context; 32 import android.os.Handler; 33 import android.os.HandlerThread; 34 import android.os.Process; 35 import android.os.SystemClock; 36 import android.view.KeyEvent; 37 38 import org.junit.Before; 39 import org.junit.Test; 40 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.TimeUnit; 43 44 /** 45 * Test class for {@link SingleKeyGestureDetector}. 46 * 47 * Build/Install/Run: 48 * atest WmTests:SingleKeyGestureTests 49 */ 50 public class SingleKeyGestureTests { 51 private SingleKeyGestureDetector mDetector; 52 53 private int mMaxMultiPressCount = 3; 54 private int mExpectedMultiPressCount = 2; 55 56 private CountDownLatch mShortPressed = new CountDownLatch(1); 57 private CountDownLatch mLongPressed = new CountDownLatch(1); 58 private CountDownLatch mVeryLongPressed = new CountDownLatch(1); 59 private CountDownLatch mMultiPressed = new CountDownLatch(1); 60 61 private final Instrumentation mInstrumentation = getInstrumentation(); 62 private final Context mContext = mInstrumentation.getTargetContext(); 63 private long mWaitTimeout; 64 private long mLongPressTime; 65 private long mVeryLongPressTime; 66 67 // Allow press from non interactive mode. 68 private boolean mAllowNonInteractiveForPress = true; 69 private boolean mAllowNonInteractiveForLongPress = true; 70 71 private boolean mLongPressOnPowerBehavior = true; 72 private boolean mVeryLongPressOnPowerBehavior = true; 73 private boolean mLongPressOnBackBehavior = false; 74 75 @Before setUp()76 public void setUp() { 77 mInstrumentation.runOnMainSync(() -> { 78 mDetector = SingleKeyGestureDetector.get(mContext); 79 initSingleKeyGestureRules(); 80 }); 81 82 mWaitTimeout = SingleKeyGestureDetector.MULTI_PRESS_TIMEOUT + 50; 83 mLongPressTime = SingleKeyGestureDetector.sDefaultLongPressTimeout + 50; 84 mVeryLongPressTime = SingleKeyGestureDetector.sDefaultVeryLongPressTimeout + 50; 85 } 86 initSingleKeyGestureRules()87 private void initSingleKeyGestureRules() { 88 mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER) { 89 @Override 90 boolean supportLongPress() { 91 return mLongPressOnPowerBehavior; 92 } 93 @Override 94 boolean supportVeryLongPress() { 95 return mVeryLongPressOnPowerBehavior; 96 } 97 @Override 98 int getMaxMultiPressCount() { 99 return mMaxMultiPressCount; 100 } 101 @Override 102 public void onPress(long downTime) { 103 if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { 104 return; 105 } 106 mShortPressed.countDown(); 107 } 108 109 @Override 110 void onLongPress(long downTime) { 111 if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForLongPress) { 112 return; 113 } 114 mLongPressed.countDown(); 115 } 116 117 @Override 118 void onVeryLongPress(long downTime) { 119 mVeryLongPressed.countDown(); 120 } 121 122 @Override 123 void onMultiPress(long downTime, int count) { 124 if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { 125 return; 126 } 127 mMultiPressed.countDown(); 128 assertTrue(mMaxMultiPressCount >= count); 129 assertEquals(mExpectedMultiPressCount, count); 130 } 131 }); 132 133 mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_BACK) { 134 @Override 135 boolean supportLongPress() { 136 return mLongPressOnBackBehavior; 137 } 138 @Override 139 int getMaxMultiPressCount() { 140 return mMaxMultiPressCount; 141 } 142 @Override 143 public void onPress(long downTime) { 144 if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { 145 return; 146 } 147 mShortPressed.countDown(); 148 } 149 150 @Override 151 void onMultiPress(long downTime, int count) { 152 if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { 153 return; 154 } 155 mMultiPressed.countDown(); 156 assertTrue(mMaxMultiPressCount >= count); 157 assertEquals(mExpectedMultiPressCount, count); 158 } 159 160 @Override 161 void onLongPress(long downTime) { 162 mLongPressed.countDown(); 163 } 164 }); 165 166 } 167 pressKey(int keyCode, long pressTime)168 private void pressKey(int keyCode, long pressTime) { 169 pressKey(keyCode, pressTime, true /* interactive */); 170 } 171 pressKey(int keyCode, long pressTime, boolean interactive)172 private void pressKey(int keyCode, long pressTime, boolean interactive) { 173 long eventTime = SystemClock.uptimeMillis(); 174 final KeyEvent keyDown = new KeyEvent(eventTime, eventTime, ACTION_DOWN, 175 keyCode, 0 /* repeat */, 0 /* metaState */); 176 mDetector.interceptKey(keyDown, interactive); 177 178 // keep press down. 179 try { 180 Thread.sleep(pressTime); 181 } catch (InterruptedException e) { 182 e.printStackTrace(); 183 } 184 185 eventTime += pressTime; 186 final KeyEvent keyUp = new KeyEvent(eventTime, eventTime, ACTION_UP, 187 keyCode, 0 /* repeat */, 0 /* metaState */); 188 189 mDetector.interceptKey(keyUp, interactive); 190 } 191 192 @Test testShortPress()193 public void testShortPress() throws InterruptedException { 194 pressKey(KEYCODE_POWER, 0 /* pressTime */); 195 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 196 } 197 198 @Test testLongPress()199 public void testLongPress() throws InterruptedException { 200 pressKey(KEYCODE_POWER, mLongPressTime); 201 assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 202 } 203 204 @Test testVeryLongPress()205 public void testVeryLongPress() throws InterruptedException { 206 pressKey(KEYCODE_POWER, mVeryLongPressTime); 207 assertTrue(mVeryLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 208 } 209 210 @Test testMultiPress()211 public void testMultiPress() throws InterruptedException { 212 // Double presses. 213 mExpectedMultiPressCount = 2; 214 pressKey(KEYCODE_POWER, 0 /* pressTime */); 215 pressKey(KEYCODE_POWER, 0 /* pressTime */); 216 assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 217 218 // Triple presses. 219 mExpectedMultiPressCount = 3; 220 mMultiPressed = new CountDownLatch(1); 221 pressKey(KEYCODE_POWER, 0 /* pressTime */); 222 pressKey(KEYCODE_POWER, 0 /* pressTime */); 223 pressKey(KEYCODE_POWER, 0 /* pressTime */); 224 assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 225 } 226 227 @Test testNonInteractive()228 public void testNonInteractive() throws InterruptedException { 229 // Disallow short press behavior from non interactive. 230 mAllowNonInteractiveForPress = false; 231 pressKey(KEYCODE_POWER, 0 /* pressTime */, false /* interactive */); 232 assertFalse(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 233 234 // Allow long press behavior from non interactive. 235 pressKey(KEYCODE_POWER, mLongPressTime, false /* interactive */); 236 assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 237 } 238 239 @Test testShortPress_Pressure()240 public void testShortPress_Pressure() throws InterruptedException { 241 final HandlerThread handlerThread = 242 new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY); 243 handlerThread.start(); 244 Handler newHandler = new Handler(handlerThread.getLooper()); 245 mMaxMultiPressCount = 1; // Will trigger short press when event up. 246 try { 247 // To make sure we won't get any crash while panic pressing keys. 248 for (int i = 0; i < 100; i++) { 249 mShortPressed = new CountDownLatch(2); 250 newHandler.runWithScissors(() -> { 251 pressKey(KEYCODE_POWER, 0 /* pressTime */); 252 pressKey(KEYCODE_BACK, 0 /* pressTime */); 253 }, mWaitTimeout); 254 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 255 } 256 } finally { 257 handlerThread.quitSafely(); 258 } 259 } 260 261 @Test testMultiPress_Pressure()262 public void testMultiPress_Pressure() throws InterruptedException { 263 final HandlerThread handlerThread = 264 new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY); 265 handlerThread.start(); 266 Handler newHandler = new Handler(handlerThread.getLooper()); 267 try { 268 // To make sure we won't get any unexpected multi-press count. 269 for (int i = 0; i < 5; i++) { 270 mMultiPressed = new CountDownLatch(1); 271 mShortPressed = new CountDownLatch(1); 272 newHandler.runWithScissors(() -> { 273 pressKey(KEYCODE_POWER, 0 /* pressTime */); 274 pressKey(KEYCODE_POWER, 0 /* pressTime */); 275 }, mWaitTimeout); 276 assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 277 278 newHandler.runWithScissors(() -> { 279 pressKey(KEYCODE_POWER, 0 /* pressTime */); 280 }, mWaitTimeout); 281 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 282 } 283 } finally { 284 handlerThread.quitSafely(); 285 } 286 } 287 288 @Test testUpdateRule()289 public void testUpdateRule() throws InterruptedException { 290 // Power key rule doesn't allow the long press gesture. 291 mLongPressOnPowerBehavior = false; 292 pressKey(KEYCODE_POWER, mLongPressTime); 293 assertFalse(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 294 295 // Back key rule allows the long press gesture. 296 mLongPressOnBackBehavior = true; 297 pressKey(KEYCODE_BACK, mLongPressTime); 298 assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 299 } 300 301 @Test testAddRemove()302 public void testAddRemove() throws InterruptedException { 303 final SingleKeyGestureDetector.SingleKeyRule rule = 304 new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER) { 305 @Override 306 void onPress(long downTime) { 307 mShortPressed.countDown(); 308 } 309 }; 310 311 mDetector.removeRule(rule); 312 pressKey(KEYCODE_POWER, 0 /* pressTime */); 313 assertFalse(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 314 315 mDetector.addRule(rule); 316 pressKey(KEYCODE_POWER, 0 /* pressTime */); 317 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 318 } 319 320 // Verify short press should not be triggered if no very long press behavior defined but the 321 // press time exceeded the very long press timeout. 322 @Test testTimeoutExceedVeryLongPress()323 public void testTimeoutExceedVeryLongPress() throws InterruptedException { 324 mVeryLongPressOnPowerBehavior = false; 325 326 pressKey(KEYCODE_POWER, mVeryLongPressTime + 50); 327 assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); 328 assertEquals(mVeryLongPressed.getCount(), 1); 329 assertEquals(mShortPressed.getCount(), 1); 330 } 331 } 332