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