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