15ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette/*
25ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Copyright (C) 2011 The Android Open Source Project
35ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License.
68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at
75ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
95ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Unless required by applicable law or agreed to in writing, software
118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and
148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under the License.
155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */
165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
175ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepackage com.android.inputmethod.accessibility;
185ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1987d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaokaimport android.content.Context;
201e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaokaimport android.os.SystemClock;
219a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.AccessibilityDelegateCompat;
229a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.ViewCompat;
23f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaokaimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
24639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaokaimport android.util.Log;
255ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.MotionEvent;
269a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.View;
27c2ee72a214fef46bc02ce486220365bbefd78714Alan Viveretteimport android.view.ViewParent;
28c2ee72a214fef46bc02ce486220365bbefd78714Alan Viveretteimport android.view.accessibility.AccessibilityEvent;
295ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
30f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaokaimport com.android.inputmethod.keyboard.Key;
316a23b9e3d54031524445b1190cc1eba77916c5b3Tadashi G. Takaokaimport com.android.inputmethod.keyboard.KeyDetector;
328d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanvimport com.android.inputmethod.keyboard.Keyboard;
337b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaokaimport com.android.inputmethod.keyboard.KeyboardView;
345ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
3562316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka/**
3662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka * This class represents a delegate that can be registered in a class that extends
3762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka * {@link KeyboardView} to enhance accessibility support via composition rather via inheritance.
3862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka *
3962316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka * To implement accessibility mode, the target keyboard view has to:<p>
4062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka * - Call {@link #setKeyboard(Keyboard)} when a new keyboard is set to the keyboard view.
4162316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka * - Dispatch a hover event by calling {@link #onHoverEnter(MotionEvent)}.
4262316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka *
4362316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka * @param  The keyboard view class type.
4462316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka */
45f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaokapublic class KeyboardAccessibilityDelegate<KV extends KeyboardView>
464d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        extends AccessibilityDelegateCompat {
4762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    private static final String TAG = KeyboardAccessibilityDelegate.class.getSimpleName();
4862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    protected static final boolean DEBUG_HOVER = false;
4962316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka
504d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    protected final KV mKeyboardView;
514d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    protected final KeyDetector mKeyDetector;
5292892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka    private Keyboard mKeyboard;
531e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    private KeyboardAccessibilityNodeProvider<KV> mAccessibilityNodeProvider;
5482674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka    private Key mLastHoverKey;
555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
5662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    public static final int HOVER_EVENT_POINTER_ID = 0;
5762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka
584d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    public KeyboardAccessibilityDelegate(final KV keyboardView, final KeyDetector keyDetector) {
597b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaoka        super();
607b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaoka        mKeyboardView = keyboardView;
617b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaoka        mKeyDetector = keyDetector;
625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
639a81ce92c381007affe6bb2310bf94c9856eaae1alanv        // Ensure that the view has an accessibility delegate.
647b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaoka        ViewCompat.setAccessibilityDelegate(keyboardView, this);
659a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
665ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
67c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    /**
68c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * Called when the keyboard layout changes.
69c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * <p>
70c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * <b>Note:</b> This method will be called even if accessibility is not
71c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * enabled.
7292892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka     * @param keyboard The keyboard that is being set to the wrapping view.
73c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     */
7492892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka    public void setKeyboard(final Keyboard keyboard) {
7592892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka        if (keyboard == null) {
76559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette            return;
77559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette        }
78c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        if (mAccessibilityNodeProvider != null) {
7992892608228f680aa7e7c24c79c6285adbf4f4c1Tadashi G. Takaoka            mAccessibilityNodeProvider.setKeyboard(keyboard);
80c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        }
813cf759074ad490790ec110836242f60333efcad4Tadashi G. Takaoka        mKeyboard = keyboard;
82c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    }
83c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
84639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    protected final Keyboard getKeyboard() {
854d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka        return mKeyboard;
86c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    }
87c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
8882674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka    protected final void setLastHoverKey(final Key key) {
8982674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        mLastHoverKey = key;
90639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    }
91639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka
9282674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka    protected final Key getLastHoverKey() {
9382674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        return mLastHoverKey;
94639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    }
95639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka
96c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette    /**
9787d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka     * Sends a window state change event with the specified string resource id.
9887d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka     *
9987d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka     * @param resId The string resource id of the text to send with the event.
10087d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka     */
10187d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka    protected void sendWindowStateChanged(final int resId) {
10287d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka        if (resId == 0) {
10387d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka            return;
10487d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka        }
10587d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka        final Context context = mKeyboardView.getContext();
10687d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka        sendWindowStateChanged(context.getString(resId));
10787d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka    }
10887d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka
10987d2f3ea0edc8ebb724f5d2c6a07c125cfa59d23Tadashi G. Takaoka    /**
110c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     * Sends a window state change event with the specified text.
111c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     *
112a7b4398c35eaf87fd00086f660af7710c071c369Alan Viverette     * @param text The text to send with the event.
113c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette     */
1144d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka    protected void sendWindowStateChanged(final String text) {
115c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        final AccessibilityEvent stateChange = AccessibilityEvent.obtain(
116c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
1177b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaoka        mKeyboardView.onInitializeAccessibilityEvent(stateChange);
118c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        stateChange.getText().add(text);
119c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        stateChange.setContentDescription(null);
120c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette
1217b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaoka        final ViewParent parent = mKeyboardView.getParent();
122c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        if (parent != null) {
1237b90d2c432fd7ffbf0022fac9db921cf39197ac6Tadashi G. Takaoka            parent.requestSendAccessibilityEvent(mKeyboardView, stateChange);
124c2ee72a214fef46bc02ce486220365bbefd78714Alan Viverette        }
125f147794fd41491a3383e6aca6d49007f58124068alanv    }
126f147794fd41491a3383e6aca6d49007f58124068alanv
1279a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
128bca7e4e9a2ed07d5d87f4dce9f793e40edb09691Tadashi G. Takaoka     * Delegate method for View.getAccessibilityNodeProvider(). This method is called in SDK
129b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * version 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) and higher to obtain the virtual
130b6ca354431367b625daf9fff5fbe4b1f5ef996abKen Wakasa     * node hierarchy provider.
1319a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
132559616fb0c39e2f0bacdf294b84ba16ad1e8f371Alan Viverette     * @param host The host view for the provider.
1339a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return The accessibility node provider for the current keyboard.
1349a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
1359a81ce92c381007affe6bb2310bf94c9856eaae1alanv    @Override
1361e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    public KeyboardAccessibilityNodeProvider<KV> getAccessibilityNodeProvider(final View host) {
137f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        return getAccessibilityNodeProvider();
1385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
1395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    /**
141bca7e4e9a2ed07d5d87f4dce9f793e40edb09691Tadashi G. Takaoka     * @return A lazily-instantiated node provider for this view delegate.
142f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     */
1431e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    protected KeyboardAccessibilityNodeProvider<KV> getAccessibilityNodeProvider() {
144f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // Instantiate the provide only when requested. Since the system
145f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // will call this method multiple times it is a good practice to
146f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // cache the provider instance.
147f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        if (mAccessibilityNodeProvider == null) {
1481e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka            mAccessibilityNodeProvider =
1491e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka                    new KeyboardAccessibilityNodeProvider<>(mKeyboardView, this);
150f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        }
151f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        return mAccessibilityNodeProvider;
152f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    }
153f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
154f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    /**
155639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * Get a key that a hover event is on.
156639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     *
157639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * @param event The hover event.
158639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * @return key The key that the <code>event</code> is on.
159639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     */
16082674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka    protected final Key getHoverKeyOf(final MotionEvent event) {
161639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        final int actionIndex = event.getActionIndex();
162639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        final int x = (int)event.getX(actionIndex);
163639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        final int y = (int)event.getY(actionIndex);
164639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        return mKeyDetector.detectHitKey(x, y);
165639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    }
166639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka
167639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    /**
1684d146d5e3e00cab1cca7d0d29fe00c0d629b5eacTadashi G. Takaoka     * Receives hover events when touch exploration is turned on in SDK versions ICS and higher.
1699a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
1709a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param event The hover event.
1719647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka     * @return {@code true} if the event is handled.
1729a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
1739647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka    public boolean onHoverEvent(final MotionEvent event) {
174639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        switch (event.getActionMasked()) {
175f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka        case MotionEvent.ACTION_HOVER_ENTER:
176639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka            onHoverEnter(event);
1779647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka            break;
178f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka        case MotionEvent.ACTION_HOVER_MOVE:
179639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka            onHoverMove(event);
180639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka            break;
181639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_EXIT:
182639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka            onHoverExit(event);
183639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka            break;
184639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        default:
185639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka            Log.w(getClass().getSimpleName(), "Unknown hover event: " + event);
186639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka            break;
187639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        }
188639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        return true;
189639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    }
190639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka
191639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    /**
192639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * Process {@link MotionEvent#ACTION_HOVER_ENTER} event.
193639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     *
194639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * @param event A hover enter event.
195639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     */
196639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    protected void onHoverEnter(final MotionEvent event) {
19782674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        final Key key = getHoverKeyOf(event);
19862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (DEBUG_HOVER) {
19962316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka            Log.d(TAG, "onHoverEnter: key=" + key);
20062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
201639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        if (key != null) {
20262316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka            onHoverEnterTo(key);
203639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        }
20482674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        setLastHoverKey(key);
205639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    }
206639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka
207639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    /**
208639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * Process {@link MotionEvent#ACTION_HOVER_MOVE} event.
209639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     *
210639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * @param event A hover move event.
211639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     */
212639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    protected void onHoverMove(final MotionEvent event) {
213a021395e84d4a934c042c56ef03f352cf43463f3Tadashi G. Takaoka        final Key lastKey = getLastHoverKey();
21482674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        final Key key = getHoverKeyOf(event);
215a021395e84d4a934c042c56ef03f352cf43463f3Tadashi G. Takaoka        if (key != lastKey) {
216a021395e84d4a934c042c56ef03f352cf43463f3Tadashi G. Takaoka            if (lastKey != null) {
21762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka                onHoverExitFrom(lastKey);
218f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka            }
2199647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka            if (key != null) {
22062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka                onHoverEnterTo(key);
2219647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka            }
222f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka        }
223639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        if (key != null) {
22462316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka            onHoverMoveWithin(key);
225639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        }
22682674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        setLastHoverKey(key);
227639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    }
228639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka
229639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    /**
230639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * Process {@link MotionEvent#ACTION_HOVER_EXIT} event.
231639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     *
232639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * @param event A hover exit event.
233639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     */
234639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka    protected void onHoverExit(final MotionEvent event) {
23582674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        final Key lastKey = getLastHoverKey();
23662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (DEBUG_HOVER) {
23762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka            Log.d(TAG, "onHoverExit: key=" + getHoverKeyOf(event) + " last=" + lastKey);
23862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
23982674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        if (lastKey != null) {
24062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka            onHoverExitFrom(lastKey);
24182674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        }
24282674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        final Key key = getHoverKeyOf(event);
243639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        // Make sure we're not getting an EXIT event because the user slid
244639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        // off the keyboard area, then force a key press.
245639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        if (key != null) {
2461e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka            performClickOn(key);
24762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka            onHoverExitFrom(key);
248639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        }
24982674ca81c40acbba4fb9b7113a9a8fe13afccc6Tadashi G. Takaoka        setLastHoverKey(null);
250f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka    }
251f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka
252f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka    /**
2531e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka     * Perform click on a key.
2541a0cd0869dbe6c860edcf19ddb5af6beaba661fcTadashi G. Takaoka     *
2551a0cd0869dbe6c860edcf19ddb5af6beaba661fcTadashi G. Takaoka     * @param key A key to be registered.
2561a0cd0869dbe6c860edcf19ddb5af6beaba661fcTadashi G. Takaoka     */
2571e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    public void performClickOn(final Key key) {
25862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (DEBUG_HOVER) {
2591e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka            Log.d(TAG, "performClickOn: key=" + key);
26062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
2611e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        simulateTouchEvent(MotionEvent.ACTION_DOWN, key);
2621e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        simulateTouchEvent(MotionEvent.ACTION_UP, key);
2631a0cd0869dbe6c860edcf19ddb5af6beaba661fcTadashi G. Takaoka    }
2641a0cd0869dbe6c860edcf19ddb5af6beaba661fcTadashi G. Takaoka
2651a0cd0869dbe6c860edcf19ddb5af6beaba661fcTadashi G. Takaoka    /**
2663d8848e5cb709fb47b450e7ede5a2926d99c957dTadashi G. Takaoka     * Simulating a touch event by injecting a synthesized touch event into {@link KeyboardView}.
267f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka     *
268639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka     * @param touchAction The action of the synthesizing touch event.
2691e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka     * @param key The key that a synthesized touch event is on.
270f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka     */
2711e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    private void simulateTouchEvent(final int touchAction, final Key key) {
2721e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        final int x = key.getHitBox().centerX();
2731e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        final int y = key.getHitBox().centerY();
2741e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        final long eventTime = SystemClock.uptimeMillis();
2751e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        final MotionEvent touchEvent = MotionEvent.obtain(
2761e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka                eventTime, eventTime, touchAction, x, y, 0 /* metaState */);
2773d8848e5cb709fb47b450e7ede5a2926d99c957dTadashi G. Takaoka        mKeyboardView.onTouchEvent(touchEvent);
2783d8848e5cb709fb47b450e7ede5a2926d99c957dTadashi G. Takaoka        touchEvent.recycle();
279f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka    }
280f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka
281f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka    /**
2829647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka     * Handles a hover enter event on a key.
283f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka     *
2849647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka     * @param key The currently hovered key.
285f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka     */
28662316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    protected void onHoverEnterTo(final Key key) {
28762316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (DEBUG_HOVER) {
28862316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka            Log.d(TAG, "onHoverEnterTo: key=" + key);
28962316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
290639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        key.onPressed();
291639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        mKeyboardView.invalidateKey(key);
2921e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        final KeyboardAccessibilityNodeProvider<KV> provider = getAccessibilityNodeProvider();
293d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        provider.onHoverEnterTo(key);
2949647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka        provider.performActionForKey(key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
295f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka    }
296f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka
297f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka    /**
2989647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka     * Handles a hover move event on a key.
299f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka     *
300f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka     * @param key The currently hovered key.
301f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka     */
30262316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    protected void onHoverMoveWithin(final Key key) { }
303f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka
3049647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka    /**
3059647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka     * Handles a hover exit event on a key.
3069647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka     *
3079647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka     * @param key The currently hovered key.
3089647d7fbee4cbd72876e949e6544dc43fadbd148Tadashi G. Takaoka     */
30962316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka    protected void onHoverExitFrom(final Key key) {
31062316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        if (DEBUG_HOVER) {
31162316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka            Log.d(TAG, "onHoverExitFrom: key=" + key);
31262316d7e821fa3a1ed052eb1ac2e8c0d08931d3eTadashi G. Takaoka        }
313639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        key.onReleased();
314639e431fa24b96a6118c85407d1f4a0af73a2813Tadashi G. Takaoka        mKeyboardView.invalidateKey(key);
3151e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        final KeyboardAccessibilityNodeProvider<KV> provider = getAccessibilityNodeProvider();
316d4b6af14d8fc90ae64f55d87d88cdfd5885cb63dTadashi G. Takaoka        provider.onHoverExitFrom(key);
317f22285006af2d132a568aafbff85efb83698eaebTadashi G. Takaoka    }
3181e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka
3191e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    /**
3201e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka     * Perform long click on a key.
3211e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka     *
3221e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka     * @param key A key to be long pressed on.
3231e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka     */
3241e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    public void performLongClickOn(final Key key) {
3251e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka        // A extended class should override this method to implement long press.
3261e3167229519843b83ba8bea7d78a82ffba236bcTadashi G. Takaoka    }
3275ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette}
328