1 /*
2  * Copyright (C) 2019 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.inputmethod.leanback;
18 
19 import android.graphics.PointF;
20 import android.inputmethodservice.InputMethodService;
21 import android.inputmethodservice.Keyboard;
22 import android.inputmethodservice.Keyboard.Key;
23 import android.util.Log;
24 import android.view.KeyEvent;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.inputmethod.EditorInfo;
28 
29 import com.android.inputmethod.leanback.LeanbackKeyboardContainer.KeyFocus;
30 
31 import java.util.ArrayList;
32 /**
33  * Holds logic for the keyboard views. This includes things like when to
34  * snap, when to switch keyboards, etc. It provides callbacks for when actions
35  * that need to be handled at the IME level occur (when text is entered, when
36  * the action should be performed).
37  */
38 public class LeanbackKeyboardController implements LeanbackKeyboardContainer.VoiceListener,
39         LeanbackKeyboardContainer.DismissListener {
40     private static final String TAG = "LbKbController";
41     private static final boolean DEBUG = false;
42 
43     /**
44      * The amount of time to block movement after a button down was detected.
45      */
46     public static final int CLICK_MOVEMENT_BLOCK_DURATION_MS = 500;
47 
48     /**
49      * The minimum distance in pixels before the view will transition to the
50      * move state.
51      */
52     public float mResizeSquareDistance;
53 
54     // keep track of the most recent key changes and their times so we can
55     // revert motion caused by clicking
56     private static final int KEY_CHANGE_HISTORY_SIZE = 10;
57     private static final long KEY_CHANGE_REVERT_TIME_MS = 100;
58 
59     /**
60      * This listener reports high level actions that have occurred, such as
61      * text entry (from keys or voice) or the action button being pressed.
62      */
63     public interface InputListener {
64         public static final int ENTRY_TYPE_STRING = 0;
65         public static final int ENTRY_TYPE_BACKSPACE = 1;
66         public static final int ENTRY_TYPE_SUGGESTION = 2;
67         public static final int ENTRY_TYPE_LEFT = 3;
68         public static final int ENTRY_TYPE_RIGHT = 4;
69         public static final int ENTRY_TYPE_ACTION = 5;
70         public static final int ENTRY_TYPE_VOICE = 6;
71         public static final int ENTRY_TYPE_DISMISS = 7;
72         public static final int ENTRY_TYPE_VOICE_DISMISS = 8;
73 
74         /**
75          * Sent when the user has selected something that should affect the text
76          * field, such as entering a character, selecting the action, or
77          * finishing a voice action.
78          *
79          * @param type The type of key selected
80          * @param keyCode the key code of the key if applicable
81          * @param result The text entered if applicable
82          */
onEntry(int type, int keyCode, CharSequence result)83         public void onEntry(int type, int keyCode, CharSequence result);
84     }
85 
86     private static final class KeyChange {
87         public long time;
88         public PointF position;
89 
KeyChange(long time, PointF position)90         public KeyChange(long time, PointF position) {
91             this.time = time;
92             this.position = position;
93         }
94     }
95 
96     private class DoubleClickDetector {
97         final long DOUBLE_CLICK_TIMEOUT_MS = 200;
98         long mFirstClickTime = 0;
99         boolean mFirstClickShiftLocked;
100 
reset()101         public void reset() {
102             mFirstClickTime = 0;
103         }
104 
addEvent(long currTime)105         public void addEvent(long currTime) {
106             if (currTime - mFirstClickTime > DOUBLE_CLICK_TIMEOUT_MS) {
107                 mFirstClickTime = currTime;
108                 mFirstClickShiftLocked = mContainer.isCapsLockOn();
109                 commitKey();
110             } else {
111                 mContainer.onShiftDoubleClick(mFirstClickShiftLocked);
112                 reset();
113             }
114         }
115     }
116 
117     private DoubleClickDetector mDoubleClickDetector = new DoubleClickDetector();
118 
119     private View.OnLayoutChangeListener mOnLayoutChangeListener
120             = new View.OnLayoutChangeListener() {
121 
122                 @Override
123                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
124                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
125                     int w = right - left;
126                     int h = bottom - top;
127                     int oldW = oldRight - oldLeft;
128                     int oldH = oldBottom - oldTop;
129                     if (w > 0 && h > 0) {
130                         if (w != oldW || h != oldH) {
131                             initInputView();
132                         }
133                     }
134                 }
135             };
136 
137     private InputMethodService mContext;
138     private InputListener mInputListener;
139     private LeanbackKeyboardContainer mContainer;
140 
141     private LeanbackKeyboardContainer.KeyFocus mDownFocus =
142             new LeanbackKeyboardContainer.KeyFocus();
143     private LeanbackKeyboardContainer.KeyFocus mTempFocus =
144             new LeanbackKeyboardContainer.KeyFocus();
145 
146     ArrayList<KeyChange> mKeyChangeHistory = new ArrayList<KeyChange>(KEY_CHANGE_HISTORY_SIZE + 1);
147     private PointF mTempPoint = new PointF();
148 
149     private boolean mKeyDownReceived = false;
150     private boolean mLongPressHandled = false;
151     private KeyFocus mKeyDownKeyFocus;
152     private int mMoveCount;
153 
LeanbackKeyboardController(InputMethodService context, InputListener listener)154     public LeanbackKeyboardController(InputMethodService context, InputListener listener) {
155         this(context, listener, new LeanbackKeyboardContainer(context));
156     }
157 
LeanbackKeyboardController(InputMethodService context, InputListener listener, LeanbackKeyboardContainer container)158     LeanbackKeyboardController(InputMethodService context, InputListener listener,
159             LeanbackKeyboardContainer container) {
160         mContext = context;
161         mResizeSquareDistance = context.getResources().getDimension(R.dimen.resize_move_distance);
162         mResizeSquareDistance *= mResizeSquareDistance;
163         mInputListener = listener;
164         setKeyboardContainer(container);
165         mContainer.setVoiceListener(this);
166         mContainer.setDismissListener(this);
167     }
168 
169     /**
170      * This method is called when we start the input at a NEW input field.
171      */
onStartInput(EditorInfo attribute)172     public void onStartInput(EditorInfo attribute) {
173         if (mContainer != null) {
174             mContainer.onStartInput(attribute);
175             initInputView();
176         }
177     }
178 
179     /**
180      * This method is called by whenever we bring up the IME at an input field.
181      */
onStartInputView()182     public void onStartInputView() {
183         mKeyDownReceived = false;
184         if (mContainer != null) {
185             mContainer.onStartInputView();
186         }
187         mDoubleClickDetector.reset();
188     }
189 
190     /**
191      * This method sets the pixel positions in mSpaceTracker to match the
192      * current KeyFocus in {@link LeanbackKeyboardContainer} This method is called
193      * when the keyboard layout is complete, after
194      * {@link LeanbackKeyboardContainer.onInitInputView}, to initialize the starting
195      * position of mSpaceTracker; and in onUp to reset the pixel position in
196      * mSpaceTracker.
197      */
updatePositionToCurrentFocus()198     private void updatePositionToCurrentFocus() {
199         PointF currPosition = getCurrentKeyPosition();
200         if (currPosition != null) {
201         }
202     }
203 
initInputView()204     private void initInputView() {
205         mContainer.onInitInputView();
206         updatePositionToCurrentFocus();
207     }
208 
getCurrentKeyPosition()209     private PointF getCurrentKeyPosition() {
210         if (mContainer != null) {
211             LeanbackKeyboardContainer.KeyFocus initialKeyInfo = mContainer.getCurrFocus();
212             return new PointF(initialKeyInfo.rect.centerX(), initialKeyInfo.rect.centerY());
213         }
214         return null;
215     }
216 
performBestSnap(long time)217     private void performBestSnap(long time) {
218         KeyFocus focus = mContainer.getCurrFocus();
219         mTempPoint.x = focus.rect.centerX();
220         mTempPoint.y = focus.rect.centerY();
221         PointF bestSnap = getBestSnapPosition(mTempPoint, time);
222         mContainer.getBestFocus(bestSnap.x, bestSnap.y, mTempFocus);
223         mContainer.setFocus(mTempFocus);
224         updatePositionToCurrentFocus();
225     }
226 
getBestSnapPosition(PointF currPoint, long currTime)227     private PointF getBestSnapPosition(PointF currPoint, long currTime) {
228         if (mKeyChangeHistory.size() <= 1) {
229             return currPoint;
230         }
231         for (int i = 0; i < mKeyChangeHistory.size() - 1; i++) {
232             KeyChange change = mKeyChangeHistory.get(i);
233             KeyChange nextChange = mKeyChangeHistory.get(i + 1);
234             if (currTime - nextChange.time < KEY_CHANGE_REVERT_TIME_MS) {
235                 if (DEBUG) {
236                     Log.d(TAG, "Reverting keychange to " + change.position.toString());
237                 }
238                 // Return the oldest key change within the revert window and
239                 // clear all key changes
240                 currPoint = change.position;
241                 // on a revert, clear the history and add the reverting point.
242                 // This way the reverted point will be preferred if there's
243                 // another fast change before the next call.
244                 mKeyChangeHistory.clear();
245                 mKeyChangeHistory.add(new KeyChange(currTime, currPoint));
246                 break;
247             }
248         }
249         return currPoint;
250     }
251 
setKeyboardContainer(LeanbackKeyboardContainer container)252     public void setKeyboardContainer(LeanbackKeyboardContainer container) {
253         mContainer = container;
254         container.getView().addOnLayoutChangeListener(mOnLayoutChangeListener);
255     }
256 
getView()257     public View getView() {
258         if (mContainer != null) {
259             return mContainer.getView();
260         }
261         return null;
262     }
263 
areSuggestionsEnabled()264     public boolean areSuggestionsEnabled() {
265         if (mContainer != null) {
266             return mContainer.areSuggestionsEnabled();
267         }
268         return false;
269     }
270 
enableAutoEnterSpace()271     public boolean enableAutoEnterSpace() {
272         if (mContainer != null) {
273             return mContainer.enableAutoEnterSpace();
274         }
275         return false;
276     }
277 
onKeyDown(int keyCode, KeyEvent event)278     public boolean onKeyDown(int keyCode, KeyEvent event) {
279         mDownFocus.set(mContainer.getCurrFocus());
280         // this will handle other events, e.g. hardware keyboard
281         if (isEnterKey(keyCode)) {
282             mKeyDownReceived = true;
283             // first keyDown
284             if (event.getRepeatCount() == 0) {
285                 mContainer.setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_CLICK);
286             }
287         }
288 
289         return handleKeyDownEvent(keyCode, event.getRepeatCount());
290     }
291 
onKeyUp(int keyCode, KeyEvent event)292     public boolean onKeyUp(int keyCode, KeyEvent event) {
293         // this only handles InputDevice.SOURCE_TOUCH_NAVIGATION events
294         if (isEnterKey(keyCode)) {
295             if (!mKeyDownReceived || mLongPressHandled) {
296                 mLongPressHandled = false;
297                 return true;
298             }
299             mKeyDownReceived = false;
300             if (mContainer.getTouchState() == LeanbackKeyboardContainer.TOUCH_STATE_CLICK) {
301                 mContainer.setTouchState(LeanbackKeyboardContainer.TOUCH_STATE_TOUCH_SNAP);
302             }
303         }
304         return handleKeyUpEvent(keyCode, event.getEventTime());
305     }
306 
onGenericMotionEvent(MotionEvent event)307     public boolean onGenericMotionEvent(MotionEvent event) {
308         return false;
309     }
310 
onDirectionalMove(int dir)311     private boolean onDirectionalMove(int dir) {
312         if (mContainer.getNextFocusInDirection(dir, mDownFocus, mTempFocus)) {
313             mContainer.setFocus(mTempFocus);
314             mDownFocus.set(mTempFocus);
315 
316             clearKeyIfNecessary();
317         }
318 
319         return true;
320     }
321 
clearKeyIfNecessary()322     private void clearKeyIfNecessary() {
323         mMoveCount++;
324         if (mMoveCount >= 3) {
325             mMoveCount = 0;
326             mKeyDownKeyFocus = null;
327         }
328     }
329 
commitKey()330     private void commitKey() {
331         commitKey(mContainer.getCurrFocus());
332     }
333 
commitKey(LeanbackKeyboardContainer.KeyFocus keyFocus)334     private void commitKey(LeanbackKeyboardContainer.KeyFocus keyFocus) {
335         if (mContainer == null || keyFocus == null) {
336             return;
337         }
338 
339         switch (keyFocus.type) {
340             case KeyFocus.TYPE_VOICE:
341                 // voice doesn't have to go through the IME
342                 mContainer.onVoiceClick();
343                 break;
344             case KeyFocus.TYPE_ACTION:
345                 mInputListener.onEntry(InputListener.ENTRY_TYPE_ACTION, 0, null);
346                 break;
347             case KeyFocus.TYPE_SUGGESTION:
348                 mInputListener.onEntry(InputListener.ENTRY_TYPE_SUGGESTION, 0,
349                         mContainer.getSuggestionText(keyFocus.index));
350                 break;
351             default:
352                 Key key = mContainer.getKey(keyFocus.type, keyFocus.index);
353                 if (key != null) {
354                     int code = key.codes[0];
355                     CharSequence label = key.label;
356                     handleCommitKeyboardKey(code, label);
357                 }
358                 break;
359 
360         }
361     }
362 
handleCommitKeyboardKey(int code, CharSequence label)363     private void handleCommitKeyboardKey(int code, CharSequence label) {
364         switch (code) {
365             case Keyboard.KEYCODE_MODE_CHANGE:
366                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
367                     Log.d(TAG, "mode change");
368                 }
369                 mContainer.onModeChangeClick();
370                 break;
371             case LeanbackKeyboardView.KEYCODE_CAPS_LOCK:
372                 mContainer.onShiftDoubleClick(mContainer.isCapsLockOn());
373                 break;
374             case Keyboard.KEYCODE_SHIFT:
375                 // TODO invalidate and draw a different shift
376                 // key in the function keyboard
377                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
378                     Log.d(TAG, "shift");
379                 }
380                 mContainer.onShiftClick();
381                 break;
382             case LeanbackKeyboardView.KEYCODE_DISMISS_MINI_KEYBOARD:
383                 mContainer.dismissMiniKeyboard();
384                 break;
385             case LeanbackKeyboardView.KEYCODE_LEFT:
386                 mInputListener.onEntry(InputListener.ENTRY_TYPE_LEFT, 0, null);
387                 break;
388             case LeanbackKeyboardView.KEYCODE_RIGHT:
389                 mInputListener.onEntry(InputListener.ENTRY_TYPE_RIGHT, 0, null);
390                 break;
391             case Keyboard.KEYCODE_DELETE:
392                 mInputListener.onEntry(InputListener.ENTRY_TYPE_BACKSPACE, 0, null);
393                 break;
394             case LeanbackKeyboardView.ASCII_SPACE:
395                 mInputListener.onEntry(InputListener.ENTRY_TYPE_STRING, code, " ");
396                 mContainer.onSpaceEntry();
397                 break;
398             case LeanbackKeyboardView.ASCII_PERIOD:
399                 mInputListener.onEntry(InputListener.ENTRY_TYPE_STRING, code, label);
400                 mContainer.onPeriodEntry();
401                 break;
402             case LeanbackKeyboardView.KEYCODE_VOICE:
403                 mContainer.startVoiceRecording();
404                 break;
405         // fall through to default with this label
406             default:
407                 mInputListener.onEntry(InputListener.ENTRY_TYPE_STRING, code, label);
408                 mContainer.onTextEntry();
409 
410                 if (mContainer.isMiniKeyboardOnScreen()) {
411                     mContainer.dismissMiniKeyboard();
412                 }
413                 break;
414         }
415     }
416 
handleKeyDownEvent(int keyCode, int eventRepeatCount)417     private boolean handleKeyDownEvent(int keyCode, int eventRepeatCount) {
418         keyCode = getSimplifiedKey(keyCode);
419 
420         // never trap back
421         if (keyCode == KeyEvent.KEYCODE_BACK) {
422             mContainer.cancelVoiceRecording();
423             return false;
424         }
425 
426         // capture all key downs when voice is visible
427         if (mContainer.isVoiceVisible()) {
428             if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
429                 mContainer.cancelVoiceRecording();
430             }
431             return true;
432         }
433 
434         boolean handled = true;
435         switch(keyCode) {
436             // Direction keys are handled on down to allow repeated movement
437             case KeyEvent.KEYCODE_DPAD_LEFT:
438                 handled = onDirectionalMove(LeanbackKeyboardContainer.DIRECTION_LEFT);
439                 break;
440             case KeyEvent.KEYCODE_DPAD_RIGHT:
441                 handled = onDirectionalMove(LeanbackKeyboardContainer.DIRECTION_RIGHT);
442                 break;
443             case KeyEvent.KEYCODE_DPAD_UP:
444                 handled = onDirectionalMove(LeanbackKeyboardContainer.DIRECTION_UP);
445                 break;
446             case KeyEvent.KEYCODE_DPAD_DOWN:
447                 handled = onDirectionalMove(LeanbackKeyboardContainer.DIRECTION_DOWN);
448                 break;
449             case KeyEvent.KEYCODE_BUTTON_X:
450                 handleCommitKeyboardKey(Keyboard.KEYCODE_DELETE, null);
451                 break;
452             case KeyEvent.KEYCODE_BUTTON_Y:
453                 handleCommitKeyboardKey(LeanbackKeyboardView.ASCII_SPACE, null);
454                 break;
455             case KeyEvent.KEYCODE_BUTTON_L1:
456                 handleCommitKeyboardKey(LeanbackKeyboardView.KEYCODE_LEFT, null);
457                 break;
458             case KeyEvent.KEYCODE_BUTTON_R1:
459                 handleCommitKeyboardKey(LeanbackKeyboardView.KEYCODE_RIGHT, null);
460                 break;
461             // these are handled on up
462             case KeyEvent.KEYCODE_DPAD_CENTER:
463                 if (eventRepeatCount == 0) {
464                     mMoveCount = 0;
465                     mKeyDownKeyFocus = new KeyFocus();
466                     mKeyDownKeyFocus.set(mContainer.getCurrFocus());
467                 } else if (eventRepeatCount == 1) {
468                     if (handleKeyLongPress(keyCode)) {
469                         mKeyDownKeyFocus = null;
470                     }
471                 }
472 
473                 if (isKeyHandledOnKeyDown(mContainer.getCurrKeyCode())) {
474                     commitKey();
475                 }
476                 break;
477             // also handled on up
478             case KeyEvent.KEYCODE_BUTTON_THUMBL:
479             case KeyEvent.KEYCODE_BUTTON_THUMBR:
480             case KeyEvent.KEYCODE_ENTER:
481                 break;
482             default:
483                 handled = false;
484                 break;
485         }
486         return handled;
487     }
488 
handleKeyLongPress(int keyCode)489     private boolean handleKeyLongPress(int keyCode) {
490         mLongPressHandled = isEnterKey(keyCode) && mContainer.onKeyLongPress();
491         if (mContainer.isMiniKeyboardOnScreen()) {
492             Log.d(TAG, "mini keyboard shown after long press");
493         }
494         return mLongPressHandled;
495     }
496 
isKeyHandledOnKeyDown(int currKeyCode)497     private boolean isKeyHandledOnKeyDown(int currKeyCode) {
498         return currKeyCode == Keyboard.KEYCODE_DELETE
499                 || currKeyCode == LeanbackKeyboardView.KEYCODE_LEFT
500                 || currKeyCode == LeanbackKeyboardView.KEYCODE_RIGHT;
501     }
502 
503     /**
504      * This handles all key events from an input device
505      * @param keyCode
506      * @return true if the key was handled, false otherwise
507      */
handleKeyUpEvent(int keyCode, long currTime)508     private boolean handleKeyUpEvent(int keyCode, long currTime) {
509         keyCode = getSimplifiedKey(keyCode);
510 
511         // never trap back
512         if (keyCode == KeyEvent.KEYCODE_BACK) {
513             return false;
514         }
515 
516         // capture all key ups when voice is visible
517         if (mContainer.isVoiceVisible()) {
518             return true;
519         }
520 
521         boolean handled = true;
522         switch(keyCode) {
523             // Some keys are handled on down to allow repeats
524             case KeyEvent.KEYCODE_DPAD_LEFT:
525             case KeyEvent.KEYCODE_DPAD_RIGHT:
526             case KeyEvent.KEYCODE_DPAD_UP:
527             case KeyEvent.KEYCODE_DPAD_DOWN:
528                 clearKeyIfNecessary();
529                 break;
530             case KeyEvent.KEYCODE_BUTTON_X:
531             case KeyEvent.KEYCODE_BUTTON_Y:
532             case KeyEvent.KEYCODE_BUTTON_L1:
533             case KeyEvent.KEYCODE_BUTTON_R1:
534                 break;
535             case KeyEvent.KEYCODE_DPAD_CENTER:
536                 if (mContainer.getCurrKeyCode() == Keyboard.KEYCODE_SHIFT) {
537                     mDoubleClickDetector.addEvent(currTime);
538                 } else if (!isKeyHandledOnKeyDown(mContainer.getCurrKeyCode())) {
539                     commitKey(mKeyDownKeyFocus);
540                 }
541                 break;
542             case KeyEvent.KEYCODE_BUTTON_THUMBL:
543                 handleCommitKeyboardKey(Keyboard.KEYCODE_MODE_CHANGE, null);
544                 break;
545             case KeyEvent.KEYCODE_BUTTON_THUMBR:
546                 handleCommitKeyboardKey(LeanbackKeyboardView.KEYCODE_CAPS_LOCK, null);
547                 break;
548             case KeyEvent.KEYCODE_ENTER:
549                 if (mContainer != null) {
550                     KeyFocus keyFocus = mContainer.getCurrFocus();
551                     if (keyFocus != null && keyFocus.type ==  KeyFocus.TYPE_SUGGESTION) {
552                         mInputListener.onEntry(InputListener.ENTRY_TYPE_SUGGESTION, 0,
553                                 mContainer.getSuggestionText(keyFocus.index));
554                     }
555                 }
556                 mInputListener.onEntry(InputListener.ENTRY_TYPE_DISMISS, 0, null);
557                 break;
558             default:
559                 handled = false;
560                 break;
561         }
562         return handled;
563     }
564 
updateSuggestions(ArrayList<String> suggestions)565     public void updateSuggestions(ArrayList<String> suggestions) {
566         if (mContainer != null) {
567             mContainer.updateSuggestions(suggestions);
568         }
569     }
570 
571     @Override
onVoiceResult(String result)572     public void onVoiceResult(String result) {
573         mInputListener.onEntry(InputListener.ENTRY_TYPE_VOICE, 0, result);
574     }
575 
576     @Override
onDismiss(boolean fromVoice)577     public void onDismiss(boolean fromVoice) {
578         if (fromVoice) {
579             mInputListener.onEntry(InputListener.ENTRY_TYPE_VOICE_DISMISS, 0, null);
580         } else {
581             mInputListener.onEntry(InputListener.ENTRY_TYPE_DISMISS, 0, null);
582         }
583     }
584 
isEnterKey(int keyCode)585     private boolean isEnterKey(int keyCode) {
586         return getSimplifiedKey(keyCode) == KeyEvent.KEYCODE_DPAD_CENTER;
587     }
588 
getSimplifiedKey(int keyCode)589     private int getSimplifiedKey(int keyCode) {
590         // simplify for dpad center
591         keyCode = (keyCode == KeyEvent.KEYCODE_DPAD_CENTER ||
592                 keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER ||
593                 keyCode == KeyEvent.KEYCODE_BUTTON_A) ? KeyEvent.KEYCODE_DPAD_CENTER : keyCode;
594 
595         // simply for back
596         keyCode = (keyCode == KeyEvent.KEYCODE_BUTTON_B ? KeyEvent.KEYCODE_BACK : keyCode);
597 
598         return keyCode;
599     }
600 }
601