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