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