KeyboardAccessibilityDelegate.java revision a021395e84d4a934c042c56ef03f352cf43463f3
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 mLastHoverKey; 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 setLastHoverKey(final Key key) { 75 mLastHoverKey = key; 76 } 77 78 protected final Key getLastHoverKey() { 79 return mLastHoverKey; 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 getHoverKeyOf(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 = getHoverKeyOf(event); 183 if (key != null) { 184 onHoverEnterKey(key, event); 185 } 186 setLastHoverKey(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 lastKey = getLastHoverKey(); 196 final Key key = getHoverKeyOf(event); 197 if (key != lastKey) { 198 if (lastKey != null) { 199 onHoverExitKey(lastKey, event); 200 } 201 if (key != null) { 202 onHoverEnterKey(key, event); 203 } 204 } 205 if (key != null) { 206 onHoverMoveKey(key, event); 207 } 208 setLastHoverKey(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 lastKey = getLastHoverKey(); 218 if (lastKey != null) { 219 onHoverExitKey(lastKey, event); 220 } 221 final Key key = getHoverKeyOf(event); 222 // Make sure we're not getting an EXIT event because the user slid 223 // off the keyboard area, then force a key press. 224 if (key != null) { 225 simulateTouchEvent(MotionEvent.ACTION_DOWN, event); 226 simulateTouchEvent(MotionEvent.ACTION_UP, event); 227 onHoverExitKey(key, event); 228 } 229 setLastHoverKey(null); 230 } 231 232 /** 233 * Simulating a touch event by injecting a synthesized touch event into {@link PointerTracker}. 234 * 235 * @param touchAction The action of the synthesizing touch event. 236 * @param hoverEvent The base hover event from that the touch event is synthesized. 237 */ 238 protected void simulateTouchEvent(final int touchAction, final MotionEvent hoverEvent) { 239 final MotionEvent touchEvent = synthesizeTouchEvent(touchAction, hoverEvent); 240 final int actionIndex = touchEvent.getActionIndex(); 241 final int pointerId = touchEvent.getPointerId(actionIndex); 242 final PointerTracker tracker = PointerTracker.getPointerTracker(pointerId); 243 tracker.processMotionEvent(touchEvent, mKeyDetector); 244 touchEvent.recycle(); 245 } 246 247 /** 248 * Synthesize a touch event from a hover event. 249 * 250 * @param touchAction The action of the synthesizing touch event. 251 * @param event The base hover event from that the touch event is synthesized. 252 * @return The synthesized touch event of <code>touchAction</code> that has pointer information 253 * of <code>event</code>. 254 */ 255 protected static MotionEvent synthesizeTouchEvent(final int touchAction, 256 final MotionEvent event) { 257 final long downTime = event.getDownTime(); 258 final long eventTime = event.getEventTime(); 259 final int actionIndex = event.getActionIndex(); 260 final float x = event.getX(actionIndex); 261 final float y = event.getY(actionIndex); 262 final int pointerId = event.getPointerId(actionIndex); 263 return MotionEvent.obtain(downTime, eventTime, touchAction, x, y, pointerId); 264 } 265 266 /** 267 * Handles a hover enter event on a key. 268 * 269 * @param key The currently hovered key. 270 * @param event The hover event that triggers a call to this method. 271 */ 272 protected void onHoverEnterKey(final Key key, final MotionEvent event) { 273 key.onPressed(); 274 mKeyboardView.invalidateKey(key); 275 final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider(); 276 provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER); 277 provider.performActionForKey(key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); 278 } 279 280 /** 281 * Handles a hover move event on a key. 282 * 283 * @param key The currently hovered key. 284 * @param event The hover event that triggers a call to this method. 285 */ 286 protected void onHoverMoveKey(final Key key, final MotionEvent event) { } 287 288 /** 289 * Handles a hover exit event on a key. 290 * 291 * @param key The currently hovered key. 292 * @param event The hover event that triggers a call to this method. 293 */ 294 protected void onHoverExitKey(final Key key, final MotionEvent event) { 295 key.onReleased(); 296 mKeyboardView.invalidateKey(key); 297 final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider(); 298 provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT); 299 } 300} 301