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