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