15ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette/*
25ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Copyright (C) 2011 The Android Open Source Project
35ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
45ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License"); you may not
55ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * use this file except in compliance with the License. You may obtain a copy of
65ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the License at
75ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
85ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * http://www.apache.org/licenses/LICENSE-2.0
95ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Unless required by applicable law or agreed to in writing, software
115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
135ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * License for the specific language governing permissions and limitations under
145ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * the License.
155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */
165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
175ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepackage com.android.inputmethod.accessibility;
185ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
195ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.content.Context;
2058e3f1065ef47e7116299b9d5087ba2a2b6065a2Alan Viveretteimport android.inputmethodservice.InputMethodService;
219a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.AccessibilityDelegateCompat;
229a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.ViewCompat;
239a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.support.v4.view.accessibility.AccessibilityEventCompat;
24f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanvimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
255ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.MotionEvent;
269a81ce92c381007affe6bb2310bf94c9856eaae1alanvimport android.view.View;
275ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
28e7759091ddb5ec18268945d70d9212195bf6497bTadashi G. Takaokaimport com.android.inputmethod.keyboard.Key;
298d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanvimport com.android.inputmethod.keyboard.Keyboard;
308d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanvimport com.android.inputmethod.keyboard.KeyboardId;
31c8e45ddb032554f4e9d4411d8ef47d98db62d77bTadashi G. Takaokaimport com.android.inputmethod.keyboard.MainKeyboardView;
325ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport com.android.inputmethod.keyboard.PointerTracker;
338d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanvimport com.android.inputmethod.latin.R;
345ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
35a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
365ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
375ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
3858e3f1065ef47e7116299b9d5087ba2a2b6065a2Alan Viverette    private InputMethodService mInputMethod;
39c8e45ddb032554f4e9d4411d8ef47d98db62d77bTadashi G. Takaoka    private MainKeyboardView mView;
409a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private AccessibilityEntityProvider mAccessibilityNodeProvider;
415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
42e22baaadd314c80f835e2e96fb0dfc73838ac2cdTadashi G. Takaoka    private Key mLastHoverKey = null;
435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
446662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    /**
456662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * Inset in pixels to look for keys when the user's finger exits the
46e76a9b36cabc3eb9222be245e2cf736169432cd6alanv     * keyboard area.
476662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     */
486662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    private int mEdgeSlop;
496662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv
502ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka    public static void init(InputMethodService inputMethod) {
512ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka        sInstance.initInternal(inputMethod);
525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public static AccessibleKeyboardViewProxy getInstance() {
555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return sInstance;
565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private AccessibleKeyboardViewProxy() {
595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // Not publicly instantiable.
605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
622ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka    private void initInternal(InputMethodService inputMethod) {
6358e3f1065ef47e7116299b9d5087ba2a2b6065a2Alan Viverette        mInputMethod = inputMethod;
64e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
65e76a9b36cabc3eb9222be245e2cf736169432cd6alanv                R.dimen.accessibility_edge_slop);
665ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
675ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
689a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
699a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Sets the view wrapped by this proxy.
709a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
719a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param view The view to wrap.
729a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
73c8e45ddb032554f4e9d4411d8ef47d98db62d77bTadashi G. Takaoka    public void setView(MainKeyboardView view) {
749a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (view == null) {
759a81ce92c381007affe6bb2310bf94c9856eaae1alanv            // Ignore null views.
769a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return;
775ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
785ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
799a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mView = view;
805ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
819a81ce92c381007affe6bb2310bf94c9856eaae1alanv        // Ensure that the view has an accessibility delegate.
829a81ce92c381007affe6bb2310bf94c9856eaae1alanv        ViewCompat.setAccessibilityDelegate(view, this);
8348ccd5528163383a46b597e9d5ea919ddc799f25alanv
8448ccd5528163383a46b597e9d5ea919ddc799f25alanv        if (mAccessibilityNodeProvider != null) {
8548ccd5528163383a46b597e9d5ea919ddc799f25alanv            mAccessibilityNodeProvider.setView(view);
8648ccd5528163383a46b597e9d5ea919ddc799f25alanv        }
879a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
885ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
89f147794fd41491a3383e6aca6d49007f58124068alanv    public void setKeyboard(Keyboard keyboard) {
90f147794fd41491a3383e6aca6d49007f58124068alanv        if (mAccessibilityNodeProvider != null) {
91f147794fd41491a3383e6aca6d49007f58124068alanv            mAccessibilityNodeProvider.setKeyboard(keyboard);
92f147794fd41491a3383e6aca6d49007f58124068alanv        }
93f147794fd41491a3383e6aca6d49007f58124068alanv    }
94f147794fd41491a3383e6aca6d49007f58124068alanv
959a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
969a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Proxy method for View.getAccessibilityNodeProvider(). This method is
979a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * called in SDK version 15 and higher to obtain the virtual node hierarchy
989a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * provider.
999a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
1009a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return The accessibility node provider for the current keyboard.
1019a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
1029a81ce92c381007affe6bb2310bf94c9856eaae1alanv    @Override
1039a81ce92c381007affe6bb2310bf94c9856eaae1alanv    public AccessibilityEntityProvider getAccessibilityNodeProvider(View host) {
104f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        return getAccessibilityNodeProvider();
1055ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
1065ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1075ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    /**
108dc2ee7772402633817702e95c2a5b17f6dec03ebalanv     * Intercepts touch events before dispatch when touch exploration is turned
109dc2ee7772402633817702e95c2a5b17f6dec03ebalanv     * on in ICS and higher.
11026b424b6448fbaddc86d11377ca44ff3169a5d7ealanv     *
111dc2ee7772402633817702e95c2a5b17f6dec03ebalanv     * @param event The motion event being dispatched.
11226b424b6448fbaddc86d11377ca44ff3169a5d7ealanv     * @return {@code true} if the event is handled
11326b424b6448fbaddc86d11377ca44ff3169a5d7ealanv     */
114dc2ee7772402633817702e95c2a5b17f6dec03ebalanv    public boolean dispatchTouchEvent(MotionEvent event) {
11526b424b6448fbaddc86d11377ca44ff3169a5d7ealanv        // To avoid accidental key presses during touch exploration, always drop
116dc2ee7772402633817702e95c2a5b17f6dec03ebalanv        // touch events generated by the user.
11726b424b6448fbaddc86d11377ca44ff3169a5d7ealanv        return false;
11826b424b6448fbaddc86d11377ca44ff3169a5d7ealanv    }
11926b424b6448fbaddc86d11377ca44ff3169a5d7ealanv
12026b424b6448fbaddc86d11377ca44ff3169a5d7ealanv    /**
12126b424b6448fbaddc86d11377ca44ff3169a5d7ealanv     * Receives hover events when touch exploration is turned on in SDK versions
12226b424b6448fbaddc86d11377ca44ff3169a5d7ealanv     * ICS and higher.
1235ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     *
1245ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @param event The hover event.
1255ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @return {@code true} if the event is handled
1265ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     */
127586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette    public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
1285ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final int x = (int) event.getX();
1295ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final int y = (int) event.getY();
1309a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final Key previousKey = mLastHoverKey;
131e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        final Key key;
132e76a9b36cabc3eb9222be245e2cf736169432cd6alanv
133e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        if (pointInView(x, y)) {
134e76a9b36cabc3eb9222be245e2cf736169432cd6alanv            key = tracker.getKeyOn(x, y);
135e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        } else {
136e76a9b36cabc3eb9222be245e2cf736169432cd6alanv            key = null;
137e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        }
1389a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1399a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mLastHoverKey = key;
1405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        switch (event.getAction()) {
142c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_EXIT:
1436662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            // Make sure we're not getting an EXIT event because the user slid
1446662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            // off the keyboard area, then force a key press.
145e76a9b36cabc3eb9222be245e2cf736169432cd6alanv            if (key != null) {
146f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                getAccessibilityNodeProvider().simulateKeyPress(key);
1476662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            }
1486662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            //$FALL-THROUGH$
1496662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv        case MotionEvent.ACTION_HOVER_ENTER:
1509a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return onHoverKey(key, event);
151c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_MOVE:
1529a81ce92c381007affe6bb2310bf94c9856eaae1alanv            if (key != previousKey) {
1539a81ce92c381007affe6bb2310bf94c9856eaae1alanv                return onTransitionKey(key, previousKey, event);
1549a81ce92c381007affe6bb2310bf94c9856eaae1alanv            } else {
1559a81ce92c381007affe6bb2310bf94c9856eaae1alanv                return onHoverKey(key, event);
1565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            }
1575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
1585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return false;
1605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
1615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1629a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
163f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     * @return A lazily-instantiated node provider for this view proxy.
164f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     */
165f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    private AccessibilityEntityProvider getAccessibilityNodeProvider() {
166f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // Instantiate the provide only when requested. Since the system
167f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // will call this method multiple times it is a good practice to
168f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // cache the provider instance.
169f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        if (mAccessibilityNodeProvider == null) {
170f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod);
171f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        }
172f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        return mAccessibilityNodeProvider;
173f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    }
174f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
175f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    /**
1766662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * Utility method to determine whether the given point, in local
1776662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * coordinates, is inside the view, where the area of the view is contracted
1786662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * by the edge slop factor.
1796662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     *
1806662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * @param localX The local x-coordinate.
1816662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * @param localY The local y-coordinate.
1826662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     */
1836662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    private boolean pointInView(int localX, int localY) {
1846662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv        return (localX >= mEdgeSlop) && (localY >= mEdgeSlop)
1856662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv                && (localX < (mView.getWidth() - mEdgeSlop))
1866662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv                && (localY < (mView.getHeight() - mEdgeSlop));
1876662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    }
1886662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv
1896662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    /**
1909a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT
1919a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * on the previous key, a HOVER_ENTER on the current key, and a HOVER_MOVE
1929a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * on the current key.
1939a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
1949a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param currentKey The currently hovered key.
1959a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param previousKey The previously hovered key.
1969a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param event The event that triggered the transition.
1979a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return {@code true} if the event was handled.
1989a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
1999a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private boolean onTransitionKey(Key currentKey, Key previousKey, MotionEvent event) {
2009a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final int savedAction = event.getAction();
2019a81ce92c381007affe6bb2310bf94c9856eaae1alanv
202c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
2039a81ce92c381007affe6bb2310bf94c9856eaae1alanv        onHoverKey(previousKey, event);
2049a81ce92c381007affe6bb2310bf94c9856eaae1alanv
205c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        event.setAction(MotionEvent.ACTION_HOVER_ENTER);
2069a81ce92c381007affe6bb2310bf94c9856eaae1alanv        onHoverKey(currentKey, event);
2079a81ce92c381007affe6bb2310bf94c9856eaae1alanv
208c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        event.setAction(MotionEvent.ACTION_HOVER_MOVE);
2099a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final boolean handled = onHoverKey(currentKey, event);
2109a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2119a81ce92c381007affe6bb2310bf94c9856eaae1alanv        event.setAction(savedAction);
2129a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2139a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return handled;
2149a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
2159a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2169a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
2179a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Handles a hover event on a key. If {@link Key} extended View, this would
2189a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * be analogous to calling View.onHoverEvent(MotionEvent).
2199a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
2209a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key The currently hovered key.
2219a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param event The hover event.
2229a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return {@code true} if the event was handled.
2239a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
2249a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private boolean onHoverKey(Key key, MotionEvent event) {
2259a81ce92c381007affe6bb2310bf94c9856eaae1alanv        // Null keys can't receive events.
2269a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (key == null) {
2279a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return false;
2285ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
2295ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
230f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        final AccessibilityEntityProvider provider = getAccessibilityNodeProvider();
231f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
2329a81ce92c381007affe6bb2310bf94c9856eaae1alanv        switch (event.getAction()) {
233c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_ENTER:
234f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            provider.sendAccessibilityEventForKey(
235f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
236f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            provider.performActionForKey(
237f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
2389a81ce92c381007affe6bb2310bf94c9856eaae1alanv            break;
239c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_EXIT:
240f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            provider.sendAccessibilityEventForKey(
241f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
2429a81ce92c381007affe6bb2310bf94c9856eaae1alanv            break;
2435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
2445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
2459a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return true;
2469a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
2475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
2489a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
2498d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     * Notifies the user of changes in the keyboard shift state.
2508d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     */
2518d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    public void notifyShiftState() {
2528d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Keyboard keyboard = mView.getKeyboard();
2538d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final KeyboardId keyboardId = keyboard.mId;
2548d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final int elementId = keyboardId.mElementId;
2558d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Context context = mView.getContext();
2568d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final CharSequence text;
2578d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2588d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        switch (elementId) {
2598d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
2608d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
2618d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            text = context.getText(R.string.spoken_description_shiftmode_locked);
2628d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2638d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
2648d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
2658d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
2668d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            text = context.getText(R.string.spoken_description_shiftmode_on);
2678d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2688d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        default:
2698d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            text = context.getText(R.string.spoken_description_shiftmode_off);
2708d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        }
2718d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2725f312c9c1546da9f73d02f911d3365da4ff658fbalanv        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
2738d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    }
2748d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2758d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    /**
2768d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     * Notifies the user of changes in the keyboard symbols state.
2778d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     */
2788d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    public void notifySymbolsState() {
2798d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Keyboard keyboard = mView.getKeyboard();
2808d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Context context = mView.getContext();
2818d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final KeyboardId keyboardId = keyboard.mId;
2828d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final int elementId = keyboardId.mElementId;
2838d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final int resId;
2848d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2858d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        switch (elementId) {
2868d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET:
2878d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
2888d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
2898d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
2908d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
2918d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_alpha;
2928d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2938d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_SYMBOLS:
2948d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
2958d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_symbol;
2968d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2978d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_PHONE:
2988d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_phone;
2998d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
3008d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
3018d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_phone_shift;
3028d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
3038d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        default:
3048d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = -1;
3058d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        }
3068d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
3078d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        if (resId < 0) {
3088d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            return;
3098d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        }
3108d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
3118d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final String text = context.getString(resId);
3125f312c9c1546da9f73d02f911d3365da4ff658fbalanv        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
3138d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    }
3145ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette}
315