• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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