AccessibleKeyboardViewProxy.java revision e76a9b36cabc3eb9222be245e2cf736169432cd6
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
359a81ce92c381007affe6bb2310bf94c9856eaae1alanvpublic 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    /**
108586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     * Receives hover events when accessibility is turned on in SDK versions ICS
109586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette     * and higher.
1105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     *
1115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @param event The hover event.
1125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @return {@code true} if the event is handled
1135ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     */
114586a15c3f0d44590a5162e0ab4c3c52511f13f26Alan Viverette    public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
1155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final int x = (int) event.getX();
1165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final int y = (int) event.getY();
1179a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final Key previousKey = mLastHoverKey;
118e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        final Key key;
119e76a9b36cabc3eb9222be245e2cf736169432cd6alanv
120e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        if (pointInView(x, y)) {
121e76a9b36cabc3eb9222be245e2cf736169432cd6alanv            key = tracker.getKeyOn(x, y);
122e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        } else {
123e76a9b36cabc3eb9222be245e2cf736169432cd6alanv            key = null;
124e76a9b36cabc3eb9222be245e2cf736169432cd6alanv        }
1259a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1269a81ce92c381007affe6bb2310bf94c9856eaae1alanv        mLastHoverKey = key;
1275ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1285ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        switch (event.getAction()) {
129c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_EXIT:
1306662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            // Make sure we're not getting an EXIT event because the user slid
1316662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            // off the keyboard area, then force a key press.
132e76a9b36cabc3eb9222be245e2cf736169432cd6alanv            if (key != null) {
133f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                getAccessibilityNodeProvider().simulateKeyPress(key);
1346662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            }
1356662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv            //$FALL-THROUGH$
1366662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv        case MotionEvent.ACTION_HOVER_ENTER:
1379a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return onHoverKey(key, event);
138c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_MOVE:
1399a81ce92c381007affe6bb2310bf94c9856eaae1alanv            if (key != previousKey) {
1409a81ce92c381007affe6bb2310bf94c9856eaae1alanv                return onTransitionKey(key, previousKey, event);
1419a81ce92c381007affe6bb2310bf94c9856eaae1alanv            } else {
1429a81ce92c381007affe6bb2310bf94c9856eaae1alanv                return onHoverKey(key, event);
1435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            }
1445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
1455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return false;
1475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
1485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1499a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
150f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     * @return A lazily-instantiated node provider for this view proxy.
151f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv     */
152f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    private AccessibilityEntityProvider getAccessibilityNodeProvider() {
153f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // Instantiate the provide only when requested. Since the system
154f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // will call this method multiple times it is a good practice to
155f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        // cache the provider instance.
156f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        if (mAccessibilityNodeProvider == null) {
157f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod);
158f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        }
159f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        return mAccessibilityNodeProvider;
160f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    }
161f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
162f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv    /**
1636662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * Utility method to determine whether the given point, in local
1646662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * coordinates, is inside the view, where the area of the view is contracted
1656662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * by the edge slop factor.
1666662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     *
1676662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * @param localX The local x-coordinate.
1686662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     * @param localY The local y-coordinate.
1696662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv     */
1706662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    private boolean pointInView(int localX, int localY) {
1716662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv        return (localX >= mEdgeSlop) && (localY >= mEdgeSlop)
1726662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv                && (localX < (mView.getWidth() - mEdgeSlop))
1736662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv                && (localY < (mView.getHeight() - mEdgeSlop));
1746662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    }
1756662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv
1766662e2a40dc764d5b6a55c0e30ce650fd834afb6alanv    /**
1779a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT
1789a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * on the previous key, a HOVER_ENTER on the current key, and a HOVER_MOVE
1799a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * on the current key.
1809a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
1819a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param currentKey The currently hovered key.
1829a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param previousKey The previously hovered key.
1839a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param event The event that triggered the transition.
1849a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return {@code true} if the event was handled.
1859a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
1869a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private boolean onTransitionKey(Key currentKey, Key previousKey, MotionEvent event) {
1879a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final int savedAction = event.getAction();
1889a81ce92c381007affe6bb2310bf94c9856eaae1alanv
189c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
1909a81ce92c381007affe6bb2310bf94c9856eaae1alanv        onHoverKey(previousKey, event);
1919a81ce92c381007affe6bb2310bf94c9856eaae1alanv
192c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        event.setAction(MotionEvent.ACTION_HOVER_ENTER);
1939a81ce92c381007affe6bb2310bf94c9856eaae1alanv        onHoverKey(currentKey, event);
1949a81ce92c381007affe6bb2310bf94c9856eaae1alanv
195c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        event.setAction(MotionEvent.ACTION_HOVER_MOVE);
1969a81ce92c381007affe6bb2310bf94c9856eaae1alanv        final boolean handled = onHoverKey(currentKey, event);
1979a81ce92c381007affe6bb2310bf94c9856eaae1alanv
1989a81ce92c381007affe6bb2310bf94c9856eaae1alanv        event.setAction(savedAction);
1999a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2009a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return handled;
2019a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
2029a81ce92c381007affe6bb2310bf94c9856eaae1alanv
2039a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
2049a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * Handles a hover event on a key. If {@link Key} extended View, this would
2059a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * be analogous to calling View.onHoverEvent(MotionEvent).
2069a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
2079a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param key The currently hovered key.
2089a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @param event The hover event.
2099a81ce92c381007affe6bb2310bf94c9856eaae1alanv     * @return {@code true} if the event was handled.
2109a81ce92c381007affe6bb2310bf94c9856eaae1alanv     */
2119a81ce92c381007affe6bb2310bf94c9856eaae1alanv    private boolean onHoverKey(Key key, MotionEvent event) {
2129a81ce92c381007affe6bb2310bf94c9856eaae1alanv        // Null keys can't receive events.
2139a81ce92c381007affe6bb2310bf94c9856eaae1alanv        if (key == null) {
2149a81ce92c381007affe6bb2310bf94c9856eaae1alanv            return false;
2155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
2165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
217f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv        final AccessibilityEntityProvider provider = getAccessibilityNodeProvider();
218f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv
2199a81ce92c381007affe6bb2310bf94c9856eaae1alanv        switch (event.getAction()) {
220c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_ENTER:
221f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            provider.sendAccessibilityEventForKey(
222f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
223f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            provider.performActionForKey(
224f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
2259a81ce92c381007affe6bb2310bf94c9856eaae1alanv            break;
226c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        case MotionEvent.ACTION_HOVER_EXIT:
227f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv            provider.sendAccessibilityEventForKey(
228f2eba97cc09c86f9a84b61cccf3f233e1fb85a6calanv                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
2299a81ce92c381007affe6bb2310bf94c9856eaae1alanv            break;
2305ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
2315ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
2329a81ce92c381007affe6bb2310bf94c9856eaae1alanv        return true;
2339a81ce92c381007affe6bb2310bf94c9856eaae1alanv    }
2345ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
2359a81ce92c381007affe6bb2310bf94c9856eaae1alanv    /**
2368d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     * Notifies the user of changes in the keyboard shift state.
2378d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     */
2388d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    public void notifyShiftState() {
2398d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Keyboard keyboard = mView.getKeyboard();
2408d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final KeyboardId keyboardId = keyboard.mId;
2418d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final int elementId = keyboardId.mElementId;
2428d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Context context = mView.getContext();
2438d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final CharSequence text;
2448d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2458d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        switch (elementId) {
2468d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
2478d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
2488d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            text = context.getText(R.string.spoken_description_shiftmode_locked);
2498d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2508d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
2518d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
2528d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
2538d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            text = context.getText(R.string.spoken_description_shiftmode_on);
2548d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2558d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        default:
2568d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            text = context.getText(R.string.spoken_description_shiftmode_off);
2578d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        }
2588d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2595f312c9c1546da9f73d02f911d3365da4ff658fbalanv        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
2608d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    }
2618d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2628d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    /**
2638d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     * Notifies the user of changes in the keyboard symbols state.
2648d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv     */
2658d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    public void notifySymbolsState() {
2668d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Keyboard keyboard = mView.getKeyboard();
2678d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final Context context = mView.getContext();
2688d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final KeyboardId keyboardId = keyboard.mId;
2698d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final int elementId = keyboardId.mElementId;
2708d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final int resId;
2718d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2728d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        switch (elementId) {
2738d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET:
2748d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
2758d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
2768d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
2778d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
2788d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_alpha;
2798d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2808d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_SYMBOLS:
2818d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
2828d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_symbol;
2838d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2848d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_PHONE:
2858d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_phone;
2868d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2878d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
2888d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = R.string.spoken_description_mode_phone_shift;
2898d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            break;
2908d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        default:
2918d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            resId = -1;
2928d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        }
2938d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2948d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        if (resId < 0) {
2958d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv            return;
2968d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        }
2978d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv
2988d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv        final String text = context.getString(resId);
2995f312c9c1546da9f73d02f911d3365da4ff658fbalanv        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
3008d4f0d5d1df2e0ae0b6ac332fd6661b7fa903186alanv    }
3015ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette}
302