1 /*
2  * Copyright (C) 2020 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 import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN;
24 import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
25 
26 import static org.testng.Assert.assertFalse;
27 import static org.testng.Assert.assertTrue;
28 
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.SystemClock;
32 import android.view.KeyEvent;
33 
34 import androidx.test.filters.SmallTest;
35 
36 import org.junit.Before;
37 import org.junit.Test;
38 
39 import java.util.concurrent.CountDownLatch;
40 import java.util.concurrent.TimeUnit;
41 
42 /**
43  * Test class for {@link KeyCombinationManager}.
44  *
45  * Build/Install/Run:
46  *  atest KeyCombinationManagerTests
47  */
48 
49 @SmallTest
50 public class KeyCombinationManagerTests {
51     private KeyCombinationManager mKeyCombinationManager;
52 
53     private final CountDownLatch mAction1Triggered = new CountDownLatch(1);
54     private final CountDownLatch mAction2Triggered = new CountDownLatch(1);
55     private final CountDownLatch mAction3Triggered = new CountDownLatch(1);
56 
57     private boolean mPreCondition = true;
58     private static final long SCHEDULE_TIME = 300;
59     private Handler mHandler;
60 
61     @Before
setUp()62     public void setUp() {
63         mHandler = new Handler(Looper.getMainLooper());
64         mKeyCombinationManager = new KeyCombinationManager(mHandler);
65         initKeyCombinationRules();
66     }
67 
initKeyCombinationRules()68     private void initKeyCombinationRules() {
69         // Rule 1 : power + volume_down trigger action immediately.
70         mKeyCombinationManager.addRule(
71                 new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN,
72                         KEYCODE_POWER) {
73                     @Override
74                     void execute() {
75                         mAction1Triggered.countDown();
76                     }
77 
78                     @Override
79                     void cancel() {
80                     }
81                 });
82 
83         // Rule 2 : volume_up + volume_down with condition.
84         mKeyCombinationManager.addRule(
85                 new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN,
86                         KEYCODE_VOLUME_UP) {
87                     @Override
88                     boolean preCondition() {
89                         return mPreCondition;
90                     }
91 
92                     @Override
93                     void execute() {
94                         mAction2Triggered.countDown();
95                     }
96 
97                     @Override
98                     void cancel() {
99                     }
100 
101                     @Override
102                     long getKeyInterceptDelayMs() {
103                         return 0;
104                     }
105                 });
106 
107         // Rule 3 : power + volume_up schedule and trigger action after timeout.
108         mKeyCombinationManager.addRule(
109                 new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_UP, KEYCODE_POWER) {
110                     final Runnable mAction = new Runnable() {
111                         @Override
112                         public void run() {
113                             mAction3Triggered.countDown();
114                         }
115                     };
116 
117                     @Override
118                     void execute() {
119                         mHandler.postDelayed(mAction, SCHEDULE_TIME);
120                     }
121 
122                     @Override
123                     void cancel() {
124                         mHandler.removeCallbacks(mAction);
125                     }
126                 });
127     }
128 
pressKeys(long firstKeyTime, int firstKeyCode, long secondKeyTime, int secondKeyCode)129     private void pressKeys(long firstKeyTime, int firstKeyCode, long secondKeyTime,
130             int secondKeyCode) {
131         pressKeys(firstKeyTime, firstKeyCode, secondKeyTime, secondKeyCode, 0);
132     }
133 
pressKeys(long firstKeyTime, int firstKeyCode, long secondKeyTime, int secondKeyCode, long pressTime)134     private void pressKeys(long firstKeyTime, int firstKeyCode, long secondKeyTime,
135             int secondKeyCode, long pressTime) {
136         final KeyEvent firstKeyDown = new KeyEvent(firstKeyTime, firstKeyTime, ACTION_DOWN,
137                 firstKeyCode, 0 /* repeat */, 0 /* metaState */);
138         final KeyEvent secondKeyDown = new KeyEvent(secondKeyTime, secondKeyTime, ACTION_DOWN,
139                 secondKeyCode, 0 /* repeat */, 0 /* metaState */);
140 
141         mKeyCombinationManager.interceptKey(firstKeyDown, true);
142         mKeyCombinationManager.interceptKey(secondKeyDown, true);
143 
144         // keep press down.
145         try {
146             Thread.sleep(pressTime);
147         } catch (InterruptedException e) {
148             e.printStackTrace();
149         }
150 
151         final KeyEvent firstKeyUp = new KeyEvent(firstKeyTime, firstKeyTime, ACTION_UP,
152                 firstKeyCode, 0 /* repeat */, 0 /* metaState */);
153         final KeyEvent secondKeyUp = new KeyEvent(secondKeyTime, secondKeyTime, ACTION_UP,
154                 secondKeyCode, 0 /* repeat */, 0 /* metaState */);
155 
156         mKeyCombinationManager.interceptKey(firstKeyUp, true);
157         mKeyCombinationManager.interceptKey(secondKeyUp, true);
158     }
159 
160     @Test
testTriggerRule()161     public void testTriggerRule() throws InterruptedException {
162         final long eventTime = SystemClock.uptimeMillis();
163         pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_DOWN);
164         assertTrue(mAction1Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
165 
166         pressKeys(eventTime, KEYCODE_VOLUME_UP, eventTime, KEYCODE_VOLUME_DOWN);
167         assertTrue(mAction2Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
168 
169         pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_UP, SCHEDULE_TIME + 50);
170         assertTrue(mAction3Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
171     }
172 
173     /**
174      *  Nothing should happen if there is no definition.
175      */
176     @Test
testNotTrigger_NoRule()177     public void testNotTrigger_NoRule() throws InterruptedException {
178         final long eventTime = SystemClock.uptimeMillis();
179         pressKeys(eventTime, KEYCODE_BACK, eventTime, KEYCODE_VOLUME_DOWN);
180         assertFalse(mAction1Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
181         assertFalse(mAction2Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
182         assertFalse(mAction3Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
183     }
184 
185     /**
186      *  Nothing should happen if the interval of press time is too long.
187      */
188     @Test
testNotTrigger_Interval()189     public void testNotTrigger_Interval() throws InterruptedException {
190         final long eventTime = SystemClock.uptimeMillis();
191         final long earlyEventTime = eventTime - 200; // COMBINE_KEY_DELAY_MILLIS = 150;
192         pressKeys(earlyEventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_DOWN);
193         assertFalse(mAction1Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
194     }
195 
196     /**
197      *  Nothing should happen if the condition is false.
198      */
199     @Test
testNotTrigger_Condition()200     public void testNotTrigger_Condition() throws InterruptedException {
201         final long eventTime = SystemClock.uptimeMillis();
202         // we won't trigger action 2 because the condition is false.
203         mPreCondition = false;
204         pressKeys(eventTime, KEYCODE_VOLUME_UP, eventTime, KEYCODE_VOLUME_DOWN);
205         assertFalse(mAction2Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
206     }
207 
208     /**
209      *  Nothing should happen if the keys released too early.
210      */
211     @Test
testNotTrigger_EarlyRelease()212     public void testNotTrigger_EarlyRelease() throws InterruptedException {
213         final long eventTime = SystemClock.uptimeMillis();
214         pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_UP);
215         assertFalse(mAction3Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
216     }
217 
218     /**
219      *  The KeyInterceptTimeout should return the max timeout value.
220      */
221     @Test
testKeyInterceptTimeout()222     public void testKeyInterceptTimeout() {
223         final long eventTime = SystemClock.uptimeMillis();
224         final KeyEvent firstKeyDown = new KeyEvent(eventTime, eventTime, ACTION_DOWN,
225                 KEYCODE_VOLUME_UP, 0 /* repeat */, 0 /* metaState */);
226         // Press KEYCODE_VOLUME_UP would activate rule2 and rule3,
227         // and rule2's intercept delay is 0.
228         mKeyCombinationManager.interceptKey(firstKeyDown, true);
229         assertTrue(mKeyCombinationManager.getKeyInterceptTimeout(KEYCODE_VOLUME_UP) > eventTime);
230     }
231 
232     @Test
testAddRemove()233     public void testAddRemove() throws InterruptedException {
234         final KeyCombinationManager.TwoKeysCombinationRule rule =
235                 new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN,
236                         KEYCODE_POWER) {
237                     @Override
238                     void execute() {
239                         mAction1Triggered.countDown();
240                     }
241 
242                     @Override
243                     void cancel() {
244                     }
245                 };
246 
247         long eventTime = SystemClock.uptimeMillis();
248         mKeyCombinationManager.removeRule(rule);
249         pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_DOWN);
250         assertFalse(mAction1Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
251 
252         mKeyCombinationManager.addRule(rule);
253         eventTime = SystemClock.uptimeMillis();
254         pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_DOWN);
255         assertTrue(mAction1Triggered.await(SCHEDULE_TIME, TimeUnit.MILLISECONDS));
256     }
257 }