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