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