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