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 }