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.content.Context; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.os.Message; 23 import android.util.Log; 24 import android.view.KeyEvent; 25 import android.view.ViewConfiguration; 26 27 import com.android.hardware.input.Flags; 28 29 import java.io.PrintWriter; 30 import java.util.ArrayList; 31 32 /** 33 * Detect single key gesture: press, long press, very long press and multi press. 34 * 35 * Call {@link #reset} if current {@link KeyEvent} has been handled by another policy 36 */ 37 38 public final class SingleKeyGestureDetector { 39 private static final String TAG = "SingleKeyGesture"; 40 private static final boolean DEBUG = PhoneWindowManager.DEBUG_INPUT; 41 42 private static final int MSG_KEY_LONG_PRESS = 0; 43 private static final int MSG_KEY_VERY_LONG_PRESS = 1; 44 private static final int MSG_KEY_DELAYED_PRESS = 2; 45 private static final int MSG_KEY_UP = 3; 46 47 private int mKeyPressCounter; 48 private boolean mBeganFromNonInteractive = false; 49 private boolean mBeganFromDefaultDisplayOn = false; 50 51 private final ArrayList<SingleKeyRule> mRules = new ArrayList(); 52 private SingleKeyRule mActiveRule = null; 53 54 // Key code of current key down event, reset when key up. 55 private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; 56 private boolean mHandledByLongPress = false; 57 private final Handler mHandler; 58 private long mLastDownTime = 0; 59 60 static final long MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout(); 61 static long sDefaultLongPressTimeout; 62 static long sDefaultVeryLongPressTimeout; 63 64 /** 65 * Rule definition for single keys gesture. 66 * E.g : define power key. 67 * <pre class="prettyprint"> 68 * SingleKeyRule rule = 69 * new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) { 70 * int getMaxMultiPressCount() { // maximum multi press count. } 71 * void onPress(long downTime, int displayId) { // short press behavior. } 72 * void onLongPress(long eventTime) { // long press behavior. } 73 * void onVeryLongPress(long eventTime) { // very long press behavior. } 74 * void onMultiPress(long downTime, int count, int displayId) { 75 * // multi press behavior. 76 * } 77 * }; 78 * </pre> 79 */ 80 abstract static class SingleKeyRule { 81 private final int mKeyCode; 82 SingleKeyRule(int keyCode)83 SingleKeyRule(int keyCode) { 84 mKeyCode = keyCode; 85 } 86 87 /** 88 * True if the rule could intercept the key. 89 */ shouldInterceptKey(int keyCode)90 private boolean shouldInterceptKey(int keyCode) { 91 return keyCode == mKeyCode; 92 } 93 94 /** 95 * True if the rule support long press. 96 */ supportLongPress()97 boolean supportLongPress() { 98 return false; 99 } 100 101 /** 102 * True if the rule support very long press. 103 */ supportVeryLongPress()104 boolean supportVeryLongPress() { 105 return false; 106 } 107 108 /** 109 * Maximum count of multi presses. 110 * Return 1 will trigger onPress immediately when {@link KeyEvent#ACTION_UP}. 111 * Otherwise trigger onMultiPress immediately when reach max count when 112 * {@link KeyEvent#ACTION_DOWN}. 113 */ getMaxMultiPressCount()114 int getMaxMultiPressCount() { 115 return 1; 116 } 117 118 /** 119 * Called when short press has been detected. 120 */ onPress(long downTime, int displayId)121 abstract void onPress(long downTime, int displayId); 122 /** 123 * Callback when multi press (>= 2) has been detected. 124 */ onMultiPress(long downTime, int count, int displayId)125 void onMultiPress(long downTime, int count, int displayId) {} 126 /** 127 * Returns the timeout in milliseconds for a long press. 128 * 129 * If multipress is also supported, this should always be greater than the multipress 130 * timeout. If very long press is supported, this should always be less than the very long 131 * press timeout. 132 */ getLongPressTimeoutMs()133 long getLongPressTimeoutMs() { 134 return sDefaultLongPressTimeout; 135 } 136 /** 137 * Callback when long press has been detected. 138 */ onLongPress(long eventTime)139 void onLongPress(long eventTime) {} 140 /** 141 * Returns the timeout in milliseconds for a very long press. 142 * 143 * If long press is supported, this should always be longer than the long press timeout. 144 */ getVeryLongPressTimeoutMs()145 long getVeryLongPressTimeoutMs() { 146 return sDefaultVeryLongPressTimeout; 147 } 148 /** 149 * Callback when very long press has been detected. 150 */ onVeryLongPress(long eventTime)151 void onVeryLongPress(long eventTime) {} 152 /** 153 * Callback executed upon each key up event that hasn't been processed by long press. 154 * 155 * @param eventTime the timestamp of this event 156 * @param pressCount the number of presses detected leading up to this key up event 157 * @param displayId the display ID of the event 158 * @param deviceId the ID of the input device that generated this event 159 * @param metaState the state of the modifiers when this gesture was detected 160 */ onKeyUp(long eventTime, int pressCount, int displayId, int deviceId, int metaState)161 void onKeyUp(long eventTime, int pressCount, int displayId, int deviceId, int metaState) {} 162 163 @Override toString()164 public String toString() { 165 return "KeyCode=" + KeyEvent.keyCodeToString(mKeyCode) 166 + ", LongPress=" + supportLongPress() 167 + ", VeryLongPress=" + supportVeryLongPress() 168 + ", MaxMultiPressCount=" + getMaxMultiPressCount(); 169 } 170 171 @Override equals(Object o)172 public boolean equals(Object o) { 173 if (this == o) { 174 return true; 175 } 176 if (o instanceof SingleKeyRule) { 177 SingleKeyRule that = (SingleKeyRule) o; 178 return mKeyCode == that.mKeyCode; 179 } 180 return false; 181 } 182 183 @Override hashCode()184 public int hashCode() { 185 return mKeyCode; 186 } 187 } 188 MessageObject(SingleKeyRule activeRule, int keyCode, int pressCount, int displayId, int metaState, int deviceId)189 private record MessageObject(SingleKeyRule activeRule, int keyCode, int pressCount, 190 int displayId, int metaState, int deviceId) { 191 MessageObject(SingleKeyRule activeRule, int keyCode, int pressCount, KeyEvent event) { 192 this(activeRule, keyCode, pressCount, event.getDisplayId(), event.getMetaState(), 193 event.getDeviceId()); 194 } 195 } 196 get(Context context, Looper looper)197 static SingleKeyGestureDetector get(Context context, Looper looper) { 198 SingleKeyGestureDetector detector = new SingleKeyGestureDetector(looper); 199 sDefaultLongPressTimeout = context.getResources().getInteger( 200 com.android.internal.R.integer.config_globalActionsKeyTimeout); 201 sDefaultVeryLongPressTimeout = context.getResources().getInteger( 202 com.android.internal.R.integer.config_veryLongPressTimeout); 203 return detector; 204 } 205 SingleKeyGestureDetector(Looper looper)206 private SingleKeyGestureDetector(Looper looper) { 207 mHandler = new KeyHandler(looper); 208 } 209 addRule(SingleKeyRule rule)210 void addRule(SingleKeyRule rule) { 211 if (mRules.contains(rule)) { 212 throw new IllegalArgumentException("Rule : " + rule + " already exists."); 213 } 214 mRules.add(rule); 215 } 216 removeRule(SingleKeyRule rule)217 void removeRule(SingleKeyRule rule) { 218 mRules.remove(rule); 219 } 220 interceptKey(KeyEvent event, boolean interactive, boolean defaultDisplayOn)221 void interceptKey(KeyEvent event, boolean interactive, boolean defaultDisplayOn) { 222 if (event.getAction() == KeyEvent.ACTION_DOWN) { 223 // Store the non interactive state and display on state when first down. 224 if (mDownKeyCode == KeyEvent.KEYCODE_UNKNOWN || mDownKeyCode != event.getKeyCode()) { 225 mBeganFromNonInteractive = !interactive; 226 mBeganFromDefaultDisplayOn = defaultDisplayOn; 227 } 228 interceptKeyDown(event); 229 } else { 230 interceptKeyUp(event); 231 } 232 } 233 interceptKeyDown(KeyEvent event)234 private void interceptKeyDown(KeyEvent event) { 235 final int keyCode = event.getKeyCode(); 236 // same key down. 237 if (mDownKeyCode == keyCode) { 238 if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0 239 && mActiveRule.supportLongPress() && !mHandledByLongPress) { 240 if (DEBUG) { 241 Log.i(TAG, "Long press key " + KeyEvent.keyCodeToString(keyCode)); 242 } 243 mHandledByLongPress = true; 244 mHandler.removeMessages(MSG_KEY_LONG_PRESS); 245 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); 246 MessageObject object = new MessageObject(mActiveRule, keyCode, /* pressCount= */ 1, 247 event); 248 final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object); 249 msg.setAsynchronous(true); 250 mHandler.sendMessage(msg); 251 } 252 return; 253 } 254 255 // When a different key is pressed, stop processing gestures for the currently active key. 256 if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN 257 || (mActiveRule != null && !mActiveRule.shouldInterceptKey(keyCode))) { 258 if (DEBUG) { 259 Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode)); 260 } 261 reset(); 262 } 263 mDownKeyCode = keyCode; 264 265 // Picks a new rule, return if no rule picked. 266 if (mActiveRule == null) { 267 final int count = mRules.size(); 268 for (int index = 0; index < count; index++) { 269 final SingleKeyRule rule = mRules.get(index); 270 if (rule.shouldInterceptKey(keyCode)) { 271 if (DEBUG) { 272 Log.i(TAG, "Intercept key by rule " + rule); 273 } 274 mActiveRule = rule; 275 break; 276 } 277 } 278 mLastDownTime = 0; 279 } 280 if (mActiveRule == null) { 281 return; 282 } 283 284 final long keyDownInterval = event.getDownTime() - mLastDownTime; 285 mLastDownTime = event.getDownTime(); 286 if (keyDownInterval >= MULTI_PRESS_TIMEOUT) { 287 mKeyPressCounter = 1; 288 } else { 289 mKeyPressCounter++; 290 } 291 292 if (mKeyPressCounter == 1) { 293 if (mActiveRule.supportLongPress()) { 294 MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, 295 event); 296 final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object); 297 msg.setAsynchronous(true); 298 mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs()); 299 } 300 301 if (mActiveRule.supportVeryLongPress()) { 302 MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, 303 event); 304 final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, object); 305 msg.setAsynchronous(true); 306 mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs()); 307 } 308 } else { 309 mHandler.removeMessages(MSG_KEY_LONG_PRESS); 310 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); 311 mHandler.removeMessages(MSG_KEY_DELAYED_PRESS); 312 313 // Trigger multi press immediately when reach max count.( > 1) 314 if (mActiveRule.getMaxMultiPressCount() > 1 315 && mKeyPressCounter == mActiveRule.getMaxMultiPressCount()) { 316 if (DEBUG) { 317 Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it" 318 + " reached the max count " + mKeyPressCounter); 319 } 320 MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, 321 event); 322 final Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); 323 msg.setAsynchronous(true); 324 mHandler.sendMessage(msg); 325 } 326 } 327 } 328 interceptKeyUp(KeyEvent event)329 private boolean interceptKeyUp(KeyEvent event) { 330 mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; 331 if (mActiveRule == null) { 332 return false; 333 } 334 335 if (!mHandledByLongPress) { 336 final long eventTime = event.getEventTime(); 337 if (eventTime < mLastDownTime + mActiveRule.getLongPressTimeoutMs()) { 338 mHandler.removeMessages(MSG_KEY_LONG_PRESS); 339 } else { 340 mHandledByLongPress = mActiveRule.supportLongPress(); 341 } 342 343 if (eventTime < mLastDownTime + mActiveRule.getVeryLongPressTimeoutMs()) { 344 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); 345 } else { 346 // If long press or very long press (~3.5s) had been handled, we should skip the 347 // short press behavior. 348 mHandledByLongPress |= mActiveRule.supportVeryLongPress(); 349 } 350 } 351 352 if (mHandledByLongPress) { 353 mHandledByLongPress = false; 354 mKeyPressCounter = 0; 355 mActiveRule = null; 356 return true; 357 } 358 359 if (event.getKeyCode() == mActiveRule.mKeyCode) { 360 if (Flags.abortSlowMultiPress() 361 && (event.getEventTime() - mLastDownTime 362 >= mActiveRule.getLongPressTimeoutMs())) { 363 // In this case, we are either on a first long press (but long press behavior is not 364 // supported for this rule), or, on a non-first press that is at least as long as 365 // the long-press duration. Thus, we will cancel the multipress gesture. 366 if (DEBUG) { 367 Log.d(TAG, "The duration of the press is too slow. Resetting."); 368 } 369 reset(); 370 return false; 371 } 372 373 // key-up action should always be triggered if not processed by long press. 374 MessageObject object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, 375 mKeyPressCounter, event); 376 Message msgKeyUp = mHandler.obtainMessage(MSG_KEY_UP, object); 377 msgKeyUp.setAsynchronous(true); 378 mHandler.sendMessage(msgKeyUp); 379 380 // Directly trigger short press when max count is 1. 381 if (mActiveRule.getMaxMultiPressCount() == 1) { 382 if (DEBUG) { 383 Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode())); 384 } 385 object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, 386 /* pressCount= */ 1, event); 387 Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); 388 msg.setAsynchronous(true); 389 mHandler.sendMessage(msg); 390 mActiveRule = null; 391 return true; 392 } 393 394 // This could be a multi-press. Wait a little bit longer to confirm. 395 if (mKeyPressCounter < mActiveRule.getMaxMultiPressCount()) { 396 object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, 397 mKeyPressCounter, event); 398 Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); 399 msg.setAsynchronous(true); 400 mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT); 401 } 402 return true; 403 } 404 reset(); 405 return false; 406 } 407 getKeyPressCounter(int keyCode)408 int getKeyPressCounter(int keyCode) { 409 if (mActiveRule != null && mActiveRule.mKeyCode == keyCode) { 410 return mKeyPressCounter; 411 } else { 412 return 0; 413 } 414 } 415 reset()416 void reset() { 417 if (mActiveRule != null) { 418 if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN) { 419 mHandler.removeMessages(MSG_KEY_LONG_PRESS); 420 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); 421 } 422 423 if (mKeyPressCounter > 0) { 424 mHandler.removeMessages(MSG_KEY_DELAYED_PRESS); 425 mKeyPressCounter = 0; 426 } 427 mActiveRule = null; 428 } 429 430 mHandledByLongPress = false; 431 mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; 432 } 433 isKeyIntercepted(int keyCode)434 boolean isKeyIntercepted(int keyCode) { 435 return mActiveRule != null && mActiveRule.shouldInterceptKey(keyCode); 436 } 437 beganFromNonInteractive()438 boolean beganFromNonInteractive() { 439 return mBeganFromNonInteractive; 440 } 441 beganFromDefaultDisplayOn()442 boolean beganFromDefaultDisplayOn() { 443 return mBeganFromDefaultDisplayOn; 444 } 445 dump(String prefix, PrintWriter pw)446 void dump(String prefix, PrintWriter pw) { 447 pw.println(prefix + "SingleKey rules:"); 448 for (SingleKeyRule rule : mRules) { 449 pw.println(prefix + " " + rule); 450 } 451 } 452 453 private class KeyHandler extends Handler { KeyHandler(Looper looper)454 KeyHandler(Looper looper) { 455 super(looper); 456 } 457 458 @Override handleMessage(Message msg)459 public void handleMessage(Message msg) { 460 final MessageObject object = (MessageObject) msg.obj; 461 final SingleKeyRule rule = object.activeRule; 462 if (rule == null) { 463 Log.wtf(TAG, "No active rule."); 464 return; 465 } 466 467 final int keyCode = object.keyCode; 468 final int pressCount = object.pressCount; 469 final int displayId = object.displayId; 470 switch(msg.what) { 471 case MSG_KEY_UP: 472 if (DEBUG) { 473 Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode) 474 + " on display " + displayId); 475 } 476 rule.onKeyUp(mLastDownTime, pressCount, displayId, object.deviceId, 477 object.metaState); 478 break; 479 case MSG_KEY_LONG_PRESS: 480 if (DEBUG) { 481 Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode)); 482 } 483 rule.onLongPress(mLastDownTime); 484 break; 485 case MSG_KEY_VERY_LONG_PRESS: 486 if (DEBUG) { 487 Log.i(TAG, "Detect very long press " 488 + KeyEvent.keyCodeToString(keyCode)); 489 } 490 rule.onVeryLongPress(mLastDownTime); 491 break; 492 case MSG_KEY_DELAYED_PRESS: 493 if (DEBUG) { 494 Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode) 495 + " on display " + displayId + ", count " + pressCount); 496 } 497 if (pressCount == 1) { 498 rule.onPress(mLastDownTime, displayId); 499 } else { 500 rule.onMultiPress(mLastDownTime, pressCount, displayId); 501 } 502 break; 503 } 504 } 505 } 506 } 507