KeyboardAccessibilityNodeProvider.java revision 7b90d2c432fd7ffbf0022fac9db921cf39197ac6
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.os.Bundle; 21import android.support.v4.view.ViewCompat; 22import android.support.v4.view.accessibility.AccessibilityEventCompat; 23import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 24import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; 25import android.support.v4.view.accessibility.AccessibilityRecordCompat; 26import android.util.Log; 27import android.view.View; 28import android.view.accessibility.AccessibilityEvent; 29import android.view.inputmethod.EditorInfo; 30 31import com.android.inputmethod.keyboard.Key; 32import com.android.inputmethod.keyboard.Keyboard; 33import com.android.inputmethod.keyboard.KeyboardView; 34import com.android.inputmethod.latin.settings.Settings; 35import com.android.inputmethod.latin.settings.SettingsValues; 36import com.android.inputmethod.latin.utils.CoordinateUtils; 37 38import java.util.List; 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 */ 50public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodeProviderCompat { 51 private static final String TAG = KeyboardAccessibilityNodeProvider.class.getSimpleName(); 52 private static final int UNDEFINED = Integer.MIN_VALUE; 53 54 private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper; 55 private final AccessibilityUtils mAccessibilityUtils; 56 57 /** Temporary rect used to calculate in-screen bounds. */ 58 private final Rect mTempBoundsInScreen = new Rect(); 59 60 /** The parent view's cached on-screen location. */ 61 private final int[] mParentLocation = CoordinateUtils.newInstance(); 62 63 /** The virtual view identifier for the focused node. */ 64 private int mAccessibilityFocusedView = UNDEFINED; 65 66 /** The current keyboard view. */ 67 private final KeyboardView mKeyboardView; 68 69 /** The current keyboard. */ 70 private Keyboard mKeyboard; 71 72 public KeyboardAccessibilityNodeProvider(final KeyboardView keyboardView) { 73 super(); 74 mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance(); 75 mAccessibilityUtils = AccessibilityUtils.getInstance(); 76 mKeyboardView = keyboardView; 77 updateParentLocation(); 78 79 // Since this class is constructed lazily, we might not get a subsequent 80 // call to setKeyboard() and therefore need to call it now. 81 setKeyboard(keyboardView.getKeyboard()); 82 } 83 84 /** 85 * Sets the keyboard represented by this node provider. 86 * 87 * @param keyboard The keyboard that is being set to the keyboard view. 88 */ 89 public void setKeyboard(final Keyboard keyboard) { 90 mKeyboard = keyboard; 91 } 92 93 private Key getKeyOf(final int virtualViewId) { 94 if (mKeyboard == null) { 95 return null; 96 } 97 final List<Key> sortedKeys = mKeyboard.getSortedKeys(); 98 // Use a virtual view id as an index of the sorted keys list. 99 if (virtualViewId >= 0 && virtualViewId < sortedKeys.size()) { 100 return sortedKeys.get(virtualViewId); 101 } 102 return null; 103 } 104 105 private int getVirtualViewIdOf(final Key key) { 106 if (mKeyboard == null) { 107 return View.NO_ID; 108 } 109 final List<Key> sortedKeys = mKeyboard.getSortedKeys(); 110 final int size = sortedKeys.size(); 111 for (int index = 0; index < size; index++) { 112 if (sortedKeys.get(index) == key) { 113 // Use an index of the sorted keys list as a virtual view id. 114 return index; 115 } 116 } 117 return View.NO_ID; 118 } 119 120 /** 121 * Creates and populates an {@link AccessibilityEvent} for the specified key 122 * and event type. 123 * 124 * @param key A key on the host keyboard view. 125 * @param eventType The event type to create. 126 * @return A populated {@link AccessibilityEvent} for the key. 127 * @see AccessibilityEvent 128 */ 129 public AccessibilityEvent createAccessibilityEvent(final Key key, final int eventType) { 130 final int virtualViewId = getVirtualViewIdOf(key); 131 final String keyDescription = getKeyDescription(key); 132 final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 133 event.setPackageName(mKeyboardView.getContext().getPackageName()); 134 event.setClassName(key.getClass().getName()); 135 event.setContentDescription(keyDescription); 136 event.setEnabled(true); 137 final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event); 138 record.setSource(mKeyboardView, virtualViewId); 139 return event; 140 } 141 142 /** 143 * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual 144 * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or 145 * the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}. 146 * <p> 147 * A virtual descendant is an imaginary View that is reported as a part of 148 * the view hierarchy for accessibility purposes. This enables custom views 149 * that draw complex content to report them selves as a tree of virtual 150 * views, thus conveying their logical structure. 151 * </p> 152 * <p> 153 * The implementer is responsible for obtaining an accessibility node info 154 * from the pool of reusable instances and setting the desired properties of 155 * the node info before returning it. 156 * </p> 157 * 158 * @param virtualViewId A client defined virtual view id. 159 * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual descendant or the host 160 * View. 161 * @see AccessibilityNodeInfoCompat 162 */ 163 @Override 164 public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(final int virtualViewId) { 165 if (virtualViewId == UNDEFINED) { 166 return null; 167 } 168 if (virtualViewId == View.NO_ID) { 169 // We are requested to create an AccessibilityNodeInfo describing 170 // this View, i.e. the root of the virtual sub-tree. 171 final AccessibilityNodeInfoCompat rootInfo = 172 AccessibilityNodeInfoCompat.obtain(mKeyboardView); 173 ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, rootInfo); 174 175 // Add the virtual children of the root View. 176 final List<Key> sortedKeys = mKeyboard.getSortedKeys(); 177 final int size = sortedKeys.size(); 178 for (int index = 0; index < size; index++) { 179 final Key key = sortedKeys.get(index); 180 if (key.isSpacer()) { 181 continue; 182 } 183 // Use an index of the sorted keys list as a virtual view id. 184 rootInfo.addChild(mKeyboardView, index); 185 } 186 return rootInfo; 187 } 188 189 // Find the key that corresponds to the given virtual view id. 190 final Key key = getKeyOf(virtualViewId); 191 if (key == null) { 192 Log.e(TAG, "Invalid virtual view ID: " + virtualViewId); 193 return null; 194 } 195 final String keyDescription = getKeyDescription(key); 196 final Rect boundsInParent = key.getHitBox(); 197 198 // Calculate the key's in-screen bounds. 199 mTempBoundsInScreen.set(boundsInParent); 200 mTempBoundsInScreen.offset( 201 CoordinateUtils.x(mParentLocation), CoordinateUtils.y(mParentLocation)); 202 final Rect boundsInScreen = mTempBoundsInScreen; 203 204 // Obtain and initialize an AccessibilityNodeInfo with information about the virtual view. 205 final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); 206 info.setPackageName(mKeyboardView.getContext().getPackageName()); 207 info.setClassName(key.getClass().getName()); 208 info.setContentDescription(keyDescription); 209 info.setBoundsInParent(boundsInParent); 210 info.setBoundsInScreen(boundsInScreen); 211 info.setParent(mKeyboardView); 212 info.setSource(mKeyboardView, virtualViewId); 213 info.setBoundsInScreen(boundsInScreen); 214 info.setEnabled(true); 215 info.setVisibleToUser(true); 216 217 if (mAccessibilityFocusedView == virtualViewId) { 218 info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS); 219 } else { 220 info.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); 221 } 222 return info; 223 } 224 225 @Override 226 public boolean performAction(final int virtualViewId, final int action, 227 final Bundle arguments) { 228 final Key key = getKeyOf(virtualViewId); 229 if (key == null) { 230 return false; 231 } 232 return performActionForKey(key, action, arguments); 233 } 234 235 /** 236 * Performs the specified accessibility action for the given key. 237 * 238 * @param key The on which to perform the action. 239 * @param action The action to perform. 240 * @param arguments The action's arguments. 241 * @return The result of performing the action, or false if the action is not supported. 242 */ 243 boolean performActionForKey(final Key key, final int action, final Bundle arguments) { 244 final int virtualViewId = getVirtualViewIdOf(key); 245 246 switch (action) { 247 case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS: 248 if (mAccessibilityFocusedView == virtualViewId) { 249 return false; 250 } 251 mAccessibilityFocusedView = virtualViewId; 252 sendAccessibilityEventForKey( 253 key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 254 return true; 255 case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS: 256 if (mAccessibilityFocusedView != virtualViewId) { 257 return false; 258 } 259 mAccessibilityFocusedView = UNDEFINED; 260 sendAccessibilityEventForKey( 261 key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); 262 return true; 263 default: 264 return false; 265 } 266 } 267 268 /** 269 * Sends an accessibility event for the given {@link Key}. 270 * 271 * @param key The key that's sending the event. 272 * @param eventType The type of event to send. 273 */ 274 void sendAccessibilityEventForKey(final Key key, final int eventType) { 275 final AccessibilityEvent event = createAccessibilityEvent(key, eventType); 276 mAccessibilityUtils.requestSendAccessibilityEvent(event); 277 } 278 279 /** 280 * Returns the context-specific description for a {@link Key}. 281 * 282 * @param key The key to describe. 283 * @return The context-specific description of the key. 284 */ 285 private String getKeyDescription(final Key key) { 286 final EditorInfo editorInfo = mKeyboard.mId.mEditorInfo; 287 final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo); 288 final SettingsValues currentSettings = Settings.getInstance().getCurrent(); 289 final String keyCodeDescription = mKeyCodeDescriptionMapper.getDescriptionForKey( 290 mKeyboardView.getContext(), mKeyboard, key, shouldObscure); 291 if (currentSettings.isWordSeparator(key.getCode())) { 292 return mAccessibilityUtils.getAutoCorrectionDescription( 293 keyCodeDescription, shouldObscure); 294 } else { 295 return keyCodeDescription; 296 } 297 } 298 299 /** 300 * Updates the parent's on-screen location. 301 */ 302 private void updateParentLocation() { 303 mKeyboardView.getLocationOnScreen(mParentLocation); 304 } 305} 306