KeyboardAccessibilityDelegate.java revision 639e431fa24b96a6118c85407d1f4a0af73a2813
1/*
2 * Copyright (C) 2011 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
17package com.android.inputmethod.accessibility;
18
19import android.content.Context;
20import android.support.v4.view.AccessibilityDelegateCompat;
21import android.support.v4.view.ViewCompat;
22import android.support.v4.view.accessibility.AccessibilityEventCompat;
23import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
24import android.util.Log;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.ViewParent;
28import android.view.accessibility.AccessibilityEvent;
29
30import com.android.inputmethod.keyboard.Key;
31import com.android.inputmethod.keyboard.KeyDetector;
32import com.android.inputmethod.keyboard.Keyboard;
33import com.android.inputmethod.keyboard.KeyboardView;
34import com.android.inputmethod.keyboard.PointerTracker;
35
36public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
37        extends AccessibilityDelegateCompat {
38    protected final KV mKeyboardView;
39    protected final KeyDetector mKeyDetector;
40    private Keyboard mKeyboard;
41    private KeyboardAccessibilityNodeProvider mAccessibilityNodeProvider;
42    private Key mCurrentHoverKey;
43
44    public KeyboardAccessibilityDelegate(final KV keyboardView, final KeyDetector keyDetector) {
45        super();
46        mKeyboardView = keyboardView;
47        mKeyDetector = keyDetector;
48
49        // Ensure that the view has an accessibility delegate.
50        ViewCompat.setAccessibilityDelegate(keyboardView, this);
51    }
52
53    /**
54     * Called when the keyboard layout changes.
55     * <p>
56     * <b>Note:</b> This method will be called even if accessibility is not
57     * enabled.
58     * @param keyboard The keyboard that is being set to the wrapping view.
59     */
60    public void setKeyboard(final Keyboard keyboard) {
61        if (keyboard == null) {
62            return;
63        }
64        if (mAccessibilityNodeProvider != null) {
65            mAccessibilityNodeProvider.setKeyboard(keyboard);
66        }
67        mKeyboard = keyboard;
68    }
69
70    protected final Keyboard getKeyboard() {
71        return mKeyboard;
72    }
73
74    protected final void setCurrentHoverKey(final Key key) {
75        mCurrentHoverKey = key;
76    }
77
78    protected final Key getCurrentHoverKey() {
79        return mCurrentHoverKey;
80    }
81
82    /**
83     * Sends a window state change event with the specified string resource id.
84     *
85     * @param resId The string resource id of the text to send with the event.
86     */
87    protected void sendWindowStateChanged(final int resId) {
88        if (resId == 0) {
89            return;
90        }
91        final Context context = mKeyboardView.getContext();
92        sendWindowStateChanged(context.getString(resId));
93    }
94
95    /**
96     * Sends a window state change event with the specified text.
97     *
98     * @param text The text to send with the event.
99     */
100    protected void sendWindowStateChanged(final String text) {
101        final AccessibilityEvent stateChange = AccessibilityEvent.obtain(
102                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
103        mKeyboardView.onInitializeAccessibilityEvent(stateChange);
104        stateChange.getText().add(text);
105        stateChange.setContentDescription(null);
106
107        final ViewParent parent = mKeyboardView.getParent();
108        if (parent != null) {
109            parent.requestSendAccessibilityEvent(mKeyboardView, stateChange);
110        }
111    }
112
113    /**
114     * Delegate method for View.getAccessibilityNodeProvider(). This method is called in SDK
115     * version 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) and higher to obtain the virtual
116     * node hierarchy provider.
117     *
118     * @param host The host view for the provider.
119     * @return The accessibility node provider for the current keyboard.
120     */
121    @Override
122    public KeyboardAccessibilityNodeProvider getAccessibilityNodeProvider(final View host) {
123        return getAccessibilityNodeProvider();
124    }
125
126    /**
127     * @return A lazily-instantiated node provider for this view delegate.
128     */
129    protected KeyboardAccessibilityNodeProvider getAccessibilityNodeProvider() {
130        // Instantiate the provide only when requested. Since the system
131        // will call this method multiple times it is a good practice to
132        // cache the provider instance.
133        if (mAccessibilityNodeProvider == null) {
134            mAccessibilityNodeProvider = new KeyboardAccessibilityNodeProvider(mKeyboardView);
135        }
136        return mAccessibilityNodeProvider;
137    }
138
139    /**
140     * Get a key that a hover event is on.
141     *
142     * @param event The hover event.
143     * @return key The key that the <code>event</code> is on.
144     */
145    protected final Key getHoverKey(final MotionEvent event) {
146        final int actionIndex = event.getActionIndex();
147        final int x = (int)event.getX(actionIndex);
148        final int y = (int)event.getY(actionIndex);
149        return mKeyDetector.detectHitKey(x, y);
150    }
151
152    /**
153     * Receives hover events when touch exploration is turned on in SDK versions ICS and higher.
154     *
155     * @param event The hover event.
156     * @return {@code true} if the event is handled.
157     */
158    public boolean onHoverEvent(final MotionEvent event) {
159        switch (event.getActionMasked()) {
160        case MotionEvent.ACTION_HOVER_ENTER:
161            onHoverEnter(event);
162            break;
163        case MotionEvent.ACTION_HOVER_MOVE:
164            onHoverMove(event);
165            break;
166        case MotionEvent.ACTION_HOVER_EXIT:
167            onHoverExit(event);
168            break;
169        default:
170            Log.w(getClass().getSimpleName(), "Unknown hover event: " + event);
171            break;
172        }
173        return true;
174    }
175
176    /**
177     * Process {@link MotionEvent#ACTION_HOVER_ENTER} event.
178     *
179     * @param event A hover enter event.
180     */
181    protected void onHoverEnter(final MotionEvent event) {
182        final Key key = getHoverKey(event);
183        if (key != null) {
184            onHoverEnterKey(key);
185        }
186        setCurrentHoverKey(key);
187    }
188
189    /**
190     * Process {@link MotionEvent#ACTION_HOVER_MOVE} event.
191     *
192     * @param event A hover move event.
193     */
194    protected void onHoverMove(final MotionEvent event) {
195        final Key previousKey = getCurrentHoverKey();
196        final Key key = getHoverKey(event);
197        if (key != previousKey) {
198            if (previousKey != null) {
199                onHoverExitKey(previousKey);
200            }
201            if (key != null) {
202                onHoverEnterKey(key);
203            }
204        }
205        if (key != null) {
206            onHoverMoveKey(key);
207        }
208        setCurrentHoverKey(key);
209    }
210
211    /**
212     * Process {@link MotionEvent#ACTION_HOVER_EXIT} event.
213     *
214     * @param event A hover exit event.
215     */
216    protected void onHoverExit(final MotionEvent event) {
217        final Key key = getHoverKey(event);
218        // Make sure we're not getting an EXIT event because the user slid
219        // off the keyboard area, then force a key press.
220        if (key != null) {
221            simulateTouchEvent(MotionEvent.ACTION_DOWN, event);
222            simulateTouchEvent(MotionEvent.ACTION_UP, event);
223            onHoverExitKey(key);
224        }
225        setCurrentHoverKey(null);
226    }
227
228    /**
229     * Simulating a touch event by injecting a synthesized touch event into {@link PointerTracker}.
230     *
231     * @param touchAction The action of the synthesizing touch event.
232     * @param hoverEvent The base hover event from that the touch event is synthesized.
233     */
234    protected void simulateTouchEvent(final int touchAction, final MotionEvent hoverEvent) {
235        final MotionEvent touchEvent = synthesizeTouchEvent(touchAction, hoverEvent);
236        final int actionIndex = touchEvent.getActionIndex();
237        final int pointerId = touchEvent.getPointerId(actionIndex);
238        final PointerTracker tracker = PointerTracker.getPointerTracker(pointerId);
239        tracker.processMotionEvent(touchEvent, mKeyDetector);
240        touchEvent.recycle();
241    }
242
243    /**
244     * Synthesize a touch event from a hover event.
245     *
246     * @param touchAction The action of the synthesizing touch event.
247     * @param event The base hover event from that the touch event is synthesized.
248     * @return The synthesized touch event of <code>touchAction</code> that has pointer information
249     * of <code>event</code>.
250     */
251    protected static MotionEvent synthesizeTouchEvent(final int touchAction,
252            final MotionEvent event) {
253        final long downTime = event.getDownTime();
254        final long eventTime = event.getEventTime();
255        final int actionIndex = event.getActionIndex();
256        final float x = event.getX(actionIndex);
257        final float y = event.getY(actionIndex);
258        final int pointerId = event.getPointerId(actionIndex);
259        return MotionEvent.obtain(downTime, eventTime, touchAction, x, y, pointerId);
260    }
261
262    /**
263     * Handles a hover enter event on a key.
264     *
265     * @param key The currently hovered key.
266     */
267    protected void onHoverEnterKey(final Key key) {
268        key.onPressed();
269        mKeyboardView.invalidateKey(key);
270        final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
271        provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
272        provider.performActionForKey(key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
273    }
274
275    /**
276     * Handles a hover move event on a key.
277     *
278     * @param key The currently hovered key.
279     */
280    protected void onHoverMoveKey(final Key key) { }
281
282    /**
283     * Handles a hover exit event on a key.
284     *
285     * @param key The currently hovered key.
286     */
287    protected void onHoverExitKey(final Key key) {
288        key.onReleased();
289        mKeyboardView.invalidateKey(key);
290        final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
291        provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
292    }
293}
294