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 import com.android.inputmethod.latin.CollectionUtils; 39 40 /** 41 * Exposes a virtual view sub-tree for {@link KeyboardView} and generates 42 * {@link AccessibilityEvent}s for individual {@link Key}s. 43 * <p> 44 * A virtual sub-tree is composed of imaginary {@link View}s that are reported 45 * as a part of the view hierarchy for accessibility purposes. This enables 46 * custom views that draw complex content to report them selves as a tree of 47 * virtual views, thus conveying their logical structure. 48 * </p> 49 */ 50 public final class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat { 51 private static final String TAG = AccessibilityEntityProvider.class.getSimpleName(); 52 private static final int UNDEFINED = Integer.MIN_VALUE; 53 54 private final InputMethodService mInputMethodService; 55 private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper; 56 private final AccessibilityUtils mAccessibilityUtils; 57 58 /** A map of integer IDs to {@link Key}s. */ 59 private final SparseArray<Key> mVirtualViewIdToKey = CollectionUtils.newSparseArray(); 60 61 /** Temporary rect used to calculate in-screen bounds. */ 62 private final Rect mTempBoundsInScreen = new Rect(); 63 64 /** The parent view's cached on-screen location. */ 65 private final int[] mParentLocation = new int[2]; 66 67 /** The virtual view identifier for the focused node. */ 68 private int mAccessibilityFocusedView = UNDEFINED; 69 70 /** The current keyboard view. */ 71 private KeyboardView mKeyboardView; 72 73 public AccessibilityEntityProvider(KeyboardView keyboardView, InputMethodService inputMethod) { 74 mInputMethodService = inputMethod; 75 76 mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance(); 77 mAccessibilityUtils = AccessibilityUtils.getInstance(); 78 79 setView(keyboardView); 80 } 81 82 /** 83 * Sets the keyboard view represented by this node provider. 84 * 85 * @param keyboardView The keyboard view to represent. 86 */ 87 public void setView(KeyboardView keyboardView) { 88 mKeyboardView = keyboardView; 89 updateParentLocation(); 90 91 // Since this class is constructed lazily, we might not get a subsequent 92 // call to setKeyboard() and therefore need to call it now. 93 setKeyboard(mKeyboardView.getKeyboard()); 94 } 95 96 /** 97 * Sets the keyboard represented by this node provider. 98 * 99 * @param keyboard The keyboard to represent. 100 */ 101 public void setKeyboard(Keyboard keyboard) { 102 assignVirtualViewIds(); 103 } 104 105 /** 106 * Creates and populates an {@link AccessibilityEvent} for the specified key 107 * and event type. 108 * 109 * @param key A key on the host keyboard view. 110 * @param eventType The event type to create. 111 * @return A populated {@link AccessibilityEvent} for the key. 112 * @see AccessibilityEvent 113 */ 114 public AccessibilityEvent createAccessibilityEvent(Key key, int eventType) { 115 final int virtualViewId = generateVirtualViewIdForKey(key); 116 final String keyDescription = getKeyDescription(key); 117 118 final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 119 event.setPackageName(mKeyboardView.getContext().getPackageName()); 120 event.setClassName(key.getClass().getName()); 121 event.setContentDescription(keyDescription); 122 event.setEnabled(true); 123 124 final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event); 125 record.setSource(mKeyboardView, virtualViewId); 126 127 return event; 128 } 129 130 /** 131 * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual 132 * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or 133 * the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}. 134 * <p> 135 * A virtual descendant is an imaginary View that is reported as a part of 136 * the view hierarchy for accessibility purposes. This enables custom views 137 * that draw complex content to report them selves as a tree of virtual 138 * views, thus conveying their logical structure. 139 * </p> 140 * <p> 141 * The implementer is responsible for obtaining an accessibility node info 142 * from the pool of reusable instances and setting the desired properties of 143 * the node info before returning it. 144 * </p> 145 * 146 * @param virtualViewId A client defined virtual view id. 147 * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual 148 * descendant or the host View. 149 * @see AccessibilityNodeInfoCompat 150 */ 151 @Override 152 public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) { 153 AccessibilityNodeInfoCompat info = null; 154 155 if (virtualViewId == UNDEFINED) { 156 return null; 157 } else if (virtualViewId == View.NO_ID) { 158 // We are requested to create an AccessibilityNodeInfo describing 159 // this View, i.e. the root of the virtual sub-tree. 160 info = AccessibilityNodeInfoCompat.obtain(mKeyboardView); 161 ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, info); 162 163 // Add the virtual children of the root View. 164 final Keyboard keyboard = mKeyboardView.getKeyboard(); 165 final Key[] keys = keyboard.mKeys; 166 for (Key key : keys) { 167 final int childVirtualViewId = generateVirtualViewIdForKey(key); 168 info.addChild(mKeyboardView, childVirtualViewId); 169 } 170 } else { 171 // Find the view that corresponds to the given id. 172 final Key key = mVirtualViewIdToKey.get(virtualViewId); 173 if (key == null) { 174 Log.e(TAG, "Invalid virtual view ID: " + virtualViewId); 175 return null; 176 } 177 178 final String keyDescription = getKeyDescription(key); 179 final Rect boundsInParent = key.mHitBox; 180 181 // Calculate the key's in-screen bounds. 182 mTempBoundsInScreen.set(boundsInParent); 183 mTempBoundsInScreen.offset(mParentLocation[0], mParentLocation[1]); 184 185 final Rect boundsInScreen = mTempBoundsInScreen; 186 187 // Obtain and initialize an AccessibilityNodeInfo with 188 // information about the virtual view. 189 info = AccessibilityNodeInfoCompat.obtain(); 190 info.setPackageName(mKeyboardView.getContext().getPackageName()); 191 info.setClassName(key.getClass().getName()); 192 info.setContentDescription(keyDescription); 193 info.setBoundsInParent(boundsInParent); 194 info.setBoundsInScreen(boundsInScreen); 195 info.setParent(mKeyboardView); 196 info.setSource(mKeyboardView, virtualViewId); 197 info.setBoundsInScreen(boundsInScreen); 198 info.setEnabled(true); 199 info.setVisibleToUser(true); 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 downEvent.recycle(); 230 upEvent.recycle(); 231 } 232 233 @Override 234 public boolean performAction(int virtualViewId, int action, Bundle arguments) { 235 final Key key = mVirtualViewIdToKey.get(virtualViewId); 236 237 if (key == null) { 238 return false; 239 } 240 241 return performActionForKey(key, action, arguments); 242 } 243 244 /** 245 * Performs the specified accessibility action for the given key. 246 * 247 * @param key The on which to perform the action. 248 * @param action The action to perform. 249 * @param arguments The action's arguments. 250 * @return The result of performing the action, or false if the action is 251 * not supported. 252 */ 253 boolean performActionForKey(Key key, int action, Bundle arguments) { 254 final int virtualViewId = generateVirtualViewIdForKey(key); 255 256 switch (action) { 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