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