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