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 17 package com.android.inputmethod.accessibility; 18 19 import android.graphics.Rect; 20 import android.inputmethodservice.InputMethodService; 21 import android.os.Bundle; 22 import android.os.SystemClock; 23 import android.support.v4.view.ViewCompat; 24 import android.support.v4.view.accessibility.AccessibilityEventCompat; 25 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 26 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; 27 import android.support.v4.view.accessibility.AccessibilityRecordCompat; 28 import android.util.Log; 29 import android.util.SparseArray; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.accessibility.AccessibilityEvent; 33 import android.view.inputmethod.EditorInfo; 34 35 import com.android.inputmethod.keyboard.Key; 36 import com.android.inputmethod.keyboard.Keyboard; 37 import 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 */ 49 public 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 updateParentLocation(); 89 90 // Since this class is constructed lazily, we might not get a subsequent 91 // call to setKeyboard() and therefore need to call it now. 92 setKeyboard(mKeyboardView.getKeyboard()); 93 } 94 95 /** 96 * Sets the keyboard represented by this node provider. 97 * 98 * @param keyboard The keyboard to represent. 99 */ 100 public void setKeyboard(Keyboard keyboard) { 101 assignVirtualViewIds(); 102 } 103 104 /** 105 * Creates and populates an {@link AccessibilityEvent} for the specified key 106 * and event type. 107 * 108 * @param key A key on the host keyboard view. 109 * @param eventType The event type to create. 110 * @return A populated {@link AccessibilityEvent} for the key. 111 * @see AccessibilityEvent 112 */ 113 public AccessibilityEvent createAccessibilityEvent(Key key, int eventType) { 114 final int virtualViewId = generateVirtualViewIdForKey(key); 115 final String keyDescription = getKeyDescription(key); 116 117 final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 118 event.setPackageName(mKeyboardView.getContext().getPackageName()); 119 event.setClassName(key.getClass().getName()); 120 event.setContentDescription(keyDescription); 121 event.setEnabled(true); 122 123 final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event); 124 record.setSource(mKeyboardView, virtualViewId); 125 126 return event; 127 } 128 129 /** 130 * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual 131 * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or 132 * the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}. 133 * <p> 134 * A virtual descendant is an imaginary View that is reported as a part of 135 * the view hierarchy for accessibility purposes. This enables custom views 136 * that draw complex content to report them selves as a tree of virtual 137 * views, thus conveying their logical structure. 138 * </p> 139 * <p> 140 * The implementer is responsible for obtaining an accessibility node info 141 * from the pool of reusable instances and setting the desired properties of 142 * the node info before returning it. 143 * </p> 144 * 145 * @param virtualViewId A client defined virtual view id. 146 * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual 147 * descendant or the host View. 148 * @see AccessibilityNodeInfoCompat 149 */ 150 @Override 151 public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) { 152 AccessibilityNodeInfoCompat info = null; 153 154 if (virtualViewId == UNDEFINED) { 155 return null; 156 } else if (virtualViewId == View.NO_ID) { 157 // We are requested to create an AccessibilityNodeInfo describing 158 // this View, i.e. the root of the virtual sub-tree. 159 info = AccessibilityNodeInfoCompat.obtain(mKeyboardView); 160 ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, info); 161 162 // Add the virtual children of the root View. 163 final Keyboard keyboard = mKeyboardView.getKeyboard(); 164 final Key[] keys = keyboard.mKeys; 165 for (Key key : keys) { 166 final int childVirtualViewId = generateVirtualViewIdForKey(key); 167 info.addChild(mKeyboardView, childVirtualViewId); 168 } 169 } else { 170 // Find the view that corresponds to the given id. 171 final Key key = mVirtualViewIdToKey.get(virtualViewId); 172 if (key == null) { 173 Log.e(TAG, "Invalid virtual view ID: " + virtualViewId); 174 return null; 175 } 176 177 final String keyDescription = getKeyDescription(key); 178 final Rect boundsInParent = key.mHitBox; 179 180 // Calculate the key's in-screen bounds. 181 mTempBoundsInScreen.set(boundsInParent); 182 mTempBoundsInScreen.offset(mParentLocation[0], mParentLocation[1]); 183 184 final Rect boundsInScreen = mTempBoundsInScreen; 185 186 // Obtain and initialize an AccessibilityNodeInfo with 187 // information about the virtual view. 188 info = AccessibilityNodeInfoCompat.obtain(); 189 info.setPackageName(mKeyboardView.getContext().getPackageName()); 190 info.setClassName(key.getClass().getName()); 191 info.setContentDescription(keyDescription); 192 info.setBoundsInParent(boundsInParent); 193 info.setBoundsInScreen(boundsInScreen); 194 info.setParent(mKeyboardView); 195 info.setSource(mKeyboardView, virtualViewId); 196 info.setBoundsInScreen(boundsInScreen); 197 info.setEnabled(true); 198 info.setClickable(true); 199 info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); 200 201 if (mAccessibilityFocusedView == virtualViewId) { 202 info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS); 203 } else { 204 info.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); 205 } 206 } 207 208 return info; 209 } 210 211 /** 212 * Simulates a key press by injecting touch events into the keyboard view. 213 * This avoids the complexity of trackers and listeners within the keyboard. 214 * 215 * @param key The key to press. 216 */ 217 void simulateKeyPress(Key key) { 218 final int x = key.mHitBox.centerX(); 219 final int y = key.mHitBox.centerY(); 220 final long downTime = SystemClock.uptimeMillis(); 221 final MotionEvent downEvent = MotionEvent.obtain( 222 downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0); 223 final MotionEvent upEvent = MotionEvent.obtain( 224 downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0); 225 226 mKeyboardView.onTouchEvent(downEvent); 227 mKeyboardView.onTouchEvent(upEvent); 228 } 229 230 @Override 231 public boolean performAction(int virtualViewId, int action, Bundle arguments) { 232 final Key key = mVirtualViewIdToKey.get(virtualViewId); 233 234 if (key == null) { 235 return false; 236 } 237 238 return performActionForKey(key, action, arguments); 239 } 240 241 /** 242 * Performs the specified accessibility action for the given key. 243 * 244 * @param key The on which to perform the action. 245 * @param action The action to perform. 246 * @param arguments The action's arguments. 247 * @return The result of performing the action, or false if the action is 248 * not supported. 249 */ 250 boolean performActionForKey(Key key, int action, Bundle arguments) { 251 final int virtualViewId = generateVirtualViewIdForKey(key); 252 253 switch (action) { 254 case AccessibilityNodeInfoCompat.ACTION_CLICK: 255 simulateKeyPress(key); 256 return true; 257 case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS: 258 if (mAccessibilityFocusedView == virtualViewId) { 259 return false; 260 } 261 mAccessibilityFocusedView = virtualViewId; 262 sendAccessibilityEventForKey( 263 key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 264 return true; 265 case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS: 266 if (mAccessibilityFocusedView != virtualViewId) { 267 return false; 268 } 269 mAccessibilityFocusedView = UNDEFINED; 270 sendAccessibilityEventForKey( 271 key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); 272 return true; 273 } 274 275 return false; 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