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