1 package com.android.launcher3.accessibility; 2 3 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; 4 5 import static com.android.launcher3.LauncherState.NORMAL; 6 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback; 7 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE; 8 9 import android.appwidget.AppWidgetProviderInfo; 10 import android.graphics.Point; 11 import android.graphics.Rect; 12 import android.graphics.RectF; 13 import android.os.Bundle; 14 import android.os.Handler; 15 import android.text.TextUtils; 16 import android.util.Log; 17 import android.util.SparseArray; 18 import android.view.KeyEvent; 19 import android.view.View; 20 import android.view.View.AccessibilityDelegate; 21 import android.view.accessibility.AccessibilityNodeInfo; 22 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 23 24 import com.android.launcher3.BubbleTextView; 25 import com.android.launcher3.ButtonDropTarget; 26 import com.android.launcher3.CellLayout; 27 import com.android.launcher3.DropTarget.DragObject; 28 import com.android.launcher3.Launcher; 29 import com.android.launcher3.LauncherSettings.Favorites; 30 import com.android.launcher3.PendingAddItemInfo; 31 import com.android.launcher3.R; 32 import com.android.launcher3.Workspace; 33 import com.android.launcher3.dragndrop.DragController.DragListener; 34 import com.android.launcher3.dragndrop.DragOptions; 35 import com.android.launcher3.dragndrop.DragView; 36 import com.android.launcher3.folder.Folder; 37 import com.android.launcher3.keyboard.KeyboardDragAndDropView; 38 import com.android.launcher3.model.data.AppInfo; 39 import com.android.launcher3.model.data.FolderInfo; 40 import com.android.launcher3.model.data.ItemInfo; 41 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 42 import com.android.launcher3.model.data.WorkspaceItemInfo; 43 import com.android.launcher3.notification.NotificationListener; 44 import com.android.launcher3.popup.ArrowPopup; 45 import com.android.launcher3.popup.PopupContainerWithArrow; 46 import com.android.launcher3.touch.ItemLongClickListener; 47 import com.android.launcher3.util.IntArray; 48 import com.android.launcher3.util.IntSet; 49 import com.android.launcher3.util.ShortcutUtil; 50 import com.android.launcher3.util.Thunk; 51 import com.android.launcher3.views.OptionsPopupView; 52 import com.android.launcher3.views.OptionsPopupView.OptionItem; 53 import com.android.launcher3.widget.LauncherAppWidgetHostView; 54 import com.android.launcher3.widget.util.WidgetSizes; 55 56 import java.util.ArrayList; 57 import java.util.Collections; 58 import java.util.List; 59 60 public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener { 61 62 private static final String TAG = "LauncherAccessibilityDelegate"; 63 64 public static final int REMOVE = R.id.action_remove; 65 public static final int UNINSTALL = R.id.action_uninstall; 66 public static final int DISMISS_PREDICTION = R.id.action_dismiss_prediction; 67 public static final int PIN_PREDICTION = R.id.action_pin_prediction; 68 public static final int RECONFIGURE = R.id.action_reconfigure; 69 protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace; 70 protected static final int MOVE = R.id.action_move; 71 protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace; 72 protected static final int RESIZE = R.id.action_resize; 73 public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts; 74 public static final int SHORTCUTS_AND_NOTIFICATIONS = R.id.action_shortcuts_and_notifications; 75 76 public enum DragType { 77 ICON, 78 FOLDER, 79 WIDGET 80 } 81 82 public static class DragInfo { 83 public DragType dragType; 84 public ItemInfo info; 85 public View item; 86 } 87 88 protected final SparseArray<LauncherAction> mActions = new SparseArray<>(); 89 protected final Launcher mLauncher; 90 91 private DragInfo mDragInfo = null; 92 LauncherAccessibilityDelegate(Launcher launcher)93 public LauncherAccessibilityDelegate(Launcher launcher) { 94 mLauncher = launcher; 95 96 mActions.put(REMOVE, new LauncherAction( 97 REMOVE, R.string.remove_drop_target_label, KeyEvent.KEYCODE_X)); 98 mActions.put(UNINSTALL, new LauncherAction( 99 UNINSTALL, R.string.uninstall_drop_target_label, KeyEvent.KEYCODE_U)); 100 mActions.put(DISMISS_PREDICTION, new LauncherAction(DISMISS_PREDICTION, 101 R.string.dismiss_prediction_label, KeyEvent.KEYCODE_X)); 102 mActions.put(RECONFIGURE, new LauncherAction( 103 RECONFIGURE, R.string.gadget_setup_text, KeyEvent.KEYCODE_E)); 104 mActions.put(ADD_TO_WORKSPACE, new LauncherAction( 105 ADD_TO_WORKSPACE, R.string.action_add_to_workspace, KeyEvent.KEYCODE_P)); 106 mActions.put(MOVE, new LauncherAction( 107 MOVE, R.string.action_move, KeyEvent.KEYCODE_M)); 108 mActions.put(MOVE_TO_WORKSPACE, new LauncherAction(MOVE_TO_WORKSPACE, 109 R.string.action_move_to_workspace, KeyEvent.KEYCODE_P)); 110 mActions.put(RESIZE, new LauncherAction( 111 RESIZE, R.string.action_resize, KeyEvent.KEYCODE_R)); 112 mActions.put(DEEP_SHORTCUTS, new LauncherAction(DEEP_SHORTCUTS, 113 R.string.action_deep_shortcut, KeyEvent.KEYCODE_S)); 114 mActions.put(SHORTCUTS_AND_NOTIFICATIONS, new LauncherAction(DEEP_SHORTCUTS, 115 R.string.shortcuts_menu_with_notifications_description, KeyEvent.KEYCODE_S)); 116 } 117 118 @Override onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)119 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 120 super.onInitializeAccessibilityNodeInfo(host, info); 121 if (host.getTag() instanceof ItemInfo) { 122 ItemInfo item = (ItemInfo) host.getTag(); 123 124 List<LauncherAction> actions = new ArrayList<>(); 125 getSupportedActions(host, item, actions); 126 actions.forEach(la -> info.addAction(la.accessibilityAction)); 127 128 if (!itemSupportsLongClick(host, item)) { 129 info.setLongClickable(false); 130 info.removeAction(AccessibilityAction.ACTION_LONG_CLICK); 131 } 132 } 133 } 134 135 /** 136 * Adds all the accessibility actions that can be handled. 137 */ getSupportedActions(View host, ItemInfo item, List<LauncherAction> out)138 protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) { 139 // If the request came from keyboard, do not add custom shortcuts as that is already 140 // exposed as a direct shortcut 141 if (ShortcutUtil.supportsShortcuts(item)) { 142 out.add(mActions.get(NotificationListener.getInstanceIfConnected() != null 143 ? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS)); 144 } 145 146 for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) { 147 if (target.supportsAccessibilityDrop(item, host)) { 148 out.add(mActions.get(target.getAccessibilityAction())); 149 } 150 } 151 152 // Do not add move actions for keyboard request as this uses virtual nodes. 153 if (itemSupportsAccessibleDrag(item)) { 154 out.add(mActions.get(MOVE)); 155 156 if (item.container >= 0) { 157 out.add(mActions.get(MOVE_TO_WORKSPACE)); 158 } else if (item instanceof LauncherAppWidgetInfo) { 159 if (!getSupportedResizeActions(host, (LauncherAppWidgetInfo) item).isEmpty()) { 160 out.add(mActions.get(RESIZE)); 161 } 162 } 163 } 164 165 if ((item instanceof AppInfo) || (item instanceof WorkspaceItemInfo) 166 || (item instanceof PendingAddItemInfo)) { 167 out.add(mActions.get(ADD_TO_WORKSPACE)); 168 } 169 } 170 171 /** 172 * Returns all the accessibility actions that can be handled by the host. 173 */ getSupportedActions(Launcher launcher, View host)174 public static List<LauncherAction> getSupportedActions(Launcher launcher, View host) { 175 if (host == null || !(host.getTag() instanceof ItemInfo)) { 176 return Collections.emptyList(); 177 } 178 PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher); 179 LauncherAccessibilityDelegate delegate = container != null 180 ? container.getAccessibilityDelegate() : launcher.getAccessibilityDelegate(); 181 List<LauncherAction> result = new ArrayList<>(); 182 delegate.getSupportedActions(host, (ItemInfo) host.getTag(), result); 183 return result; 184 } 185 itemSupportsLongClick(View host, ItemInfo info)186 private boolean itemSupportsLongClick(View host, ItemInfo info) { 187 return PopupContainerWithArrow.canShow(host, info); 188 } 189 itemSupportsAccessibleDrag(ItemInfo item)190 private boolean itemSupportsAccessibleDrag(ItemInfo item) { 191 if (item instanceof WorkspaceItemInfo) { 192 // Support the action unless the item is in a context menu. 193 return item.screenId >= 0 && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION; 194 } 195 return (item instanceof LauncherAppWidgetInfo) 196 || (item instanceof FolderInfo); 197 } 198 199 @Override performAccessibilityAction(View host, int action, Bundle args)200 public boolean performAccessibilityAction(View host, int action, Bundle args) { 201 if ((host.getTag() instanceof ItemInfo) 202 && performAction(host, (ItemInfo) host.getTag(), action, false)) { 203 return true; 204 } 205 return super.performAccessibilityAction(host, action, args); 206 } 207 208 /** 209 * Performs the provided action on the host 210 */ performAction(final View host, final ItemInfo item, int action, boolean fromKeyboard)211 protected boolean performAction(final View host, final ItemInfo item, int action, 212 boolean fromKeyboard) { 213 if (action == ACTION_LONG_CLICK) { 214 if (PopupContainerWithArrow.canShow(host, item)) { 215 // Long press should be consumed for workspace items, and it should invoke the 216 // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the 217 // standard long press path does. 218 PopupContainerWithArrow.showForIcon((BubbleTextView) host); 219 return true; 220 } 221 } else if (action == MOVE) { 222 return beginAccessibleDrag(host, item, fromKeyboard); 223 } else if (action == ADD_TO_WORKSPACE) { 224 final int[] coordinates = new int[2]; 225 final int screenId = findSpaceOnWorkspace(item, coordinates); 226 if (screenId == -1) { 227 return false; 228 } 229 mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> { 230 if (item instanceof AppInfo) { 231 WorkspaceItemInfo info = ((AppInfo) item).makeWorkspaceItem(); 232 mLauncher.getModelWriter().addItemToDatabase(info, 233 Favorites.CONTAINER_DESKTOP, 234 screenId, coordinates[0], coordinates[1]); 235 236 mLauncher.bindItems( 237 Collections.singletonList(info), 238 /* forceAnimateIcons= */ true, 239 /* focusFirstItemForAccessibility= */ true); 240 announceConfirmation(R.string.item_added_to_workspace); 241 } else if (item instanceof PendingAddItemInfo) { 242 PendingAddItemInfo info = (PendingAddItemInfo) item; 243 Workspace workspace = mLauncher.getWorkspace(); 244 workspace.snapToPage(workspace.getPageIndexForScreenId(screenId)); 245 mLauncher.addPendingItem(info, Favorites.CONTAINER_DESKTOP, 246 screenId, coordinates, info.spanX, info.spanY); 247 } 248 else if (item instanceof WorkspaceItemInfo) { 249 WorkspaceItemInfo info = ((WorkspaceItemInfo) item).clone(); 250 mLauncher.getModelWriter().addItemToDatabase(info, 251 Favorites.CONTAINER_DESKTOP, 252 screenId, coordinates[0], coordinates[1]); 253 mLauncher.bindItems(Collections.singletonList(info), true, true); 254 } 255 })); 256 return true; 257 } else if (action == MOVE_TO_WORKSPACE) { 258 Folder folder = Folder.getOpen(mLauncher); 259 folder.close(true); 260 WorkspaceItemInfo info = (WorkspaceItemInfo) item; 261 folder.getInfo().remove(info, false); 262 263 final int[] coordinates = new int[2]; 264 final int screenId = findSpaceOnWorkspace(item, coordinates); 265 if (screenId == -1) { 266 return false; 267 } 268 mLauncher.getModelWriter().moveItemInDatabase(info, 269 Favorites.CONTAINER_DESKTOP, 270 screenId, coordinates[0], coordinates[1]); 271 272 // Bind the item in next frame so that if a new workspace page was created, 273 // it will get laid out. 274 new Handler().post(() -> { 275 mLauncher.bindItems(Collections.singletonList(item), true); 276 announceConfirmation(R.string.item_moved); 277 }); 278 return true; 279 } else if (action == RESIZE) { 280 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item; 281 List<OptionItem> actions = getSupportedResizeActions(host, info); 282 Rect pos = new Rect(); 283 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(host, pos); 284 ArrowPopup popup = OptionsPopupView.show(mLauncher, new RectF(pos), actions, false); 285 popup.requestFocus(); 286 popup.setOnCloseCallback(host::requestFocus); 287 return true; 288 } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) { 289 return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null; 290 } else { 291 for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) { 292 if (dropTarget.supportsAccessibilityDrop(item, host) 293 && action == dropTarget.getAccessibilityAction()) { 294 dropTarget.onAccessibilityDrop(host, item); 295 return true; 296 } 297 } 298 } 299 return false; 300 } 301 getSupportedResizeActions(View host, LauncherAppWidgetInfo info)302 private List<OptionItem> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) { 303 List<OptionItem> actions = new ArrayList<>(); 304 AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo(); 305 if (providerInfo == null) { 306 return actions; 307 } 308 309 CellLayout layout; 310 if (host.getParent() instanceof DragView) { 311 layout = (CellLayout) ((DragView) host.getParent()).getContentViewParent().getParent(); 312 } else { 313 layout = (CellLayout) host.getParent().getParent(); 314 } 315 if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) { 316 if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) || 317 layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) { 318 actions.add(new OptionItem(mLauncher, 319 R.string.action_increase_width, 320 R.drawable.ic_widget_width_increase, 321 IGNORE, 322 v -> performResizeAction(R.string.action_increase_width, host, info))); 323 } 324 325 if (info.spanX > info.minSpanX && info.spanX > 1) { 326 actions.add(new OptionItem(mLauncher, 327 R.string.action_decrease_width, 328 R.drawable.ic_widget_width_decrease, 329 IGNORE, 330 v -> performResizeAction(R.string.action_decrease_width, host, info))); 331 } 332 } 333 334 if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) { 335 if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) || 336 layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) { 337 actions.add(new OptionItem(mLauncher, 338 R.string.action_increase_height, 339 R.drawable.ic_widget_height_increase, 340 IGNORE, 341 v -> performResizeAction(R.string.action_increase_height, host, info))); 342 } 343 344 if (info.spanY > info.minSpanY && info.spanY > 1) { 345 actions.add(new OptionItem(mLauncher, 346 R.string.action_decrease_height, 347 R.drawable.ic_widget_height_decrease, 348 IGNORE, 349 v -> performResizeAction(R.string.action_decrease_height, host, info))); 350 } 351 } 352 return actions; 353 } 354 performResizeAction(int action, View host, LauncherAppWidgetInfo info)355 private boolean performResizeAction(int action, View host, LauncherAppWidgetInfo info) { 356 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams(); 357 CellLayout layout = (CellLayout) host.getParent().getParent(); 358 layout.markCellsAsUnoccupiedForView(host); 359 360 if (action == R.string.action_increase_width) { 361 if (((host.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) 362 && layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) 363 || !layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY)) { 364 lp.cellX --; 365 info.cellX --; 366 } 367 lp.cellHSpan ++; 368 info.spanX ++; 369 } else if (action == R.string.action_decrease_width) { 370 lp.cellHSpan --; 371 info.spanX --; 372 } else if (action == R.string.action_increase_height) { 373 if (!layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1)) { 374 lp.cellY --; 375 info.cellY --; 376 } 377 lp.cellVSpan ++; 378 info.spanY ++; 379 } else if (action == R.string.action_decrease_height) { 380 lp.cellVSpan --; 381 info.spanY --; 382 } 383 384 layout.markCellsAsOccupiedForView(host); 385 WidgetSizes.updateWidgetSizeRanges(((LauncherAppWidgetHostView) host), mLauncher, 386 info.spanX, info.spanY); 387 host.requestLayout(); 388 mLauncher.getModelWriter().updateItemInDatabase(info); 389 announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY)); 390 return true; 391 } 392 announceConfirmation(int resId)393 @Thunk void announceConfirmation(int resId) { 394 announceConfirmation(mLauncher.getResources().getString(resId)); 395 } 396 announceConfirmation(String confirmation)397 @Thunk void announceConfirmation(String confirmation) { 398 mLauncher.getDragLayer().announceForAccessibility(confirmation); 399 400 } 401 isInAccessibleDrag()402 public boolean isInAccessibleDrag() { 403 return mDragInfo != null; 404 } 405 getDragInfo()406 public DragInfo getDragInfo() { 407 return mDragInfo; 408 } 409 410 /** 411 * @param clickedTarget the actual view that was clicked 412 * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used 413 * as the actual drop location otherwise the views center is used. 414 */ handleAccessibleDrop(View clickedTarget, Rect dropLocation, String confirmation)415 public void handleAccessibleDrop(View clickedTarget, Rect dropLocation, 416 String confirmation) { 417 if (!isInAccessibleDrag()) return; 418 419 int[] loc = new int[2]; 420 if (dropLocation == null) { 421 loc[0] = clickedTarget.getWidth() / 2; 422 loc[1] = clickedTarget.getHeight() / 2; 423 } else { 424 loc[0] = dropLocation.centerX(); 425 loc[1] = dropLocation.centerY(); 426 } 427 428 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc); 429 mLauncher.getDragController().completeAccessibleDrag(loc); 430 431 if (!TextUtils.isEmpty(confirmation)) { 432 announceConfirmation(confirmation); 433 } 434 } 435 beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard)436 private boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard) { 437 if (!itemSupportsAccessibleDrag(info)) { 438 return false; 439 } 440 441 mDragInfo = new DragInfo(); 442 mDragInfo.info = info; 443 mDragInfo.item = item; 444 mDragInfo.dragType = DragType.ICON; 445 if (info instanceof FolderInfo) { 446 mDragInfo.dragType = DragType.FOLDER; 447 } else if (info instanceof LauncherAppWidgetInfo) { 448 mDragInfo.dragType = DragType.WIDGET; 449 } 450 451 Rect pos = new Rect(); 452 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos); 453 mLauncher.getDragController().addDragListener(this); 454 455 DragOptions options = new DragOptions(); 456 options.isAccessibleDrag = true; 457 options.isKeyboardDrag = fromKeyboard; 458 options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY()); 459 460 if (fromKeyboard) { 461 KeyboardDragAndDropView popup = (KeyboardDragAndDropView) mLauncher.getLayoutInflater() 462 .inflate(R.layout.keyboard_drag_and_drop, mLauncher.getDragLayer(), false); 463 popup.showForIcon(item, info, options); 464 } else { 465 ItemLongClickListener.beginDrag(item, mLauncher, info, options); 466 } 467 return true; 468 } 469 470 @Override onDragStart(DragObject dragObject, DragOptions options)471 public void onDragStart(DragObject dragObject, DragOptions options) { 472 // No-op 473 } 474 475 @Override onDragEnd()476 public void onDragEnd() { 477 mLauncher.getDragController().removeDragListener(this); 478 mDragInfo = null; 479 } 480 481 /** 482 * Find empty space on the workspace and returns the screenId. 483 */ findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates)484 protected int findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) { 485 Workspace workspace = mLauncher.getWorkspace(); 486 IntArray workspaceScreens = workspace.getScreenOrder(); 487 int screenId; 488 489 // First check if there is space on the current screen. 490 int screenIndex = workspace.getCurrentPage(); 491 screenId = workspaceScreens.get(screenIndex); 492 CellLayout layout = (CellLayout) workspace.getPageAt(screenIndex); 493 494 boolean found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY); 495 screenIndex = 0; 496 while (!found && screenIndex < workspaceScreens.size()) { 497 screenId = workspaceScreens.get(screenIndex); 498 layout = (CellLayout) workspace.getPageAt(screenIndex); 499 found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY); 500 screenIndex++; 501 } 502 503 if (found) { 504 return screenId; 505 } 506 507 workspace.addExtraEmptyScreens(); 508 IntSet emptyScreenIds = workspace.commitExtraEmptyScreens(); 509 if (emptyScreenIds.isEmpty()) { 510 // Couldn't create extra empty screens for some reason (e.g. Workspace is loading) 511 return -1; 512 } 513 514 screenId = emptyScreenIds.getArray().get(0); 515 layout = workspace.getScreenWithId(screenId); 516 found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY); 517 518 if (!found) { 519 Log.wtf(TAG, "Not enough space on an empty screen"); 520 } 521 return screenId; 522 } 523 524 public class LauncherAction { 525 public final int keyCode; 526 public final AccessibilityAction accessibilityAction; 527 528 private final LauncherAccessibilityDelegate mDelegate; 529 LauncherAction(int id, int labelRes, int keyCode)530 public LauncherAction(int id, int labelRes, int keyCode) { 531 this.keyCode = keyCode; 532 accessibilityAction = new AccessibilityAction(id, mLauncher.getString(labelRes)); 533 mDelegate = LauncherAccessibilityDelegate.this; 534 } 535 536 /** 537 * Invokes the action for the provided host 538 */ invokeFromKeyboard(View host)539 public boolean invokeFromKeyboard(View host) { 540 if (host != null && host.getTag() instanceof ItemInfo) { 541 return mDelegate.performAction( 542 host, (ItemInfo) host.getTag(), accessibilityAction.getId(), true); 543 } else { 544 return false; 545 } 546 } 547 } 548 } 549