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 android.annotation.IntDef; 20 import android.content.Context; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.util.Log; 25 import android.view.KeyEvent; 26 import android.view.ViewConfiguration; 27 28 import java.io.PrintWriter; 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.util.ArrayList; 32 33 /** 34 * Detect single key gesture: press, long press, very long press and multi press. 35 * 36 * Call {@link #reset} if current {@link KeyEvent} has been handled by another policy 37 */ 38 39 public final class SingleKeyGestureDetector { 40 private static final String TAG = "SingleKeyGesture"; 41 private static final boolean DEBUG = PhoneWindowManager.DEBUG_INPUT; 42 43 private static final int MSG_KEY_LONG_PRESS = 0; 44 private static final int MSG_KEY_VERY_LONG_PRESS = 1; 45 private static final int MSG_KEY_DELAYED_PRESS = 2; 46 47 private volatile int mKeyPressCounter; 48 private boolean mBeganFromNonInteractive = false; 49 50 private final ArrayList<SingleKeyRule> mRules = new ArrayList(); 51 private SingleKeyRule mActiveRule = null; 52 53 // Key code of current key down event, reset when key up. 54 private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; 55 private volatile boolean mHandledByLongPress = false; 56 private final Handler mHandler; 57 private static final long MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout(); 58 59 /** Supported gesture flags */ 60 public static final int KEY_LONGPRESS = 1 << 1; 61 public static final int KEY_VERYLONGPRESS = 1 << 2; 62 63 /** @hide */ 64 @Retention(RetentionPolicy.SOURCE) 65 @IntDef(prefix = { "KEY_" }, value = { 66 KEY_LONGPRESS, 67 KEY_VERYLONGPRESS, 68 }) 69 public @interface KeyGestureFlag {} 70 71 /** 72 * Rule definition for single keys gesture. 73 * E.g : define power key. 74 * <pre class="prettyprint"> 75 * SingleKeyRule rule = 76 * new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) { 77 * int getMaxMultiPressCount() { // maximum multi press count. } 78 * void onPress(long downTime) { // short press behavior. } 79 * void onLongPress(long eventTime) { // long press behavior. } 80 * void onVeryLongPress(long eventTime) { // very long press behavior. } 81 * void onMultiPress(long downTime, int count) { // multi press behavior. } 82 * }; 83 * </pre> 84 */ 85 abstract static class SingleKeyRule { 86 87 private final int mKeyCode; 88 private final int mSupportedGestures; 89 private final long mDefaultLongPressTimeout; 90 private final long mDefaultVeryLongPressTimeout; 91 SingleKeyRule(Context context, int keyCode, @KeyGestureFlag int supportedGestures)92 SingleKeyRule(Context context, int keyCode, @KeyGestureFlag int supportedGestures) { 93 mKeyCode = keyCode; 94 mSupportedGestures = supportedGestures; 95 mDefaultLongPressTimeout = 96 ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout(); 97 mDefaultVeryLongPressTimeout = context.getResources().getInteger( 98 com.android.internal.R.integer.config_veryLongPressTimeout); 99 } 100 101 /** 102 * True if the rule could intercept the key. 103 */ shouldInterceptKey(int keyCode)104 private boolean shouldInterceptKey(int keyCode) { 105 return keyCode == mKeyCode; 106 } 107 108 /** 109 * True if the rule support long press. 110 */ supportLongPress()111 private boolean supportLongPress() { 112 return (mSupportedGestures & KEY_LONGPRESS) != 0; 113 } 114 115 /** 116 * True if the rule support very long press. 117 */ supportVeryLongPress()118 private boolean supportVeryLongPress() { 119 return (mSupportedGestures & KEY_VERYLONGPRESS) != 0; 120 } 121 122 /** 123 * Maximum count of multi presses. 124 * Return 1 will trigger onPress immediately when {@link KeyEvent.ACTION_UP}. 125 * Otherwise trigger onMultiPress immediately when reach max count when 126 * {@link KeyEvent.ACTION_DOWN}. 127 */ getMaxMultiPressCount()128 int getMaxMultiPressCount() { 129 return 1; 130 } 131 132 /** 133 * Called when short press has been detected. 134 */ onPress(long downTime)135 abstract void onPress(long downTime); 136 /** 137 * Callback when multi press (>= 2) has been detected. 138 */ onMultiPress(long downTime, int count)139 void onMultiPress(long downTime, int count) {} 140 /** 141 * Returns the timeout in milliseconds for a long press. 142 * 143 * If multipress is also supported, this should always be greater than the multipress 144 * timeout. If very long press is supported, this should always be less than the very long 145 * press timeout. 146 */ getLongPressTimeoutMs()147 long getLongPressTimeoutMs() { 148 return mDefaultLongPressTimeout; 149 } 150 /** 151 * Callback when long press has been detected. 152 */ onLongPress(long eventTime)153 void onLongPress(long eventTime) {} 154 /** 155 * Returns the timeout in milliseconds for a very long press. 156 * 157 * If long press is supported, this should always be longer than the long press timeout. 158 */ getVeryLongPressTimeoutMs()159 long getVeryLongPressTimeoutMs() { 160 return mDefaultVeryLongPressTimeout; 161 } 162 /** 163 * Callback when very long press has been detected. 164 */ onVeryLongPress(long eventTime)165 void onVeryLongPress(long eventTime) {} 166 167 @Override toString()168 public String toString() { 169 return "KeyCode=" + KeyEvent.keyCodeToString(mKeyCode) 170 + ", LongPress=" + supportLongPress() 171 + ", VeryLongPress=" + supportVeryLongPress() 172 + ", MaxMultiPressCount=" + getMaxMultiPressCount(); 173 } 174 } 175 SingleKeyGestureDetector()176 public SingleKeyGestureDetector() { 177 mHandler = new KeyHandler(); 178 } 179 addRule(SingleKeyRule rule)180 void addRule(SingleKeyRule rule) { 181 mRules.add(rule); 182 } 183 interceptKey(KeyEvent event, boolean interactive)184 void interceptKey(KeyEvent event, boolean interactive) { 185 if (event.getAction() == KeyEvent.ACTION_DOWN) { 186 // Store the non interactive state when first down. 187 if (mDownKeyCode == KeyEvent.KEYCODE_UNKNOWN || mDownKeyCode != event.getKeyCode()) { 188 mBeganFromNonInteractive = !interactive; 189 } 190 interceptKeyDown(event); 191 } else { 192 interceptKeyUp(event); 193 } 194 } 195 interceptKeyDown(KeyEvent event)196 private void interceptKeyDown(KeyEvent event) { 197 final int keyCode = event.getKeyCode(); 198 // same key down. 199 if (mDownKeyCode == keyCode) { 200 if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0 201 && mActiveRule.supportLongPress() && !mHandledByLongPress) { 202 if (DEBUG) { 203 Log.i(TAG, "Long press key " + KeyEvent.keyCodeToString(keyCode)); 204 } 205 mHandledByLongPress = true; 206 mHandler.removeMessages(MSG_KEY_LONG_PRESS); 207 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); 208 mActiveRule.onLongPress(event.getEventTime()); 209 } 210 return; 211 } 212 213 // When a different key is pressed, stop processing gestures for the currently active key. 214 if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN 215 || (mActiveRule != null && !mActiveRule.shouldInterceptKey(keyCode))) { 216 if (DEBUG) { 217 Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode)); 218 } 219 reset(); 220 } 221 mDownKeyCode = keyCode; 222 223 // Picks a new rule, return if no rule picked. 224 if (mActiveRule == null) { 225 final int count = mRules.size(); 226 for (int index = 0; index < count; index++) { 227 final SingleKeyRule rule = mRules.get(index); 228 if (rule.shouldInterceptKey(keyCode)) { 229 if (DEBUG) { 230 Log.i(TAG, "Intercept key by rule " + rule); 231 } 232 mActiveRule = rule; 233 break; 234 } 235 } 236 } 237 if (mActiveRule == null) { 238 return; 239 } 240 241 final long eventTime = event.getEventTime(); 242 if (mKeyPressCounter == 0) { 243 if (mActiveRule.supportLongPress()) { 244 final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0, 245 eventTime); 246 msg.setAsynchronous(true); 247 mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs()); 248 } 249 250 if (mActiveRule.supportVeryLongPress()) { 251 final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0, 252 eventTime); 253 msg.setAsynchronous(true); 254 mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs()); 255 } 256 } else { 257 mHandler.removeMessages(MSG_KEY_LONG_PRESS); 258 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); 259 mHandler.removeMessages(MSG_KEY_DELAYED_PRESS); 260 261 // Trigger multi press immediately when reach max count.( > 1) 262 if (mKeyPressCounter == mActiveRule.getMaxMultiPressCount() - 1) { 263 if (DEBUG) { 264 Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it" 265 + " reach the max count " + mKeyPressCounter); 266 } 267 mActiveRule.onMultiPress(eventTime, mKeyPressCounter + 1); 268 mKeyPressCounter = 0; 269 } 270 } 271 } 272 interceptKeyUp(KeyEvent event)273 private boolean interceptKeyUp(KeyEvent event) { 274 mHandler.removeMessages(MSG_KEY_LONG_PRESS); 275 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); 276 mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; 277 if (mActiveRule == null) { 278 return false; 279 } 280 281 if (mHandledByLongPress) { 282 mHandledByLongPress = false; 283 mKeyPressCounter = 0; 284 return true; 285 } 286 287 final long downTime = event.getDownTime(); 288 if (event.getKeyCode() == mActiveRule.mKeyCode) { 289 // Directly trigger short press when max count is 1. 290 if (mActiveRule.getMaxMultiPressCount() == 1) { 291 if (DEBUG) { 292 Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode())); 293 } 294 Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode, 295 1, downTime); 296 msg.setAsynchronous(true); 297 mHandler.sendMessage(msg); 298 return true; 299 } 300 301 // This could be a multi-press. Wait a little bit longer to confirm. 302 mKeyPressCounter++; 303 Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode, 304 mKeyPressCounter, downTime); 305 msg.setAsynchronous(true); 306 mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT); 307 return true; 308 } 309 reset(); 310 return false; 311 } 312 getKeyPressCounter(int keyCode)313 int getKeyPressCounter(int keyCode) { 314 if (mActiveRule != null && mActiveRule.mKeyCode == keyCode) { 315 return mKeyPressCounter; 316 } else { 317 return 0; 318 } 319 } 320 reset()321 void reset() { 322 if (mActiveRule != null) { 323 if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN) { 324 mHandler.removeMessages(MSG_KEY_LONG_PRESS); 325 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); 326 } 327 328 if (mKeyPressCounter > 0) { 329 mHandler.removeMessages(MSG_KEY_DELAYED_PRESS); 330 mKeyPressCounter = 0; 331 } 332 mActiveRule = null; 333 } 334 335 mHandledByLongPress = false; 336 mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; 337 } 338 isKeyIntercepted(int keyCode)339 boolean isKeyIntercepted(int keyCode) { 340 return mActiveRule != null && mActiveRule.shouldInterceptKey(keyCode); 341 } 342 beganFromNonInteractive()343 boolean beganFromNonInteractive() { 344 return mBeganFromNonInteractive; 345 } 346 dump(String prefix, PrintWriter pw)347 void dump(String prefix, PrintWriter pw) { 348 pw.println(prefix + "SingleKey rules:"); 349 for (SingleKeyRule rule : mRules) { 350 pw.println(prefix + " " + rule); 351 } 352 } 353 354 private class KeyHandler extends Handler { KeyHandler()355 KeyHandler() { 356 super(Looper.getMainLooper()); 357 } 358 359 @Override handleMessage(Message msg)360 public void handleMessage(Message msg) { 361 if (mActiveRule == null) { 362 return; 363 } 364 final int keyCode = msg.arg1; 365 final long eventTime = (long) msg.obj; 366 switch(msg.what) { 367 case MSG_KEY_LONG_PRESS: 368 if (DEBUG) { 369 Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode)); 370 } 371 mHandledByLongPress = true; 372 mActiveRule.onLongPress(eventTime); 373 break; 374 case MSG_KEY_VERY_LONG_PRESS: 375 if (DEBUG) { 376 Log.i(TAG, "Detect very long press " 377 + KeyEvent.keyCodeToString(keyCode)); 378 } 379 mHandledByLongPress = true; 380 mActiveRule.onVeryLongPress(eventTime); 381 break; 382 case MSG_KEY_DELAYED_PRESS: 383 if (DEBUG) { 384 Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode) 385 + ", count " + mKeyPressCounter); 386 } 387 if (mKeyPressCounter == 1) { 388 mActiveRule.onPress(eventTime); 389 } else { 390 mActiveRule.onMultiPress(eventTime, mKeyPressCounter); 391 } 392 reset(); 393 break; 394 } 395 } 396 } 397 } 398