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