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