AccessibilityEntityProvider.java revision 48ccd5528163383a46b597e9d5ea919ddc799f25
1/* 2 * Copyright (C) 2012 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.graphics.Rect; 20import android.inputmethodservice.InputMethodService; 21import android.os.Bundle; 22import android.os.SystemClock; 23import android.support.v4.view.ViewCompat; 24import android.support.v4.view.accessibility.AccessibilityEventCompat; 25import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 26import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; 27import android.support.v4.view.accessibility.AccessibilityRecordCompat; 28import android.util.Log; 29import android.util.SparseArray; 30import android.view.MotionEvent; 31import android.view.View; 32import android.view.accessibility.AccessibilityEvent; 33import android.view.inputmethod.EditorInfo; 34 35import com.android.inputmethod.keyboard.Key; 36import com.android.inputmethod.keyboard.Keyboard; 37import com.android.inputmethod.keyboard.KeyboardView; 38 39/** 40 * Exposes a virtual view sub-tree for {@link KeyboardView} and generates 41 * {@link AccessibilityEvent}s for individual {@link Key}s. 42 * <p> 43 * A virtual sub-tree is composed of imaginary {@link View}s that are reported 44 * as a part of the view hierarchy for accessibility purposes. This enables 45 * custom views that draw complex content to report them selves as a tree of 46 * virtual views, thus conveying their logical structure. 47 * </p> 48 */ 49public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat { 50 private static final String TAG = AccessibilityEntityProvider.class.getSimpleName(); 51 private static final int UNDEFINED = Integer.MIN_VALUE; 52 53 private final InputMethodService mInputMethodService; 54 private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper; 55 private final AccessibilityUtils mAccessibilityUtils; 56 57 /** A map of integer IDs to {@link Key}s. */ 58 private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>(); 59 60 /** Temporary rect used to calculate in-screen bounds. */ 61 private final Rect mTempBoundsInScreen = new Rect(); 62 63 /** The parent view's cached on-screen location. */ 64 private final int[] mParentLocation = new int[2]; 65 66 /** The virtual view identifier for the focused node. */ 67 private int mAccessibilityFocusedView = UNDEFINED; 68 69 /** The current keyboard view. */ 70 private KeyboardView mKeyboardView; 71 72 public AccessibilityEntityProvider(KeyboardView keyboardView, InputMethodService inputMethod) { 73 mInputMethodService = inputMethod; 74 75 mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance(); 76 mAccessibilityUtils = AccessibilityUtils.getInstance(); 77 78 setView(keyboardView); 79 } 80 81 /** 82 * Sets the keyboard view represented by this node provider. 83 * 84 * @param keyboardView The keyboard view to represent. 85 */ 86 public void setView(KeyboardView keyboardView) { 87 mKeyboardView = keyboardView; 88 89 assignVirtualViewIds(); 90 updateParentLocation(); 91 } 92 93 /** 94 * Creates and populates an {@link AccessibilityEvent} for the specified key 95 * and event type. 96 * 97 * @param key A key on the host keyboard view. 98 * @param eventType The event type to create. 99 * @return A populated {@link AccessibilityEvent} for the key. 100 * @see AccessibilityEvent 101 */ 102 public AccessibilityEvent createAccessibilityEvent(Key key, int eventType) { 103 final int virtualViewId = generateVirtualViewIdForKey(key); 104 final String keyDescription = getKeyDescription(key); 105 106 final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 107 event.setPackageName(mKeyboardView.getContext().getPackageName()); 108 event.setClassName(key.getClass().getName()); 109 event.setContentDescription(keyDescription); 110 event.setEnabled(true); 111 112 final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event); 113 record.setSource(mKeyboardView, virtualViewId); 114 115 return event; 116 } 117 118 /** 119 * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual 120 * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or 121 * the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}. 122 * <p> 123 * A virtual descendant is an imaginary View that is reported as a part of 124 * the view hierarchy for accessibility purposes. This enables custom views 125 * that draw complex content to report them selves as a tree of virtual 126 * views, thus conveying their logical structure. 127 * </p> 128 * <p> 129 * The implementer is responsible for obtaining an accessibility node info 130 * from the pool of reusable instances and setting the desired properties of 131 * the node info before returning it. 132 * </p> 133 * 134 * @param virtualViewId A client defined virtual view id. 135 * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual 136 * descendant or the host View. 137 * @see AccessibilityNodeInfoCompat 138 */ 139 @Override 140 public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) { 141 AccessibilityNodeInfoCompat info = null; 142 143 if (virtualViewId == UNDEFINED) { 144 return null; 145 } else if (virtualViewId == View.NO_ID) { 146 // We are requested to create an AccessibilityNodeInfo describing 147 // this View, i.e. the root of the virtual sub-tree. 148 info = AccessibilityNodeInfoCompat.obtain(mKeyboardView); 149 ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, info); 150 151 // Add the virtual children of the root View. 152 final Keyboard keyboard = mKeyboardView.getKeyboard(); 153 final Key[] keys = keyboard.mKeys; 154 for (Key key : keys) { 155 final int childVirtualViewId = generateVirtualViewIdForKey(key); 156 info.addChild(mKeyboardView, childVirtualViewId); 157 } 158 } else { 159 // Find the view that corresponds to the given id. 160 final Key key = mVirtualViewIdToKey.get(virtualViewId); 161 if (key == null) { 162 Log.e(TAG, "Invalid virtual view ID: " + virtualViewId); 163 return null; 164 } 165 166 final String keyDescription = getKeyDescription(key); 167 final Rect boundsInParent = key.mHitBox; 168 169 // Calculate the key's in-screen bounds. 170 mTempBoundsInScreen.set(boundsInParent); 171 mTempBoundsInScreen.offset(mParentLocation[0], mParentLocation[1]); 172 173 final Rect boundsInScreen = mTempBoundsInScreen; 174 175 // Obtain and initialize an AccessibilityNodeInfo with 176 // information about the virtual view. 177 info = AccessibilityNodeInfoCompat.obtain(); 178 info.setPackageName(mKeyboardView.getContext().getPackageName()); 179 info.setClassName(key.getClass().getName()); 180 info.setContentDescription(keyDescription); 181 info.setBoundsInParent(boundsInParent); 182 info.setBoundsInScreen(boundsInScreen); 183 info.setParent(mKeyboardView); 184 info.setSource(mKeyboardView, virtualViewId); 185 info.setBoundsInScreen(boundsInScreen); 186 info.setEnabled(true); 187 info.setClickable(true); 188 info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); 189 190 if (mAccessibilityFocusedView == virtualViewId) { 191 info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS); 192 } else { 193 info.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); 194 } 195 } 196 197 return info; 198 } 199 200 /** 201 * Simulates a key press by injecting touch events into the keyboard view. 202 * This avoids the complexity of trackers and listeners within the keyboard. 203 * 204 * @param key The key to press. 205 */ 206 void simulateKeyPress(Key key) { 207 final int x = key.mHitBox.centerX(); 208 final int y = key.mHitBox.centerY(); 209 final long downTime = SystemClock.uptimeMillis(); 210 final MotionEvent downEvent = MotionEvent.obtain( 211 downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0); 212 final MotionEvent upEvent = MotionEvent.obtain( 213 downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0); 214 215 mKeyboardView.onTouchEvent(downEvent); 216 mKeyboardView.onTouchEvent(upEvent); 217 } 218 219 @Override 220 public boolean performAction(int virtualViewId, int action, Bundle arguments) { 221 final Key key = mVirtualViewIdToKey.get(virtualViewId); 222 223 if (key == null) { 224 return false; 225 } 226 227 return performActionForKey(key, action, arguments); 228 } 229 230 /** 231 * Performs the specified accessibility action for the given key. 232 * 233 * @param key The on which to perform the action. 234 * @param action The action to perform. 235 * @param arguments The action's arguments. 236 * @return The result of performing the action, or false if the action is 237 * not supported. 238 */ 239 boolean performActionForKey(Key key, int action, Bundle arguments) { 240 final int virtualViewId = generateVirtualViewIdForKey(key); 241 242 switch (action) { 243 case AccessibilityNodeInfoCompat.ACTION_CLICK: 244 simulateKeyPress(key); 245 return true; 246 case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS: 247 if (mAccessibilityFocusedView == virtualViewId) { 248 return false; 249 } 250 mAccessibilityFocusedView = virtualViewId; 251 sendAccessibilityEventForKey( 252 key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 253 return true; 254 case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS: 255 if (mAccessibilityFocusedView != virtualViewId) { 256 return false; 257 } 258 mAccessibilityFocusedView = UNDEFINED; 259 sendAccessibilityEventForKey( 260 key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); 261 return true; 262 } 263 264 return false; 265 } 266 267 @Override 268 public AccessibilityNodeInfoCompat findAccessibilityFocus(int virtualViewId) { 269 return createAccessibilityNodeInfo(mAccessibilityFocusedView); 270 } 271 272 @Override 273 public AccessibilityNodeInfoCompat accessibilityFocusSearch(int direction, int virtualViewId) { 274 // Focus search is not currently supported for IMEs. 275 return null; 276 } 277 278 /** 279 * Sends an accessibility event for the given {@link Key}. 280 * 281 * @param key The key that's sending the event. 282 * @param eventType The type of event to send. 283 */ 284 void sendAccessibilityEventForKey(Key key, int eventType) { 285 final AccessibilityEvent event = createAccessibilityEvent(key, eventType); 286 mAccessibilityUtils.requestSendAccessibilityEvent(event); 287 } 288 289 /** 290 * Returns the context-specific description for a {@link Key}. 291 * 292 * @param key The key to describe. 293 * @return The context-specific description of the key. 294 */ 295 private String getKeyDescription(Key key) { 296 final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo(); 297 final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo); 298 final String keyDescription = mKeyCodeDescriptionMapper.getDescriptionForKey( 299 mKeyboardView.getContext(), mKeyboardView.getKeyboard(), key, shouldObscure); 300 301 return keyDescription; 302 } 303 304 /** 305 * Assigns virtual view IDs to keyboard keys and populates the related maps. 306 */ 307 private void assignVirtualViewIds() { 308 final Keyboard keyboard = mKeyboardView.getKeyboard(); 309 if (keyboard == null) { 310 return; 311 } 312 313 mVirtualViewIdToKey.clear(); 314 315 final Key[] keys = keyboard.mKeys; 316 for (Key key : keys) { 317 final int virtualViewId = generateVirtualViewIdForKey(key); 318 mVirtualViewIdToKey.put(virtualViewId, key); 319 } 320 } 321 322 /** 323 * Updates the parent's on-screen location. 324 */ 325 private void updateParentLocation() { 326 mKeyboardView.getLocationOnScreen(mParentLocation); 327 } 328 329 /** 330 * Generates a virtual view identifier for the given key. Returned 331 * identifiers are valid until the next global layout state change. 332 * 333 * @param key The key to identify. 334 * @return A virtual view identifier. 335 */ 336 private static int generateVirtualViewIdForKey(Key key) { 337 // The key x- and y-coordinates are stable between layout changes. 338 // Generate an identifier by bit-shifting the x-coordinate to the 339 // left-half of the integer and OR'ing with the y-coordinate. 340 return ((0xFFFF & key.mX) << (Integer.SIZE / 2)) | (0xFFFF & key.mY); 341 } 342} 343