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