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