xref: /aosp_15_r20/frameworks/base/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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, int deviceId,
145                             int metaState) {
146                         mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount));
147                     }
148                 });
149 
150         mDetector.addRule(
151                 new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_BACK) {
152                     @Override
153                     boolean supportLongPress() {
154                         return mLongPressOnBackBehavior;
155                     }
156 
157                     @Override
158                     int getMaxMultiPressCount() {
159                         return mMaxMultiPressCount;
160                     }
161 
162                     @Override
163                     public void onPress(long downTime, int displayId) {
164                         if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
165                             return;
166                         }
167                         mShortPressed.countDown();
168                     }
169 
170                     @Override
171                     void onMultiPress(long downTime, int count, int displayId) {
172                         if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) {
173                             return;
174                         }
175                         mMultiPressed.countDown();
176                         assertTrue(mMaxMultiPressCount >= count);
177                         assertEquals(mExpectedMultiPressCount, count);
178                     }
179 
180                     @Override
181                     void onKeyUp(long eventTime, int multiPressCount, int displayId, int deviceId,
182                             int metaState) {
183                         mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount));
184                     }
185 
186                     @Override
187                     void onLongPress(long downTime) {
188                         mLongPressed.countDown();
189                     }
190                 });
191     }
192 
193     private static class KeyUpData {
194         public final int keyCode;
195         public final int pressCount;
196 
KeyUpData(int keyCode, int pressCount)197         KeyUpData(int keyCode, int pressCount) {
198             this.keyCode = keyCode;
199             this.pressCount = pressCount;
200         }
201     }
202 
pressKey(int keyCode, long pressTime)203     private void pressKey(int keyCode, long pressTime) {
204         pressKey(keyCode, pressTime, true /* interactive */);
205     }
206 
pressKey(int keyCode, long pressTime, boolean interactive)207     private void pressKey(int keyCode, long pressTime, boolean interactive) {
208         pressKey(keyCode, pressTime, interactive, false /* defaultDisplayOn */);
209     }
210 
pressKey( int keyCode, long pressTime, boolean interactive, boolean defaultDisplayOn)211     private void pressKey(
212             int keyCode, long pressTime, boolean interactive, boolean defaultDisplayOn) {
213         long eventTime = SystemClock.uptimeMillis();
214         final KeyEvent keyDown =
215                 new KeyEvent(
216                         eventTime,
217                         eventTime,
218                         ACTION_DOWN,
219                         keyCode,
220                         0 /* repeat */,
221                         0 /* metaState */);
222         mDetector.interceptKey(keyDown, interactive, defaultDisplayOn);
223 
224         // keep press down.
225         try {
226             Thread.sleep(pressTime);
227         } catch (InterruptedException e) {
228             e.printStackTrace();
229         }
230 
231         eventTime += pressTime;
232         final KeyEvent keyUp =
233                 new KeyEvent(
234                         eventTime,
235                         eventTime,
236                         ACTION_UP,
237                         keyCode,
238                         0 /* repeat */,
239                         0 /* metaState */);
240 
241         mDetector.interceptKey(keyUp, interactive, defaultDisplayOn);
242     }
243 
244     @Test
testShortPress()245     public void testShortPress() throws InterruptedException {
246         pressKey(KEYCODE_POWER, 0 /* pressTime */);
247         assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
248     }
249 
250     @Test
testLongPress()251     public void testLongPress() throws InterruptedException {
252         pressKey(KEYCODE_POWER, mLongPressTime);
253         assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
254     }
255 
256     @Test
testVeryLongPress()257     public void testVeryLongPress() throws InterruptedException {
258         pressKey(KEYCODE_POWER, mVeryLongPressTime);
259         assertTrue(mVeryLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
260     }
261 
262     @Test
testMultiPress()263     public void testMultiPress() throws InterruptedException {
264         // Double presses.
265         mExpectedMultiPressCount = 2;
266         pressKey(KEYCODE_POWER, 0 /* pressTime */);
267         pressKey(KEYCODE_POWER, 0 /* pressTime */);
268         assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
269 
270         // Triple presses.
271         mExpectedMultiPressCount = 3;
272         mMultiPressed = new CountDownLatch(1);
273         pressKey(KEYCODE_POWER, 0 /* pressTime */);
274         pressKey(KEYCODE_POWER, 0 /* pressTime */);
275         pressKey(KEYCODE_POWER, 0 /* pressTime */);
276         assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
277     }
278 
279     @Test
testOnKeyUp()280     public void testOnKeyUp() throws InterruptedException {
281         pressKey(KEYCODE_POWER, 0 /* pressTime */);
282 
283         verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
284     }
285 
verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount)286     private void verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount)
287             throws InterruptedException {
288         KeyUpData keyUpData = mKeyUpQueue.poll(mWaitTimeout, TimeUnit.MILLISECONDS);
289         assertNotNull(keyUpData);
290         assertEquals(expectedKeyCode, keyUpData.keyCode);
291         assertEquals(expectedMultiPressCount, keyUpData.pressCount);
292     }
293 
294     @Test
testNonInteractive()295     public void testNonInteractive() throws InterruptedException {
296         // Disallow short press behavior from non interactive.
297         mAllowNonInteractiveForPress = false;
298         pressKey(KEYCODE_POWER, 0 /* pressTime */, false /* interactive */);
299         assertFalse(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
300 
301         // Allow long press behavior from non interactive.
302         pressKey(KEYCODE_POWER, mLongPressTime, false /* interactive */);
303         assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
304     }
305 
306     @Test
testShortPress_Pressure()307     public void testShortPress_Pressure() throws InterruptedException {
308         final HandlerThread handlerThread =
309                 new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY);
310         handlerThread.start();
311         Handler newHandler = new Handler(handlerThread.getLooper());
312         mMaxMultiPressCount = 1; // Will trigger short press when event up.
313         try {
314             // To make sure we won't get any crash while panic pressing keys.
315             for (int i = 0; i < 100; i++) {
316                 mShortPressed = new CountDownLatch(2);
317                 newHandler.runWithScissors(
318                         () -> {
319                             pressKey(KEYCODE_POWER, 0 /* pressTime */);
320                             pressKey(KEYCODE_BACK, 0 /* pressTime */);
321                         },
322                         mWaitTimeout);
323                 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
324             }
325         } finally {
326             handlerThread.quitSafely();
327         }
328     }
329 
330     @Test
testMultiPress_Pressure()331     public void testMultiPress_Pressure() throws InterruptedException {
332         final HandlerThread handlerThread =
333                 new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY);
334         handlerThread.start();
335         Handler newHandler = new Handler(handlerThread.getLooper());
336         try {
337             // To make sure we won't get any unexpected multi-press count.
338             for (int i = 0; i < 5; i++) {
339                 mMultiPressed = new CountDownLatch(1);
340                 mShortPressed = new CountDownLatch(1);
341                 newHandler.runWithScissors(
342                         () -> {
343                             pressKey(KEYCODE_POWER, 0 /* pressTime */);
344                             pressKey(KEYCODE_POWER, 0 /* pressTime */);
345                         },
346                         mWaitTimeout);
347                 assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
348 
349                 newHandler.runWithScissors(
350                         () -> pressKey(KEYCODE_POWER, 0 /* pressTime */), mWaitTimeout);
351                 assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
352             }
353         } finally {
354             handlerThread.quitSafely();
355         }
356     }
357 
358     @Test
testOnKeyUp_Pressure()359     public void testOnKeyUp_Pressure() throws InterruptedException {
360         final HandlerThread handlerThread =
361                 new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY);
362         handlerThread.start();
363         Handler newHandler = new Handler(handlerThread.getLooper());
364         try {
365             // To make sure we won't get any unexpected multi-press count.
366             for (int i = 0; i < 5; i++) {
367                 newHandler.runWithScissors(
368                         () -> {
369                             pressKey(KEYCODE_POWER, 0 /* pressTime */);
370                             pressKey(KEYCODE_POWER, 0 /* pressTime */);
371                         },
372                         mWaitTimeout);
373                 newHandler.runWithScissors(
374                         () -> pressKey(KEYCODE_BACK, 0 /* pressTime */), mWaitTimeout);
375 
376                 verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
377                 verifyKeyUpData(KEYCODE_POWER, 2 /* expectedMultiPressCount */);
378                 verifyKeyUpData(KEYCODE_BACK, 1 /* expectedMultiPressCount */);
379             }
380         } finally {
381             handlerThread.quitSafely();
382         }
383     }
384 
385     @Test
testUpdateRule()386     public void testUpdateRule() throws InterruptedException {
387         // Power key rule doesn't allow the long press gesture.
388         mLongPressOnPowerBehavior = false;
389         pressKey(KEYCODE_POWER, mLongPressTime);
390         assertFalse(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
391 
392         // Back key rule allows the long press gesture.
393         mLongPressOnBackBehavior = true;
394         pressKey(KEYCODE_BACK, mLongPressTime);
395         assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
396     }
397 
398     @Test
testAddRemove()399     public void testAddRemove() throws InterruptedException {
400         final SingleKeyGestureDetector.SingleKeyRule rule =
401                 new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER) {
402                     @Override
403                     void onPress(long downTime, int displayId) {
404                         mShortPressed.countDown();
405                     }
406                 };
407 
408         mDetector.removeRule(rule);
409         pressKey(KEYCODE_POWER, 0 /* pressTime */);
410         assertFalse(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
411 
412         mDetector.addRule(rule);
413         pressKey(KEYCODE_POWER, 0 /* pressTime */);
414         assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
415     }
416 
417     // Verify short press should not be triggered if no very long press behavior defined but the
418     // press time exceeded the very long press timeout.
419     @Test
testTimeoutExceedVeryLongPress()420     public void testTimeoutExceedVeryLongPress() throws InterruptedException {
421         mVeryLongPressOnPowerBehavior = false;
422 
423         pressKey(KEYCODE_POWER, mVeryLongPressTime + 50);
424         assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
425         assertEquals(mVeryLongPressed.getCount(), 1);
426         assertEquals(mShortPressed.getCount(), 1);
427     }
428 }
429