ExploreByTouchHelper.java revision 6eb3cdf42d5382aef6b6a6afd7c305dbc27885b9
1/* 2 * Copyright (C) 2013 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 android.support.v4.widget; 18 19import android.content.Context; 20import android.graphics.Rect; 21import android.os.Bundle; 22import android.support.v4.view.AccessibilityDelegateCompat; 23import android.support.v4.view.MotionEventCompat; 24import android.support.v4.view.ViewCompat; 25import android.support.v4.view.ViewParentCompat; 26import android.support.v4.view.accessibility.AccessibilityEventCompat; 27import android.support.v4.view.accessibility.AccessibilityManagerCompat; 28import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 29import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; 30import android.support.v4.view.accessibility.AccessibilityRecordCompat; 31import android.view.MotionEvent; 32import android.view.View; 33import android.view.ViewParent; 34import android.view.accessibility.AccessibilityEvent; 35import android.view.accessibility.AccessibilityManager; 36 37import java.util.LinkedList; 38import java.util.List; 39 40/** 41 * ExploreByTouchHelper is a utility class for implementing accessibility 42 * support in custom {@link View}s that represent a collection of View-like 43 * logical items. It extends {@link AccessibilityNodeProviderCompat} and 44 * simplifies many aspects of providing information to accessibility services 45 * and managing accessibility focus. This class does not currently support 46 * hierarchies of logical items. 47 * <p> 48 * This should be applied to the parent view using 49 * {@link ViewCompat#setAccessibilityDelegate}: 50 * 51 * <pre> 52 * mAccessHelper = ExploreByTouchHelper.create(someView, mAccessHelperCallback); 53 * ViewCompat.setAccessibilityDelegate(someView, mAccessHelper); 54 * </pre> 55 */ 56public abstract class ExploreByTouchHelper extends AccessibilityDelegateCompat { 57 /** Virtual node identifier value for invalid nodes. */ 58 public static final int INVALID_ID = Integer.MIN_VALUE; 59 60 /** Default class name used for virtual views. */ 61 private static final String DEFAULT_CLASS_NAME = View.class.getName(); 62 63 // Temporary, reusable data structures. 64 private final Rect mTempScreenRect = new Rect(); 65 private final Rect mTempParentRect = new Rect(); 66 private final Rect mTempVisibleRect = new Rect(); 67 private final int[] mTempGlobalRect = new int[2]; 68 69 /** System accessibility manager, used to check state and send events. */ 70 private final AccessibilityManager mManager; 71 72 /** View whose internal structure is exposed through this helper. */ 73 private final View mView; 74 75 /** Node provider that handles creating nodes and performing actions. */ 76 private ExploreByTouchNodeProvider mNodeProvider; 77 78 /** Virtual view id for the currently focused logical item. */ 79 private int mFocusedVirtualViewId = INVALID_ID; 80 81 /** Virtual view id for the currently hovered logical item. */ 82 private int mHoveredVirtualViewId = INVALID_ID; 83 84 /** 85 * Factory method to create a new {@link ExploreByTouchHelper}. 86 * 87 * @param forView View whose logical children are exposed by this helper. 88 */ 89 public ExploreByTouchHelper(View forView) { 90 if (forView == null) { 91 throw new IllegalArgumentException("View may not be null"); 92 } 93 94 mView = forView; 95 final Context context = forView.getContext(); 96 mManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); 97 } 98 99 /** 100 * Returns the {@link AccessibilityNodeProviderCompat} for this helper. 101 * 102 * @param host View whose logical children are exposed by this helper. 103 * @return The accessibility node provider for this helper. 104 */ 105 @Override 106 public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) { 107 if (mNodeProvider == null) { 108 mNodeProvider = new ExploreByTouchNodeProvider(); 109 } 110 return mNodeProvider; 111 } 112 113 /** 114 * Dispatches hover {@link MotionEvent}s to the virtual view hierarchy when 115 * the Explore by Touch feature is enabled. 116 * <p> 117 * This method should be called by overriding 118 * {@link View#dispatchHoverEvent}: 119 * 120 * <pre>@Override 121 * public boolean dispatchHoverEvent(MotionEvent event) { 122 * if (mHelper.dispatchHoverEvent(this, event) { 123 * return true; 124 * } 125 * return super.dispatchHoverEvent(event); 126 * } 127 * </pre> 128 * 129 * @param event The hover event to dispatch to the virtual view hierarchy. 130 * @return Whether the hover event was handled. 131 */ 132 public boolean dispatchHoverEvent(MotionEvent event) { 133 if (!mManager.isEnabled() 134 || !AccessibilityManagerCompat.isTouchExplorationEnabled(mManager)) { 135 return false; 136 } 137 138 switch (event.getAction()) { 139 case MotionEventCompat.ACTION_HOVER_MOVE: 140 case MotionEventCompat.ACTION_HOVER_ENTER: 141 final int virtualViewId = getVirtualViewAt(event.getX(), event.getY()); 142 updateHoveredVirtualView(virtualViewId); 143 return (virtualViewId != INVALID_ID); 144 case MotionEventCompat.ACTION_HOVER_EXIT: 145 if (mFocusedVirtualViewId != INVALID_ID) { 146 updateHoveredVirtualView(INVALID_ID); 147 return true; 148 } 149 return false; 150 default: 151 return false; 152 } 153 } 154 155 /** 156 * Populates an event of the specified type with information about an item 157 * and attempts to send it up through the view hierarchy. 158 * <p> 159 * You should call this method after performing a user action that normally 160 * fires an accessibility event, such as clicking on an item. 161 * 162 * <pre>public void performItemClick(T item) { 163 * ... 164 * sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED); 165 * } 166 * </pre> 167 * 168 * @param virtualViewId The virtual view id for which to send an event. 169 * @param eventType The type of event to send. 170 * @return true if the event was sent successfully. 171 */ 172 public boolean sendEventForVirtualView(int virtualViewId, int eventType) { 173 if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) { 174 return false; 175 } 176 177 final ViewParent parent = mView.getParent(); 178 if (parent == null) { 179 return false; 180 } 181 182 final AccessibilityEvent event = createEvent(virtualViewId, eventType); 183 return ViewParentCompat.requestSendAccessibilityEvent(parent, mView, event); 184 } 185 186 /** 187 * Notifies the accessibility framework that the properties of the parent 188 * view have changed. 189 * <p> 190 * You <b>must</b> call this method after adding or removing items from the 191 * parent view. 192 */ 193 public void invalidateRoot() { 194 invalidateVirtualView(View.NO_ID); 195 } 196 197 /** 198 * Notifies the accessibility framework that the properties of a particular 199 * item have changed. 200 * <p> 201 * You <b>must</b> call this method after changing any of the properties set 202 * in {@link #onPopulateNodeForVirtualView}. 203 * 204 * @param virtualViewId The virtual view id to invalidate. 205 */ 206 public void invalidateVirtualView(int virtualViewId) { 207 sendEventForVirtualView( 208 virtualViewId, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED); 209 } 210 211 /** 212 * Sets the currently hovered item, sending hover accessibility events as 213 * necessary to maintain the correct state. 214 * 215 * @param virtualViewId The virtual view id for the item currently being 216 * hovered, or {@link #INVALID_ID} if no item is hovered within 217 * the parent view. 218 */ 219 private void updateHoveredVirtualView(int virtualViewId) { 220 if (mHoveredVirtualViewId == virtualViewId) { 221 return; 222 } 223 224 final int previousVirtualViewId = mHoveredVirtualViewId; 225 mHoveredVirtualViewId = virtualViewId; 226 227 // Stay consistent with framework behavior by sending ENTER/EXIT pairs 228 // in reverse order. This is accurate as of API 18. 229 sendEventForVirtualView(virtualViewId, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER); 230 sendEventForVirtualView( 231 previousVirtualViewId, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT); 232 } 233 234 /** 235 * Constructs and returns an {@link AccessibilityEvent} for the specified 236 * virtual view id, which includes the host view ({@link View#NO_ID}). 237 * 238 * @param virtualViewId The virtual view id for the item for which to 239 * construct an event. 240 * @param eventType The type of event to construct. 241 * @return An {@link AccessibilityEvent} populated with information about 242 * the specified item. 243 */ 244 private AccessibilityEvent createEvent(int virtualViewId, int eventType) { 245 switch (virtualViewId) { 246 case View.NO_ID: 247 return createEventForHost(eventType); 248 default: 249 return createEventForChild(virtualViewId, eventType); 250 } 251 } 252 253 /** 254 * Constructs and returns an {@link AccessibilityEvent} for the host node. 255 * 256 * @param eventType The type of event to construct. 257 * @return An {@link AccessibilityEvent} populated with information about 258 * the specified item. 259 */ 260 private AccessibilityEvent createEventForHost(int eventType) { 261 final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 262 ViewCompat.onInitializeAccessibilityEvent(mView, event); 263 return event; 264 } 265 266 /** 267 * Constructs and returns an {@link AccessibilityEvent} populated with 268 * information about the specified item. 269 * 270 * @param virtualViewId The virtual view id for the item for which to 271 * construct an event. 272 * @param eventType The type of event to construct. 273 * @return An {@link AccessibilityEvent} populated with information about 274 * the specified item. 275 */ 276 private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) { 277 final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 278 event.setEnabled(true); 279 event.setClassName(DEFAULT_CLASS_NAME); 280 281 // Allow the client to populate the event. 282 onPopulateEventForVirtualView(virtualViewId, event); 283 284 // Make sure the developer is following the rules. 285 if (event.getText().isEmpty() && (event.getContentDescription() == null)) { 286 throw new RuntimeException("Callbacks must add text or a content description in " 287 + "populateEventForVirtualViewId()"); 288 } 289 290 // Don't allow the client to override these properties. 291 event.setPackageName(mView.getContext().getPackageName()); 292 293 final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event); 294 record.setSource(mView, virtualViewId); 295 296 return event; 297 } 298 299 /** 300 * Constructs and returns an {@link AccessibilityNodeInfoCompat} for the 301 * specified virtual view id, which includes the host view 302 * ({@link View#NO_ID}). 303 * 304 * @param virtualViewId The virtual view id for the item for which to 305 * construct a node. 306 * @return An {@link AccessibilityNodeInfoCompat} populated with information 307 * about the specified item. 308 */ 309 private AccessibilityNodeInfoCompat createNode(int virtualViewId) { 310 switch (virtualViewId) { 311 case View.NO_ID: 312 return createNodeForHost(); 313 default: 314 return createNodeForChild(virtualViewId); 315 } 316 } 317 318 /** 319 * Constructs and returns an {@link AccessibilityNodeInfoCompat} for the 320 * host view populated with its virtual descendants. 321 * 322 * @return An {@link AccessibilityNodeInfoCompat} for the parent node. 323 */ 324 private AccessibilityNodeInfoCompat createNodeForHost() { 325 final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain(mView); 326 ViewCompat.onInitializeAccessibilityNodeInfo(mView, node); 327 328 // Add the virtual descendants. 329 final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>(); 330 getVisibleVirtualViews(virtualViewIds); 331 332 for (Integer childVirtualViewId : virtualViewIds) { 333 node.addChild(mView, childVirtualViewId); 334 } 335 336 return node; 337 } 338 339 /** 340 * Constructs and returns an {@link AccessibilityNodeInfoCompat} for the 341 * specified item. Automatically manages accessibility focus actions. 342 * <p> 343 * Allows the implementing class to specify most node properties, but 344 * overrides the following: 345 * <ul> 346 * <li>{@link AccessibilityNodeInfoCompat#setPackageName} 347 * <li>{@link AccessibilityNodeInfoCompat#setClassName} 348 * <li>{@link AccessibilityNodeInfoCompat#setParent(View)} 349 * <li>{@link AccessibilityNodeInfoCompat#setSource(View, int)} 350 * <li>{@link AccessibilityNodeInfoCompat#setVisibleToUser} 351 * <li>{@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)} 352 * </ul> 353 * <p> 354 * Uses the bounds of the parent view and the parent-relative bounding 355 * rectangle specified by 356 * {@link AccessibilityNodeInfoCompat#getBoundsInParent} to automatically 357 * update the following properties: 358 * <ul> 359 * <li>{@link AccessibilityNodeInfoCompat#setVisibleToUser} 360 * <li>{@link AccessibilityNodeInfoCompat#setBoundsInParent} 361 * </ul> 362 * 363 * @param virtualViewId The virtual view id for item for which to construct 364 * a node. 365 * @return An {@link AccessibilityNodeInfoCompat} for the specified item. 366 */ 367 private AccessibilityNodeInfoCompat createNodeForChild(int virtualViewId) { 368 final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain(); 369 370 // Ensure the client has good defaults. 371 node.setEnabled(true); 372 node.setClassName(DEFAULT_CLASS_NAME); 373 374 // Allow the client to populate the node. 375 onPopulateNodeForVirtualView(virtualViewId, node); 376 377 // Make sure the developer is following the rules. 378 if ((node.getText() == null) && (node.getContentDescription() == null)) { 379 throw new RuntimeException("Callbacks must add text or a content description in " 380 + "populateNodeForVirtualViewId()"); 381 } 382 383 node.getBoundsInParent(mTempParentRect); 384 if (mTempParentRect.isEmpty()) { 385 throw new RuntimeException("Callbacks must set parent bounds in " 386 + "populateNodeForVirtualViewId()"); 387 } 388 389 final int actions = node.getActions(); 390 if ((actions & AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS) != 0) { 391 throw new RuntimeException("Callbacks must not add ACTION_ACCESSIBILITY_FOCUS in " 392 + "populateNodeForVirtualViewId()"); 393 } 394 if ((actions & AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS) != 0) { 395 throw new RuntimeException("Callbacks must not add ACTION_CLEAR_ACCESSIBILITY_FOCUS in " 396 + "populateNodeForVirtualViewId()"); 397 } 398 399 // Don't allow the client to override these properties. 400 node.setPackageName(mView.getContext().getPackageName()); 401 node.setSource(mView, virtualViewId); 402 node.setParent(mView); 403 404 // Manage internal accessibility focus state. 405 if (mFocusedVirtualViewId == virtualViewId) { 406 node.setAccessibilityFocused(true); 407 node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS); 408 } else { 409 node.setAccessibilityFocused(false); 410 node.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); 411 } 412 413 // Set the visibility based on the parent bound. 414 if (intersectVisibleToUser(mTempParentRect)) { 415 node.setVisibleToUser(true); 416 node.setBoundsInParent(mTempParentRect); 417 } 418 419 // Calculate screen-relative bound. 420 mView.getLocationOnScreen(mTempGlobalRect); 421 final int offsetX = mTempGlobalRect[0]; 422 final int offsetY = mTempGlobalRect[1]; 423 mTempScreenRect.set(mTempParentRect); 424 mTempScreenRect.offset(offsetX, offsetY); 425 node.setBoundsInScreen(mTempScreenRect); 426 427 return node; 428 } 429 430 private boolean performAction(int virtualViewId, int action, Bundle arguments) { 431 switch (virtualViewId) { 432 case View.NO_ID: 433 return performActionForHost(action, arguments); 434 default: 435 return performActionForChild(virtualViewId, action, arguments); 436 } 437 } 438 439 private boolean performActionForHost(int action, Bundle arguments) { 440 return ViewCompat.performAccessibilityAction(mView, action, arguments); 441 } 442 443 private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) { 444 switch (action) { 445 case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS: 446 case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS: 447 return manageFocusForChild(virtualViewId, action, arguments); 448 default: 449 return onPerformActionForVirtualView(virtualViewId, action, arguments); 450 } 451 } 452 453 private boolean manageFocusForChild(int virtualViewId, int action, Bundle arguments) { 454 switch (action) { 455 case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS: 456 if (!isAccessibilityFocused(virtualViewId)) { 457 return requestAccessibilityFocus(virtualViewId); 458 } 459 return false; 460 case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS: 461 if (isAccessibilityFocused(virtualViewId)) { 462 clearAccessibilityFocus(virtualViewId); 463 return true; 464 } 465 return false; 466 default: 467 return false; 468 } 469 } 470 471 /** 472 * Computes whether the specified {@link Rect} intersects with the visible 473 * portion of its parent {@link View}. Modifies {@code localRect} to contain 474 * only the visible portion. 475 * 476 * @param localRect A rectangle in local (parent) coordinates. 477 * @return Whether the specified {@link Rect} is visible on the screen. 478 */ 479 private boolean intersectVisibleToUser(Rect localRect) { 480 // Missing or empty bounds mean this view is not visible. 481 if ((localRect == null) || localRect.isEmpty()) { 482 return false; 483 } 484 485 // Attached to invisible window means this view is not visible. 486 if (mView.getWindowVisibility() != View.VISIBLE) { 487 return false; 488 } 489 490 // An invisible predecessor means that this view is not visible. 491 Object viewParent = this; 492 while (viewParent instanceof View) { 493 final View view = (View) viewParent; 494 if ((ViewCompat.getAlpha(view) <= 0) || (view.getVisibility() != View.VISIBLE)) { 495 return false; 496 } 497 viewParent = view.getParent(); 498 } 499 500 // A null parent implies the view is not visible. 501 if (viewParent == null) { 502 return false; 503 } 504 505 // If no portion of the parent is visible, this view is not visible. 506 if (!mView.getLocalVisibleRect(mTempVisibleRect)) { 507 return false; 508 } 509 510 // Check if the view intersects the visible portion of the parent. 511 return localRect.intersect(mTempVisibleRect); 512 } 513 514 /** 515 * Returns whether this virtual view is accessibility focused. 516 * 517 * @return True if the view is accessibility focused. 518 */ 519 private boolean isAccessibilityFocused(int virtualViewId) { 520 return (mFocusedVirtualViewId == virtualViewId); 521 } 522 523 /** 524 * Attempts to give accessibility focus to a virtual view. 525 * <p> 526 * A virtual view will not actually take focus if 527 * {@link AccessibilityManager#isEnabled()} returns false, 528 * {@link AccessibilityManager#isTouchExplorationEnabled()} returns false, 529 * or the view already has accessibility focus. 530 * 531 * @param virtualViewId The id of the virtual view on which to place 532 * accessibility focus. 533 * @return Whether this virtual view actually took accessibility focus. 534 */ 535 private boolean requestAccessibilityFocus(int virtualViewId) { 536 if (!mManager.isEnabled() 537 || !AccessibilityManagerCompat.isTouchExplorationEnabled(mManager)) { 538 return false; 539 } 540 // TODO: Check virtual view visibility. 541 if (!isAccessibilityFocused(virtualViewId)) { 542 mFocusedVirtualViewId = virtualViewId; 543 // TODO: Only invalidate virtual view bounds. 544 mView.invalidate(); 545 sendEventForVirtualView(virtualViewId, 546 AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 547 notifyAccessibilityStateChanged(virtualViewId); 548 return true; 549 } 550 return false; 551 } 552 553 /** 554 * Attempts to clear accessibility focus from a virtual view. 555 */ 556 private void clearAccessibilityFocus(int virtualViewId) { 557 if (isAccessibilityFocused(virtualViewId)) { 558 mFocusedVirtualViewId = INVALID_ID; 559 mView.invalidate(); 560 sendEventForVirtualView(virtualViewId, 561 AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); 562 notifyAccessibilityStateChanged(virtualViewId); 563 } 564 } 565 566 private void notifyAccessibilityStateChanged(int virtualViewId) { 567 // TODO: This method is not visible. 568 } 569 570 /** 571 * Provides a mapping between view-relative coordinates and logical 572 * items. 573 * 574 * @param x The view-relative x coordinate 575 * @param y The view-relative y coordinate 576 * @return virtual view identifier for the logical item under 577 * coordinates (x,y) 578 */ 579 protected abstract int getVirtualViewAt(float x, float y); 580 581 /** 582 * Populates a list with the view's visible items. The ordering of items 583 * within {@code virtualViewIds} specifies order of accessibility focus 584 * traversal. 585 * 586 * @param virtualViewIds The list to populate with visible items 587 */ 588 protected abstract void getVisibleVirtualViews(List<Integer> virtualViewIds); 589 590 /** 591 * Populates an {@link AccessibilityEvent} with information about the 592 * specified item. 593 * <p> 594 * Implementations <b>must</b> populate the following required fields: 595 * <ul> 596 * <li>event text, see {@link AccessibilityEvent#getText} or 597 * {@link AccessibilityEvent#setContentDescription} 598 * </ul> 599 * <p> 600 * The helper class automatically populates the following fields with 601 * default values, but implementations may optionally override them: 602 * <ul> 603 * <li>item class name, set to android.view.View, see 604 * {@link AccessibilityEvent#setClassName} 605 * </ul> 606 * <p> 607 * The following required fields are automatically populated by the 608 * helper class and may not be overridden: 609 * <ul> 610 * <li>package name, set to the package of the host view's 611 * {@link Context}, see {@link AccessibilityEvent#setPackageName} 612 * <li>event source, set to the host view and virtual view identifier, 613 * see {@link AccessibilityRecordCompat#setSource(View, int)} 614 * </ul> 615 * 616 * @param virtualViewId The virtual view id for the item for which to 617 * populate the event 618 * @param event The event to populate 619 */ 620 protected abstract void onPopulateEventForVirtualView( 621 int virtualViewId, AccessibilityEvent event); 622 623 /** 624 * Populates an {@link AccessibilityNodeInfoCompat} with information 625 * about the specified item. 626 * <p> 627 * Implementations <b>must</b> populate the following required fields: 628 * <ul> 629 * <li>event text, see {@link AccessibilityNodeInfoCompat#setText} or 630 * {@link AccessibilityNodeInfoCompat#setContentDescription} 631 * <li>bounds in parent coordinates, see 632 * {@link AccessibilityNodeInfoCompat#setBoundsInParent} 633 * </ul> 634 * <p> 635 * The helper class automatically populates the following fields with 636 * default values, but implementations may optionally override them: 637 * <ul> 638 * <li>enabled state, set to true, see 639 * {@link AccessibilityNodeInfoCompat#setEnabled} 640 * <li>item class name, identical to the class name set by 641 * {@link #onPopulateEventForVirtualView}, see 642 * {@link AccessibilityNodeInfoCompat#setClassName} 643 * </ul> 644 * <p> 645 * The following required fields are automatically populated by the 646 * helper class and may not be overridden: 647 * <ul> 648 * <li>package name, identical to the package name set by 649 * {@link #onPopulateEventForVirtualView}, see 650 * {@link AccessibilityNodeInfoCompat#setPackageName} 651 * <li>node source, identical to the event source set in 652 * {@link #onPopulateEventForVirtualView}, see 653 * {@link AccessibilityNodeInfoCompat#setSource(View, int)} 654 * <li>parent view, set to the host view, see 655 * {@link AccessibilityNodeInfoCompat#setParent(View)} 656 * <li>visibility, computed based on parent-relative bounds, see 657 * {@link AccessibilityNodeInfoCompat#setVisibleToUser} 658 * <li>accessibility focus, computed based on internal helper state, see 659 * {@link AccessibilityNodeInfoCompat#setAccessibilityFocused} 660 * <li>bounds in screen coordinates, computed based on host view bounds, 661 * see {@link AccessibilityNodeInfoCompat#setBoundsInScreen} 662 * </ul> 663 * <p> 664 * Additionally, the helper class automatically handles accessibility 665 * focus management by adding the appropriate 666 * {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS} or 667 * {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_ACCESSIBILITY_FOCUS} 668 * action. Implementations must <b>never</b> manually add these actions. 669 * <p> 670 * The helper class also automatically modifies parent- and 671 * screen-relative bounds to reflect the portion of the item visible 672 * within its parent. 673 * 674 * @param virtualViewId The virtual view identifier of the item for 675 * which to populate the node 676 * @param node The node to populate 677 */ 678 protected abstract void onPopulateNodeForVirtualView( 679 int virtualViewId, AccessibilityNodeInfoCompat node); 680 681 /** 682 * Performs the specified accessibility action on the item associated 683 * with the virtual view identifier. See 684 * {@link AccessibilityNodeInfoCompat#performAction(int, Bundle)} for 685 * more information. 686 * <p> 687 * Implementations <b>must</b> handle any actions added manually in 688 * {@link #onPopulateNodeForVirtualView}. 689 * <p> 690 * The helper class automatically handles focus management resulting 691 * from {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS} 692 * and 693 * {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_ACCESSIBILITY_FOCUS} 694 * actions. 695 * 696 * @param virtualViewId The virtual view identifier of the item on which 697 * to perform the action 698 * @param action The accessibility action to perform 699 * @param arguments (Optional) A bundle with additional arguments, or 700 * null 701 * @return true if the action was performed 702 */ 703 protected abstract boolean onPerformActionForVirtualView( 704 int virtualViewId, int action, Bundle arguments); 705 706 /** 707 * Exposes a virtual view hierarchy to the accessibility framework. Only 708 * used in API 16+. 709 */ 710 private class ExploreByTouchNodeProvider extends AccessibilityNodeProviderCompat { 711 @Override 712 public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) { 713 return ExploreByTouchHelper.this.createNode(virtualViewId); 714 } 715 716 @Override 717 public boolean performAction(int virtualViewId, int action, Bundle arguments) { 718 return ExploreByTouchHelper.this.performAction(virtualViewId, action, arguments); 719 } 720 } 721} 722