1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3; 18 19 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; 20 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 21 import static com.android.launcher3.LauncherState.ALL_APPS; 22 import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE; 23 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED; 24 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_INACCESSIBLE; 25 import static com.android.launcher3.LauncherState.HINT_STATE; 26 import static com.android.launcher3.LauncherState.NORMAL; 27 import static com.android.launcher3.LauncherState.SPRING_LOADED; 28 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback; 29 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; 30 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY; 31 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; 32 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT; 33 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT; 34 35 import android.animation.Animator; 36 import android.animation.AnimatorListenerAdapter; 37 import android.animation.LayoutTransition; 38 import android.animation.ValueAnimator; 39 import android.animation.ValueAnimator.AnimatorUpdateListener; 40 import android.annotation.SuppressLint; 41 import android.app.WallpaperManager; 42 import android.appwidget.AppWidgetHostView; 43 import android.appwidget.AppWidgetProviderInfo; 44 import android.content.Context; 45 import android.content.res.Resources; 46 import android.graphics.Bitmap; 47 import android.graphics.Point; 48 import android.graphics.Rect; 49 import android.graphics.drawable.Drawable; 50 import android.os.Handler; 51 import android.os.Message; 52 import android.os.Parcelable; 53 import android.os.UserHandle; 54 import android.text.TextUtils; 55 import android.util.AttributeSet; 56 import android.util.Log; 57 import android.util.SparseArray; 58 import android.view.LayoutInflater; 59 import android.view.MotionEvent; 60 import android.view.View; 61 import android.view.ViewGroup; 62 import android.view.ViewTreeObserver; 63 import android.view.accessibility.AccessibilityNodeInfo; 64 import android.widget.Toast; 65 66 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; 67 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; 68 import com.android.launcher3.anim.Interpolators; 69 import com.android.launcher3.anim.PendingAnimation; 70 import com.android.launcher3.config.FeatureFlags; 71 import com.android.launcher3.dot.FolderDotInfo; 72 import com.android.launcher3.dragndrop.DragController; 73 import com.android.launcher3.dragndrop.DragLayer; 74 import com.android.launcher3.dragndrop.DragOptions; 75 import com.android.launcher3.dragndrop.DragView; 76 import com.android.launcher3.dragndrop.DraggableView; 77 import com.android.launcher3.dragndrop.SpringLoadedDragController; 78 import com.android.launcher3.folder.Folder; 79 import com.android.launcher3.folder.FolderIcon; 80 import com.android.launcher3.folder.PreviewBackground; 81 import com.android.launcher3.graphics.DragPreviewProvider; 82 import com.android.launcher3.graphics.PreloadIconDrawable; 83 import com.android.launcher3.icons.BitmapRenderer; 84 import com.android.launcher3.icons.FastBitmapDrawable; 85 import com.android.launcher3.logger.LauncherAtom; 86 import com.android.launcher3.logging.StatsLogManager; 87 import com.android.launcher3.logging.StatsLogManager.LauncherEvent; 88 import com.android.launcher3.model.data.AppInfo; 89 import com.android.launcher3.model.data.FolderInfo; 90 import com.android.launcher3.model.data.ItemInfo; 91 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 92 import com.android.launcher3.model.data.WorkspaceItemInfo; 93 import com.android.launcher3.pageindicators.WorkspacePageIndicator; 94 import com.android.launcher3.popup.PopupContainerWithArrow; 95 import com.android.launcher3.statemanager.StateManager; 96 import com.android.launcher3.statemanager.StateManager.StateHandler; 97 import com.android.launcher3.states.StateAnimationConfig; 98 import com.android.launcher3.touch.WorkspaceTouchListener; 99 import com.android.launcher3.util.EdgeEffectCompat; 100 import com.android.launcher3.util.Executors; 101 import com.android.launcher3.util.IntArray; 102 import com.android.launcher3.util.IntSparseArrayMap; 103 import com.android.launcher3.util.ItemInfoMatcher; 104 import com.android.launcher3.util.OverlayEdgeEffect; 105 import com.android.launcher3.util.PackageUserKey; 106 import com.android.launcher3.util.RunnableList; 107 import com.android.launcher3.util.Thunk; 108 import com.android.launcher3.util.WallpaperOffsetInterpolator; 109 import com.android.launcher3.widget.LauncherAppWidgetHost; 110 import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener; 111 import com.android.launcher3.widget.LauncherAppWidgetHostView; 112 import com.android.launcher3.widget.PendingAddShortcutInfo; 113 import com.android.launcher3.widget.PendingAddWidgetInfo; 114 import com.android.launcher3.widget.PendingAppWidgetHostView; 115 import com.android.launcher3.widget.WidgetManagerHelper; 116 import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener; 117 import com.android.launcher3.widget.util.WidgetSizes; 118 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay; 119 120 import java.util.ArrayList; 121 import java.util.Collections; 122 import java.util.HashSet; 123 import java.util.List; 124 import java.util.function.Predicate; 125 import java.util.stream.Collectors; 126 127 /** 128 * The workspace is a wide area with a wallpaper and a finite number of pages. 129 * Each page contains a number of icons, folders or widgets the user can 130 * interact with. A workspace is meant to be used with a fixed width only. 131 */ 132 public class Workspace extends PagedView<WorkspacePageIndicator> 133 implements DropTarget, DragSource, View.OnTouchListener, 134 DragController.DragListener, Insettable, StateHandler<LauncherState>, 135 WorkspaceLayoutManager { 136 137 /** The value that {@link #mTransitionProgress} must be greater than for 138 * {@link #transitionStateShouldAllowDrop()} to return true. */ 139 private static final float ALLOW_DROP_TRANSITION_PROGRESS = 0.25f; 140 141 /** The value that {@link #mTransitionProgress} must be greater than for 142 * {@link #isFinishedSwitchingState()} ()} to return true. */ 143 private static final float FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS = 0.5f; 144 145 private static final boolean ENFORCE_DRAG_EVENT_ORDER = false; 146 147 private static final int ADJACENT_SCREEN_DROP_DURATION = 300; 148 149 public static final int DEFAULT_PAGE = 0; 150 151 private static final int DEFAULT_SMARTSPACE_HEIGHT = 1; 152 153 private static final int EXPANDED_SMARTSPACE_HEIGHT = 2; 154 155 private LayoutTransition mLayoutTransition; 156 @Thunk final WallpaperManager mWallpaperManager; 157 158 private ShortcutAndWidgetContainer mDragSourceInternal; 159 160 @Thunk final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>(); 161 @Thunk final IntArray mScreenOrder = new IntArray(); 162 163 @Thunk boolean mDeferRemoveExtraEmptyScreen = false; 164 165 /** 166 * CellInfo for the cell that is currently being dragged 167 */ 168 private CellLayout.CellInfo mDragInfo; 169 170 /** 171 * Target drop area calculated during last acceptDrop call. 172 */ 173 @Thunk int[] mTargetCell = new int[2]; 174 private int mDragOverX = -1; 175 private int mDragOverY = -1; 176 177 /** 178 * The CellLayout that is currently being dragged over 179 */ 180 @Thunk CellLayout mDragTargetLayout = null; 181 /** 182 * The CellLayout that we will show as highlighted 183 */ 184 private CellLayout mDragOverlappingLayout = null; 185 186 /** 187 * The CellLayout which will be dropped to 188 */ 189 private CellLayout mDropToLayout = null; 190 191 @Thunk final Launcher mLauncher; 192 @Thunk DragController mDragController; 193 194 private final int[] mTempXY = new int[2]; 195 private final float[] mTempFXY = new float[2]; 196 @Thunk float[] mDragViewVisualCenter = new float[2]; 197 private final float[] mTempTouchCoordinates = new float[2]; 198 199 private SpringLoadedDragController mSpringLoadedDragController; 200 201 private boolean mIsSwitchingState = false; 202 203 boolean mChildrenLayersEnabled = true; 204 205 private boolean mStripScreensOnPageStopMoving = false; 206 207 private DragPreviewProvider mOutlineProvider = null; 208 209 private boolean mWorkspaceFadeInAdjacentScreens; 210 211 final WallpaperOffsetInterpolator mWallpaperOffset; 212 private boolean mUnlockWallpaperFromDefaultPageOnLayout; 213 214 public static final int REORDER_TIMEOUT = 650; 215 private final Alarm mReorderAlarm = new Alarm(); 216 private PreviewBackground mFolderCreateBg; 217 private FolderIcon mDragOverFolderIcon = null; 218 private boolean mCreateUserFolderOnDrop = false; 219 private boolean mAddToExistingFolderOnDrop = false; 220 private float mMaxDistanceForFolderCreation; 221 222 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget) 223 private float mXDown; 224 private float mYDown; 225 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6; 226 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3; 227 final static float TOUCH_SLOP_DAMPING_FACTOR = 4; 228 229 // Relating to the animation of items being dropped externally 230 public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0; 231 public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1; 232 public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2; 233 public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3; 234 public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4; 235 236 // Related to dragging, folder creation and reordering 237 private static final int DRAG_MODE_NONE = 0; 238 private static final int DRAG_MODE_CREATE_FOLDER = 1; 239 private static final int DRAG_MODE_ADD_TO_FOLDER = 2; 240 private static final int DRAG_MODE_REORDER = 3; 241 private int mDragMode = DRAG_MODE_NONE; 242 @Thunk int mLastReorderX = -1; 243 @Thunk int mLastReorderY = -1; 244 245 private SparseArray<Parcelable> mSavedStates; 246 private final IntArray mRestoredPages = new IntArray(); 247 248 private float mCurrentScale; 249 private float mTransitionProgress; 250 251 // State related to Launcher Overlay 252 private OverlayEdgeEffect mOverlayEdgeEffect; 253 boolean mOverlayShown = false; 254 private Runnable mOnOverlayHiddenCallback; 255 256 private boolean mForceDrawAdjacentPages = false; 257 258 // Total over scrollX in the overlay direction. 259 private float mOverlayTranslation; 260 261 // Handles workspace state transitions 262 private final WorkspaceStateTransitionAnimation mStateTransitionAnimation; 263 264 private final StatsLogManager mStatsLogManager; 265 266 /** 267 * Used to inflate the Workspace from XML. 268 * 269 * @param context The application's context. 270 * @param attrs The attributes set containing the Workspace's customization values. 271 */ Workspace(Context context, AttributeSet attrs)272 public Workspace(Context context, AttributeSet attrs) { 273 this(context, attrs, 0); 274 } 275 276 /** 277 * Used to inflate the Workspace from XML. 278 * 279 * @param context The application's context. 280 * @param attrs The attributes set containing the Workspace's customization values. 281 * @param defStyle Unused. 282 */ Workspace(Context context, AttributeSet attrs, int defStyle)283 public Workspace(Context context, AttributeSet attrs, int defStyle) { 284 super(context, attrs, defStyle); 285 286 mLauncher = Launcher.getLauncher(context); 287 mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); 288 mWallpaperManager = WallpaperManager.getInstance(context); 289 290 mWallpaperOffset = new WallpaperOffsetInterpolator(this); 291 292 setHapticFeedbackEnabled(false); 293 initWorkspace(); 294 295 // Disable multitouch across the workspace/all apps/customize tray 296 setMotionEventSplittingEnabled(true); 297 setOnTouchListener(new WorkspaceTouchListener(mLauncher, this)); 298 mStatsLogManager = StatsLogManager.newInstance(context); 299 } 300 301 @Override setInsets(Rect insets)302 public void setInsets(Rect insets) { 303 DeviceProfile grid = mLauncher.getDeviceProfile(); 304 305 mMaxDistanceForFolderCreation = grid.isTablet 306 ? 0.75f * grid.iconSizePx : 0.55f * grid.iconSizePx; 307 mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens(); 308 309 Rect padding = grid.workspacePadding; 310 setPadding(padding.left, padding.top, padding.right, padding.bottom); 311 mInsets.set(insets); 312 // Increase our bottom insets so we don't overlap with the taskbar. 313 mInsets.bottom += grid.nonOverlappingTaskbarInset; 314 315 if (isTwoPanelEnabled()) { 316 setPageSpacing(0); // we have two pages and we don't want any spacing 317 } else if (mWorkspaceFadeInAdjacentScreens) { 318 // In landscape mode the page spacing is set to the default. 319 setPageSpacing(grid.edgeMarginPx); 320 } else { 321 // In portrait, we want the pages spaced such that there is no 322 // overhang of the previous / next page into the current page viewport. 323 // We assume symmetrical padding in portrait mode. 324 int maxInsets = Math.max(insets.left, insets.right); 325 int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1); 326 setPageSpacing(Math.max(maxInsets, maxPadding)); 327 } 328 329 int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx; 330 int paddingBottom = grid.cellLayoutBottomPaddingPx; 331 int twoPanelLandscapeSidePadding = paddingLeftRight * 2; 332 int twoPanelPortraitSidePadding = paddingLeftRight / 2; 333 334 int panelCount = getPanelCount(); 335 for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) { 336 int paddingLeft = paddingLeftRight; 337 int paddingRight = paddingLeftRight; 338 if (panelCount > 1) { 339 if (i % panelCount == 0) { // left side panel 340 paddingLeft = grid.isLandscape ? twoPanelLandscapeSidePadding 341 : twoPanelPortraitSidePadding; 342 paddingRight = 0; 343 } else if (i % panelCount == panelCount - 1) { // right side panel 344 paddingLeft = 0; 345 paddingRight = grid.isLandscape ? twoPanelLandscapeSidePadding 346 : twoPanelPortraitSidePadding; 347 } else { // middle panel 348 paddingLeft = 0; 349 paddingRight = 0; 350 } 351 } 352 mWorkspaceScreens.valueAt(i).setPadding(paddingLeft, 0, paddingRight, paddingBottom); 353 } 354 } 355 356 /** 357 * Estimates the size of an item using spans: hSpan, vSpan. 358 * 359 * @return MAX_VALUE for each dimension if unsuccessful. 360 */ estimateItemSize(ItemInfo itemInfo)361 public int[] estimateItemSize(ItemInfo itemInfo) { 362 int[] size = new int[2]; 363 if (getChildCount() > 0) { 364 // Use the first page to estimate the child position 365 CellLayout cl = (CellLayout) getChildAt(0); 366 boolean isWidget = itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; 367 368 Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY); 369 370 float scale = 1; 371 if (isWidget) { 372 DeviceProfile profile = mLauncher.getDeviceProfile(); 373 scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y); 374 } 375 size[0] = r.width(); 376 size[1] = r.height(); 377 378 if (isWidget) { 379 size[0] /= scale; 380 size[1] /= scale; 381 } 382 return size; 383 } else { 384 size[0] = Integer.MAX_VALUE; 385 size[1] = Integer.MAX_VALUE; 386 return size; 387 } 388 } 389 getWallpaperOffsetForCenterPage()390 public float getWallpaperOffsetForCenterPage() { 391 return getWallpaperOffsetForPage(getPageNearestToCenterOfScreen()); 392 } 393 getWallpaperOffsetForPage(int page)394 private float getWallpaperOffsetForPage(int page) { 395 int pageScroll = getScrollForPage(page); 396 return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll); 397 } 398 399 /** Returns the number of pages used for the wallpaper parallax. */ getNumPagesForWallpaperParallax()400 public int getNumPagesForWallpaperParallax() { 401 return mWallpaperOffset.getNumPagesForWallpaperParallax(); 402 } 403 estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan)404 public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) { 405 Rect r = new Rect(); 406 cl.cellToRect(hCell, vCell, hSpan, vSpan, r); 407 return r; 408 } 409 410 @Override onDragStart(DragObject dragObject, DragOptions options)411 public void onDragStart(DragObject dragObject, DragOptions options) { 412 if (ENFORCE_DRAG_EVENT_ORDER) { 413 enforceDragParity("onDragStart", 0, 0); 414 } 415 416 if (mDragInfo != null && mDragInfo.cell != null) { 417 CellLayout layout = (CellLayout) (mDragInfo.cell instanceof LauncherAppWidgetHostView 418 ? dragObject.dragView.getContentViewParent().getParent() 419 : mDragInfo.cell.getParent().getParent()); 420 layout.markCellsAsUnoccupiedForView(mDragInfo.cell); 421 } 422 423 updateChildrenLayersEnabled(); 424 425 // Do not add a new page if it is a accessible drag which was not started by the workspace. 426 // We do not support accessibility drag from other sources and instead provide a direct 427 // action for move/add to homescreen. 428 // When a accessible drag is started by the folder, we only allow rearranging withing the 429 // folder. 430 boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this); 431 if (addNewPage) { 432 mDeferRemoveExtraEmptyScreen = false; 433 addExtraEmptyScreenOnDrag(dragObject); 434 435 if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET 436 && dragObject.dragSource != this) { 437 // When dragging a widget from different source, move to a page which has 438 // enough space to place this widget (after rearranging/resizing). We special case 439 // widgets as they cannot be placed inside a folder. 440 // Start at the current page and search right (on LTR) until finding a page with 441 // enough space. Since an empty screen is the furthest right, a page must be found. 442 int currentPage = getDestinationPage(); 443 for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) { 444 CellLayout page = (CellLayout) getPageAt(pageIndex); 445 if (page.hasReorderSolution(dragObject.dragInfo)) { 446 setCurrentPage(pageIndex); 447 break; 448 } 449 } 450 } 451 } 452 453 // Always enter the spring loaded mode 454 mLauncher.getStateManager().goToState(SPRING_LOADED); 455 mStatsLogManager.logger().withItemInfo(dragObject.dragInfo) 456 .withInstanceId(dragObject.logInstanceId) 457 .log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED); 458 } 459 isTwoPanelEnabled()460 private boolean isTwoPanelEnabled() { 461 return mLauncher.mDeviceProfile.isTwoPanels; 462 } 463 464 @Override getPanelCount()465 protected int getPanelCount() { 466 return isTwoPanelEnabled() ? 2 : super.getPanelCount(); 467 } 468 deferRemoveExtraEmptyScreen()469 public void deferRemoveExtraEmptyScreen() { 470 mDeferRemoveExtraEmptyScreen = true; 471 } 472 473 @Override onDragEnd()474 public void onDragEnd() { 475 if (ENFORCE_DRAG_EVENT_ORDER) { 476 enforceDragParity("onDragEnd", 0, 0); 477 } 478 479 updateChildrenLayersEnabled(); 480 StateManager<LauncherState> stateManager = mLauncher.getStateManager(); 481 stateManager.addStateListener(new StateManager.StateListener<LauncherState>() { 482 @Override 483 public void onStateTransitionComplete(LauncherState finalState) { 484 if (finalState == NORMAL) { 485 if (!mDeferRemoveExtraEmptyScreen) { 486 removeExtraEmptyScreen(true /* stripEmptyScreens */); 487 } 488 stateManager.removeStateListener(this); 489 } 490 } 491 }); 492 493 mDragInfo = null; 494 mOutlineProvider = null; 495 mDragSourceInternal = null; 496 } 497 498 /** 499 * Initializes various states for this workspace. 500 */ initWorkspace()501 protected void initWorkspace() { 502 mCurrentPage = DEFAULT_PAGE; 503 setClipToPadding(false); 504 505 setupLayoutTransition(); 506 507 // Set the wallpaper dimensions when Launcher starts up 508 setWallpaperDimension(); 509 } 510 setupLayoutTransition()511 private void setupLayoutTransition() { 512 // We want to show layout transitions when pages are deleted, to close the gap. 513 mLayoutTransition = new LayoutTransition(); 514 515 mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING); 516 mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); 517 // Change the interpolators such that the fade animation plays before the move animation. 518 // This prevents empty adjacent pages to overlay during animation 519 mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING, 520 Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0, 0.5f)); 521 mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_DISAPPEARING, 522 Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0.5f, 1)); 523 524 mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING); 525 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); 526 setLayoutTransition(mLayoutTransition); 527 } 528 enableLayoutTransitions()529 void enableLayoutTransitions() { 530 setLayoutTransition(mLayoutTransition); 531 } disableLayoutTransitions()532 void disableLayoutTransitions() { 533 setLayoutTransition(null); 534 } 535 536 @Override onViewAdded(View child)537 public void onViewAdded(View child) { 538 if (!(child instanceof CellLayout)) { 539 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 540 } 541 CellLayout cl = ((CellLayout) child); 542 cl.setOnInterceptTouchListener(this); 543 cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 544 super.onViewAdded(child); 545 } 546 547 /** 548 * Initializes and binds the first page 549 * @param qsb an existing qsb to recycle or null. 550 */ bindAndInitFirstWorkspaceScreen(View qsb)551 public void bindAndInitFirstWorkspaceScreen(View qsb) { 552 if (!FeatureFlags.QSB_ON_FIRST_SCREEN) { 553 return; 554 } 555 // Add the first page 556 CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0); 557 // Always add a QSB on the first screen. 558 if (qsb == null) { 559 // In transposed layout, we add the QSB in the Grid. As workspace does not touch the 560 // edges, we do not need a full width QSB. 561 qsb = LayoutInflater.from(getContext()) 562 .inflate(R.layout.search_container_workspace, firstPage, false); 563 } 564 565 int cellVSpan = FeatureFlags.EXPANDED_SMARTSPACE.get() 566 ? EXPANDED_SMARTSPACE_HEIGHT : DEFAULT_SMARTSPACE_HEIGHT; 567 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 568 cellVSpan); 569 lp.canReorder = false; 570 if (!firstPage.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true)) { 571 Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout"); 572 } 573 } 574 removeAllWorkspaceScreens()575 public void removeAllWorkspaceScreens() { 576 // Disable all layout transitions before removing all pages to ensure that we don't get the 577 // transition animations competing with us changing the scroll when we add pages 578 disableLayoutTransitions(); 579 580 // Recycle the QSB widget 581 View qsb = findViewById(R.id.search_container_workspace); 582 if (qsb != null) { 583 ((ViewGroup) qsb.getParent()).removeView(qsb); 584 } 585 586 // Remove the pages and clear the screen models 587 removeFolderListeners(); 588 removeAllViews(); 589 mScreenOrder.clear(); 590 mWorkspaceScreens.clear(); 591 592 // Remove any deferred refresh callbacks 593 mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class); 594 595 // Ensure that the first page is always present 596 bindAndInitFirstWorkspaceScreen(qsb); 597 598 // Re-enable the layout transitions 599 enableLayoutTransitions(); 600 } 601 insertNewWorkspaceScreenBeforeEmptyScreen(int screenId)602 public void insertNewWorkspaceScreenBeforeEmptyScreen(int screenId) { 603 // Find the index to insert this view into. If the empty screen exists, then 604 // insert it before that. 605 int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); 606 if (insertIndex < 0) { 607 insertIndex = mScreenOrder.size(); 608 } 609 insertNewWorkspaceScreen(screenId, insertIndex); 610 } 611 insertNewWorkspaceScreen(int screenId)612 public void insertNewWorkspaceScreen(int screenId) { 613 insertNewWorkspaceScreen(screenId, getChildCount()); 614 } 615 insertNewWorkspaceScreen(int screenId, int insertIndex)616 public CellLayout insertNewWorkspaceScreen(int screenId, int insertIndex) { 617 if (mWorkspaceScreens.containsKey(screenId)) { 618 throw new RuntimeException("Screen id " + screenId + " already exists!"); 619 } 620 621 // Inflate the cell layout, but do not add it automatically so that we can get the newly 622 // created CellLayout. 623 CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate( 624 R.layout.workspace_screen, this, false /* attachToRoot */); 625 DeviceProfile grid = mLauncher.getDeviceProfile(); 626 int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx; 627 int paddingBottom = grid.cellLayoutBottomPaddingPx; 628 newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom); 629 630 mWorkspaceScreens.put(screenId, newScreen); 631 mScreenOrder.add(insertIndex, screenId); 632 addView(newScreen, insertIndex); 633 mStateTransitionAnimation.applyChildState( 634 mLauncher.getStateManager().getState(), newScreen, insertIndex); 635 636 updatePageScrollValues(); 637 return newScreen; 638 } 639 addExtraEmptyScreenOnDrag(DragObject dragObject)640 private void addExtraEmptyScreenOnDrag(DragObject dragObject) { 641 boolean lastChildOnScreen = false; 642 boolean childOnFinalScreen = false; 643 644 if (mDragSourceInternal != null) { 645 // When the drag view content is a LauncherAppWidgetHostView, we should increment the 646 // drag source child count by 1 because the widget in drag has been detached from its 647 // original parent, ShortcutAndWidgetContainer, and reattached to the DragView. 648 int dragSourceChildCount = 649 dragObject.dragView.getContentView() instanceof LauncherAppWidgetHostView 650 ? mDragSourceInternal.getChildCount() + 1 651 : mDragSourceInternal.getChildCount(); 652 if (dragSourceChildCount == 1) { 653 lastChildOnScreen = true; 654 } 655 CellLayout cl = (CellLayout) mDragSourceInternal.getParent(); 656 if (indexOfChild(cl) == getChildCount() - 1) { 657 childOnFinalScreen = true; 658 } 659 } 660 661 // If this is the last item on the final screen 662 if (lastChildOnScreen && childOnFinalScreen) { 663 return; 664 } 665 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) { 666 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID); 667 } 668 } 669 addExtraEmptyScreen()670 public boolean addExtraEmptyScreen() { 671 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) { 672 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID); 673 return true; 674 } 675 return false; 676 } 677 convertFinalScreenToEmptyScreenIfNecessary()678 private void convertFinalScreenToEmptyScreenIfNecessary() { 679 if (mLauncher.isWorkspaceLoading()) { 680 // Invalid and dangerous operation if workspace is loading 681 return; 682 } 683 684 if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return; 685 int finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1); 686 687 CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId); 688 689 // If the final screen is empty, convert it to the extra empty screen 690 if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 && 691 !finalScreen.isDropPending()) { 692 mWorkspaceScreens.remove(finalScreenId); 693 mScreenOrder.removeValue(finalScreenId); 694 695 // if this is the last screen, convert it to the empty screen 696 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen); 697 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); 698 } 699 } 700 removeExtraEmptyScreen(boolean stripEmptyScreens)701 public void removeExtraEmptyScreen(boolean stripEmptyScreens) { 702 removeExtraEmptyScreenDelayed(0, stripEmptyScreens, null); 703 } 704 removeExtraEmptyScreenDelayed( int delay, boolean stripEmptyScreens, Runnable onComplete)705 public void removeExtraEmptyScreenDelayed( 706 int delay, boolean stripEmptyScreens, Runnable onComplete) { 707 if (mLauncher.isWorkspaceLoading()) { 708 // Don't strip empty screens if the workspace is still loading 709 return; 710 } 711 712 if (delay > 0) { 713 postDelayed( 714 () -> removeExtraEmptyScreenDelayed(0, stripEmptyScreens, onComplete), delay); 715 return; 716 } 717 718 convertFinalScreenToEmptyScreenIfNecessary(); 719 if (hasExtraEmptyScreen()) { 720 removeView(mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID)); 721 setCurrentPage(getNextPage()); 722 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID); 723 mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID); 724 725 // Update the page indicator to reflect the removed page. 726 showPageIndicatorAtCurrentScroll(); 727 } 728 729 if (stripEmptyScreens) { 730 stripEmptyScreens(); 731 } 732 733 if (onComplete != null) { 734 onComplete.run(); 735 } 736 } 737 hasExtraEmptyScreen()738 public boolean hasExtraEmptyScreen() { 739 return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1; 740 } 741 commitExtraEmptyScreen()742 public int commitExtraEmptyScreen() { 743 if (mLauncher.isWorkspaceLoading()) { 744 // Invalid and dangerous operation if workspace is loading 745 return -1; 746 } 747 748 CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID); 749 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID); 750 mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID); 751 752 int newId = LauncherSettings.Settings.call(getContext().getContentResolver(), 753 LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) 754 .getInt(LauncherSettings.Settings.EXTRA_VALUE); 755 mWorkspaceScreens.put(newId, cl); 756 mScreenOrder.add(newId); 757 758 return newId; 759 } 760 761 @Override getHotseat()762 public Hotseat getHotseat() { 763 return mLauncher.getHotseat(); 764 } 765 766 @Override onAddDropTarget(DropTarget target)767 public void onAddDropTarget(DropTarget target) { 768 mDragController.addDropTarget(target); 769 } 770 771 @Override getScreenWithId(int screenId)772 public CellLayout getScreenWithId(int screenId) { 773 return mWorkspaceScreens.get(screenId); 774 } 775 getIdForScreen(CellLayout layout)776 public int getIdForScreen(CellLayout layout) { 777 int index = mWorkspaceScreens.indexOfValue(layout); 778 if (index != -1) { 779 return mWorkspaceScreens.keyAt(index); 780 } 781 return -1; 782 } 783 getPageIndexForScreenId(int screenId)784 public int getPageIndexForScreenId(int screenId) { 785 return indexOfChild(mWorkspaceScreens.get(screenId)); 786 } 787 getScreenIdForPageIndex(int index)788 public int getScreenIdForPageIndex(int index) { 789 if (0 <= index && index < mScreenOrder.size()) { 790 return mScreenOrder.get(index); 791 } 792 return -1; 793 } 794 getScreenOrder()795 public IntArray getScreenOrder() { 796 return mScreenOrder; 797 } 798 stripEmptyScreens()799 public void stripEmptyScreens() { 800 if (mLauncher.isWorkspaceLoading()) { 801 // Don't strip empty screens if the workspace is still loading. 802 // This is dangerous and can result in data loss. 803 return; 804 } 805 806 if (isPageInTransition()) { 807 mStripScreensOnPageStopMoving = true; 808 return; 809 } 810 811 int currentPage = getNextPage(); 812 IntArray removeScreens = new IntArray(); 813 int total = mWorkspaceScreens.size(); 814 for (int i = 0; i < total; i++) { 815 int id = mWorkspaceScreens.keyAt(i); 816 CellLayout cl = mWorkspaceScreens.valueAt(i); 817 // FIRST_SCREEN_ID can never be removed. 818 if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID) 819 && cl.getShortcutsAndWidgets().getChildCount() == 0) { 820 removeScreens.add(id); 821 } 822 } 823 824 // We enforce at least one page to add new items to. In the case that we remove the last 825 // such screen, we convert the last screen to the empty screen 826 int minScreens = 1; 827 828 int pageShift = 0; 829 for (int i = 0; i < removeScreens.size(); i++) { 830 int id = removeScreens.get(i); 831 CellLayout cl = mWorkspaceScreens.get(id); 832 mWorkspaceScreens.remove(id); 833 mScreenOrder.removeValue(id); 834 835 if (getChildCount() > minScreens) { 836 if (indexOfChild(cl) < currentPage) { 837 pageShift++; 838 } 839 removeView(cl); 840 } else { 841 // if this is the last screen, convert it to the empty screen 842 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl); 843 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); 844 } 845 } 846 847 if (pageShift >= 0) { 848 setCurrentPage(currentPage - pageShift); 849 } 850 } 851 852 /** 853 * Called directly from a CellLayout (not by the framework), after we've been added as a 854 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout 855 * that it should intercept touch events, which is not something that is normally supported. 856 */ 857 @SuppressLint("ClickableViewAccessibility") 858 @Override onTouch(View v, MotionEvent event)859 public boolean onTouch(View v, MotionEvent event) { 860 return shouldConsumeTouch(v); 861 } 862 shouldConsumeTouch(View v)863 private boolean shouldConsumeTouch(View v) { 864 return !workspaceIconsCanBeDragged() 865 || (!workspaceInModalState() && !isVisible(v)); 866 } 867 isSwitchingState()868 public boolean isSwitchingState() { 869 return mIsSwitchingState; 870 } 871 872 /** This differs from isSwitchingState in that we take into account how far the transition 873 * has completed. */ isFinishedSwitchingState()874 public boolean isFinishedSwitchingState() { 875 return !mIsSwitchingState 876 || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS); 877 } 878 879 @Override dispatchUnhandledMove(View focused, int direction)880 public boolean dispatchUnhandledMove(View focused, int direction) { 881 if (workspaceInModalState() || !isFinishedSwitchingState()) { 882 // when the home screens are shrunken, shouldn't allow side-scrolling 883 return false; 884 } 885 return super.dispatchUnhandledMove(focused, direction); 886 } 887 888 @Override onInterceptTouchEvent(MotionEvent ev)889 public boolean onInterceptTouchEvent(MotionEvent ev) { 890 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 891 mXDown = ev.getX(); 892 mYDown = ev.getY(); 893 } 894 return super.onInterceptTouchEvent(ev); 895 } 896 897 @Override determineScrollingStart(MotionEvent ev)898 protected void determineScrollingStart(MotionEvent ev) { 899 if (!isFinishedSwitchingState()) return; 900 901 float deltaX = ev.getX() - mXDown; 902 float absDeltaX = Math.abs(deltaX); 903 float absDeltaY = Math.abs(ev.getY() - mYDown); 904 905 if (Float.compare(absDeltaX, 0f) == 0) return; 906 907 float slope = absDeltaY / absDeltaX; 908 float theta = (float) Math.atan(slope); 909 910 if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) { 911 cancelCurrentPageLongPress(); 912 } 913 914 if (theta > MAX_SWIPE_ANGLE) { 915 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace 916 return; 917 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) { 918 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to 919 // increase the touch slop to make it harder to begin scrolling the workspace. This 920 // results in vertically scrolling widgets to more easily. The higher the angle, the 921 // more we increase touch slop. 922 theta -= START_DAMPING_TOUCH_SLOP_ANGLE; 923 float extraRatio = (float) 924 Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE))); 925 super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio); 926 } else { 927 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special 928 super.determineScrollingStart(ev); 929 } 930 } 931 onPageBeginTransition()932 protected void onPageBeginTransition() { 933 super.onPageBeginTransition(); 934 updateChildrenLayersEnabled(); 935 } 936 onPageEndTransition()937 protected void onPageEndTransition() { 938 super.onPageEndTransition(); 939 updateChildrenLayersEnabled(); 940 941 if (mDragController.isDragging()) { 942 if (workspaceInModalState()) { 943 // If we are in springloaded mode, then force an event to check if the current touch 944 // is under a new page (to scroll to) 945 mDragController.forceTouchMove(); 946 } 947 } 948 949 if (mStripScreensOnPageStopMoving) { 950 stripEmptyScreens(); 951 mStripScreensOnPageStopMoving = false; 952 } 953 } 954 setLauncherOverlay(LauncherOverlay overlay)955 public void setLauncherOverlay(LauncherOverlay overlay) { 956 mOverlayEdgeEffect = overlay == null ? null : new OverlayEdgeEffect(getContext(), overlay); 957 EdgeEffectCompat newEffect = overlay == null 958 ? new EdgeEffectCompat(getContext()) : mOverlayEdgeEffect; 959 if (mIsRtl) { 960 mEdgeGlowRight = newEffect; 961 } else { 962 mEdgeGlowLeft = newEffect; 963 } 964 onOverlayScrollChanged(0); 965 } 966 hasOverlay()967 public boolean hasOverlay() { 968 return mOverlayEdgeEffect != null; 969 } 970 971 @Override snapToDestination()972 protected void snapToDestination() { 973 if (mOverlayEdgeEffect != null && !mOverlayEdgeEffect.isFinished()) { 974 snapToPageImmediately(0); 975 } else { 976 super.snapToDestination(); 977 } 978 } 979 980 @Override onScrollChanged(int l, int t, int oldl, int oldt)981 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 982 super.onScrollChanged(l, t, oldl, oldt); 983 984 // Update the page indicator progress. 985 // Unlike from other states, we show the page indicator when transitioning from HINT_STATE. 986 boolean isSwitchingState = mIsSwitchingState 987 && mLauncher.getStateManager().getCurrentStableState() != HINT_STATE; 988 boolean isTransitioning = isSwitchingState 989 || (getLayoutTransition() != null && getLayoutTransition().isRunning()); 990 if (!isTransitioning) { 991 showPageIndicatorAtCurrentScroll(); 992 } 993 994 updatePageAlphaValues(); 995 updatePageScrollValues(); 996 enableHwLayersOnVisiblePages(); 997 } 998 showPageIndicatorAtCurrentScroll()999 public void showPageIndicatorAtCurrentScroll() { 1000 if (mPageIndicator != null) { 1001 mPageIndicator.setScroll(getScrollX(), computeMaxScroll()); 1002 } 1003 } 1004 1005 @Override shouldFlingForVelocity(int velocityX)1006 protected boolean shouldFlingForVelocity(int velocityX) { 1007 // When the overlay is moving, the fling or settle transition is controlled by the overlay. 1008 return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 && 1009 super.shouldFlingForVelocity(velocityX); 1010 } 1011 1012 /** 1013 * The overlay scroll is being controlled locally, just update our overlay effect 1014 */ onOverlayScrollChanged(float scroll)1015 public void onOverlayScrollChanged(float scroll) { 1016 if (Float.compare(scroll, 1f) == 0) { 1017 if (!mOverlayShown) { 1018 mLauncher.getStatsLogManager().logger() 1019 .withSrcState(LAUNCHER_STATE_HOME) 1020 .withDstState(LAUNCHER_STATE_HOME) 1021 .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder() 1022 .setWorkspace( 1023 LauncherAtom.WorkspaceContainer.newBuilder() 1024 .setPageIndex(0)) 1025 .build()) 1026 .log(LAUNCHER_SWIPELEFT); 1027 } 1028 mOverlayShown = true; 1029 // Not announcing the overlay page for accessibility since it announces itself. 1030 } else if (Float.compare(scroll, 0f) == 0) { 1031 if (mOverlayShown) { 1032 // TODO: this is logged unnecessarily on home gesture. 1033 mLauncher.getStatsLogManager().logger() 1034 .withSrcState(LAUNCHER_STATE_HOME) 1035 .withDstState(LAUNCHER_STATE_HOME) 1036 .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder() 1037 .setWorkspace( 1038 LauncherAtom.WorkspaceContainer.newBuilder() 1039 .setPageIndex(-1)) 1040 .build()) 1041 .log(LAUNCHER_SWIPERIGHT); 1042 } else if (Float.compare(mOverlayTranslation, 0f) != 0) { 1043 // When arriving to 0 overscroll from non-zero overscroll, announce page for 1044 // accessibility since default announcements were disabled while in overscroll 1045 // state. 1046 // Not doing this if mOverlayShown because in that case the accessibility service 1047 // will announce the launcher window description upon regaining focus after 1048 // switching from the overlay screen. 1049 announcePageForAccessibility(); 1050 } 1051 mOverlayShown = false; 1052 tryRunOverlayCallback(); 1053 } 1054 1055 float offset = 0f; 1056 1057 scroll = Math.max(scroll - offset, 0); 1058 scroll = Math.min(1, scroll / (1 - offset)); 1059 1060 float alpha = 1 - Interpolators.DEACCEL_3.getInterpolation(scroll); 1061 float transX = mLauncher.getDragLayer().getMeasuredWidth() * scroll; 1062 1063 if (mIsRtl) { 1064 transX = -transX; 1065 } 1066 mOverlayTranslation = transX; 1067 1068 // TODO(adamcohen): figure out a final effect here. We may need to recommend 1069 // different effects based on device performance. On at least one relatively high-end 1070 // device I've tried, translating the launcher causes things to get quite laggy. 1071 mLauncher.getDragLayer().setTranslationX(transX); 1072 mLauncher.getDragLayer().getAlphaProperty(ALPHA_INDEX_OVERLAY).setValue(alpha); 1073 } 1074 1075 /** 1076 * @return false if the callback is still pending 1077 */ tryRunOverlayCallback()1078 private boolean tryRunOverlayCallback() { 1079 if (mOnOverlayHiddenCallback == null) { 1080 // Return true as no callback is pending. This is used by OnWindowFocusChangeListener 1081 // to remove itself if multiple focus handles were added. 1082 return true; 1083 } 1084 if (mOverlayShown || !hasWindowFocus()) { 1085 return false; 1086 } 1087 1088 mOnOverlayHiddenCallback.run(); 1089 mOnOverlayHiddenCallback = null; 1090 return true; 1091 } 1092 1093 /** 1094 * Runs the given callback when the minus one overlay is hidden. Specifically, it is run 1095 * when launcher's window has focus and the overlay is no longer being shown. If a callback 1096 * is already present, the new callback will chain off it so both are run. 1097 * 1098 * @return Whether the callback was deferred. 1099 */ runOnOverlayHidden(Runnable callback)1100 public boolean runOnOverlayHidden(Runnable callback) { 1101 if (mOnOverlayHiddenCallback == null) { 1102 mOnOverlayHiddenCallback = callback; 1103 } else { 1104 // Chain the new callback onto the previous callback(s). 1105 Runnable oldCallback = mOnOverlayHiddenCallback; 1106 mOnOverlayHiddenCallback = () -> { 1107 oldCallback.run(); 1108 callback.run(); 1109 }; 1110 } 1111 if (!tryRunOverlayCallback()) { 1112 ViewTreeObserver observer = getViewTreeObserver(); 1113 if (observer != null && observer.isAlive()) { 1114 observer.addOnWindowFocusChangeListener( 1115 new ViewTreeObserver.OnWindowFocusChangeListener() { 1116 @Override 1117 public void onWindowFocusChanged(boolean hasFocus) { 1118 if (tryRunOverlayCallback() && observer.isAlive()) { 1119 observer.removeOnWindowFocusChangeListener(this); 1120 } 1121 }}); 1122 } 1123 return true; 1124 } 1125 return false; 1126 } 1127 1128 @Override notifyPageSwitchListener(int prevPage)1129 protected void notifyPageSwitchListener(int prevPage) { 1130 super.notifyPageSwitchListener(prevPage); 1131 if (prevPage != mCurrentPage) { 1132 StatsLogManager.EventEnum event = (prevPage < mCurrentPage) 1133 ? LAUNCHER_SWIPERIGHT : LAUNCHER_SWIPELEFT; 1134 mLauncher.getStatsLogManager().logger() 1135 .withSrcState(LAUNCHER_STATE_HOME) 1136 .withDstState(LAUNCHER_STATE_HOME) 1137 .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder() 1138 .setWorkspace( 1139 LauncherAtom.WorkspaceContainer.newBuilder() 1140 .setPageIndex(prevPage)).build()) 1141 .log(event); 1142 } 1143 } 1144 setWallpaperDimension()1145 protected void setWallpaperDimension() { 1146 Executors.THREAD_POOL_EXECUTOR.execute(new Runnable() { 1147 @Override 1148 public void run() { 1149 final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize; 1150 if (size.x != mWallpaperManager.getDesiredMinimumWidth() 1151 || size.y != mWallpaperManager.getDesiredMinimumHeight()) { 1152 mWallpaperManager.suggestDesiredDimensions(size.x, size.y); 1153 } 1154 } 1155 }); 1156 } 1157 lockWallpaperToDefaultPage()1158 public void lockWallpaperToDefaultPage() { 1159 mWallpaperOffset.setLockToDefaultPage(true); 1160 } 1161 unlockWallpaperFromDefaultPageOnNextLayout()1162 public void unlockWallpaperFromDefaultPageOnNextLayout() { 1163 if (mWallpaperOffset.isLockedToDefaultPage()) { 1164 mUnlockWallpaperFromDefaultPageOnLayout = true; 1165 requestLayout(); 1166 } 1167 } 1168 1169 @Override computeScroll()1170 public void computeScroll() { 1171 super.computeScroll(); 1172 mWallpaperOffset.syncWithScroll(); 1173 } 1174 1175 @Override announceForAccessibility(CharSequence text)1176 public void announceForAccessibility(CharSequence text) { 1177 // Don't announce if apps is on top of us. 1178 if (!mLauncher.isInState(ALL_APPS)) { 1179 super.announceForAccessibility(text); 1180 } 1181 } 1182 updatePageAlphaValues()1183 private void updatePageAlphaValues() { 1184 // We need to check the isDragging case because updatePageAlphaValues is called between 1185 // goToState(SPRING_LOADED) and onStartStateTransition. 1186 if (!workspaceInModalState() && !mIsSwitchingState && !mDragController.isDragging()) { 1187 int screenCenter = getScrollX() + getMeasuredWidth() / 2; 1188 for (int i = 0; i < getChildCount(); i++) { 1189 CellLayout child = (CellLayout) getChildAt(i); 1190 if (child != null) { 1191 float scrollProgress = getScrollProgress(screenCenter, child, i); 1192 float alpha = 1 - Math.abs(scrollProgress); 1193 if (mWorkspaceFadeInAdjacentScreens) { 1194 child.getShortcutsAndWidgets().setAlpha(alpha); 1195 } else { 1196 // Pages that are off-screen aren't important for accessibility. 1197 child.getShortcutsAndWidgets().setImportantForAccessibility( 1198 alpha > 0 ? IMPORTANT_FOR_ACCESSIBILITY_AUTO 1199 : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 1200 } 1201 } 1202 } 1203 } 1204 } 1205 updatePageScrollValues()1206 private void updatePageScrollValues() { 1207 int screenCenter = getScrollX() + getMeasuredWidth() / 2; 1208 for (int i = 0; i < getChildCount(); i++) { 1209 CellLayout child = (CellLayout) getChildAt(i); 1210 if (child != null) { 1211 float scrollProgress = getScrollProgress(screenCenter, child, i); 1212 child.setScrollProgress(scrollProgress); 1213 } 1214 } 1215 } 1216 onAttachedToWindow()1217 protected void onAttachedToWindow() { 1218 super.onAttachedToWindow(); 1219 mWallpaperOffset.setWindowToken(getWindowToken()); 1220 computeScroll(); 1221 } 1222 onDetachedFromWindow()1223 protected void onDetachedFromWindow() { 1224 super.onDetachedFromWindow(); 1225 mWallpaperOffset.setWindowToken(null); 1226 } 1227 1228 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1229 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1230 if (mUnlockWallpaperFromDefaultPageOnLayout) { 1231 mWallpaperOffset.setLockToDefaultPage(false); 1232 mUnlockWallpaperFromDefaultPageOnLayout = false; 1233 } 1234 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { 1235 mWallpaperOffset.syncWithScroll(); 1236 mWallpaperOffset.jumpToFinal(); 1237 } 1238 super.onLayout(changed, left, top, right, bottom); 1239 updatePageAlphaValues(); 1240 } 1241 1242 @Override getDescendantFocusability()1243 public int getDescendantFocusability() { 1244 if (workspaceInModalState()) { 1245 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 1246 } 1247 return super.getDescendantFocusability(); 1248 } 1249 workspaceInModalState()1250 private boolean workspaceInModalState() { 1251 return !mLauncher.isInState(NORMAL); 1252 } 1253 workspaceInScrollableState()1254 private boolean workspaceInScrollableState() { 1255 return mLauncher.isInState(SPRING_LOADED) || !workspaceInModalState(); 1256 } 1257 1258 /** Returns whether a drag should be allowed to be started from the current workspace state. */ workspaceIconsCanBeDragged()1259 public boolean workspaceIconsCanBeDragged() { 1260 return mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED); 1261 } 1262 updateChildrenLayersEnabled()1263 private void updateChildrenLayersEnabled() { 1264 boolean enableChildrenLayers = mIsSwitchingState || isPageInTransition(); 1265 1266 if (enableChildrenLayers != mChildrenLayersEnabled) { 1267 mChildrenLayersEnabled = enableChildrenLayers; 1268 if (mChildrenLayersEnabled) { 1269 enableHwLayersOnVisiblePages(); 1270 } else { 1271 for (int i = 0; i < getPageCount(); i++) { 1272 final CellLayout cl = (CellLayout) getChildAt(i); 1273 cl.enableHardwareLayer(false); 1274 } 1275 } 1276 } 1277 } 1278 enableHwLayersOnVisiblePages()1279 private void enableHwLayersOnVisiblePages() { 1280 if (mChildrenLayersEnabled) { 1281 final int screenCount = getChildCount(); 1282 1283 final int[] visibleScreens = getVisibleChildrenRange(); 1284 int leftScreen = visibleScreens[0]; 1285 int rightScreen = visibleScreens[1]; 1286 if (mForceDrawAdjacentPages) { 1287 // In overview mode, make sure that the two side pages are visible. 1288 leftScreen = Utilities.boundToRange(getCurrentPage() - 1, 0, rightScreen); 1289 rightScreen = Utilities.boundToRange(getCurrentPage() + 1, 1290 leftScreen, getPageCount() - 1); 1291 } 1292 1293 if (leftScreen == rightScreen) { 1294 // make sure we're caching at least two pages always 1295 if (rightScreen < screenCount - 1) { 1296 rightScreen++; 1297 } else if (leftScreen > 0) { 1298 leftScreen--; 1299 } 1300 } 1301 1302 for (int i = 0; i < screenCount; i++) { 1303 final CellLayout layout = (CellLayout) getPageAt(i); 1304 // enable layers between left and right screen inclusive. 1305 boolean enableLayer = leftScreen <= i && i <= rightScreen; 1306 layout.enableHardwareLayer(enableLayer); 1307 } 1308 } 1309 } 1310 onWallpaperTap(MotionEvent ev)1311 public void onWallpaperTap(MotionEvent ev) { 1312 final int[] position = mTempXY; 1313 getLocationOnScreen(position); 1314 1315 int pointerIndex = ev.getActionIndex(); 1316 position[0] += (int) ev.getX(pointerIndex); 1317 position[1] += (int) ev.getY(pointerIndex); 1318 1319 mWallpaperManager.sendWallpaperCommand(getWindowToken(), 1320 ev.getAction() == MotionEvent.ACTION_UP 1321 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP, 1322 position[0], position[1], 0, null); 1323 } 1324 prepareDragWithProvider(DragPreviewProvider outlineProvider)1325 public void prepareDragWithProvider(DragPreviewProvider outlineProvider) { 1326 mOutlineProvider = outlineProvider; 1327 } 1328 onStartStateTransition(LauncherState state)1329 private void onStartStateTransition(LauncherState state) { 1330 mIsSwitchingState = true; 1331 mTransitionProgress = 0; 1332 1333 updateChildrenLayersEnabled(); 1334 } 1335 onEndStateTransition()1336 private void onEndStateTransition() { 1337 mIsSwitchingState = false; 1338 mForceDrawAdjacentPages = false; 1339 mTransitionProgress = 1; 1340 1341 updateChildrenLayersEnabled(); 1342 updateAccessibilityFlags(); 1343 } 1344 1345 /** 1346 * Sets the current workspace {@link LauncherState} and updates the UI without any animations 1347 */ 1348 @Override setState(LauncherState toState)1349 public void setState(LauncherState toState) { 1350 onStartStateTransition(toState); 1351 mStateTransitionAnimation.setState(toState); 1352 onEndStateTransition(); 1353 } 1354 1355 /** 1356 * Sets the current workspace {@link LauncherState}, then animates the UI 1357 */ 1358 @Override setStateWithAnimation( LauncherState toState, StateAnimationConfig config, PendingAnimation animation)1359 public void setStateWithAnimation( 1360 LauncherState toState, StateAnimationConfig config, PendingAnimation animation) { 1361 StateTransitionListener listener = new StateTransitionListener(toState); 1362 mStateTransitionAnimation.setStateWithAnimation(toState, config, animation); 1363 1364 // Invalidate the pages now, so that we have the visible pages before the 1365 // animation is started 1366 if (toState.hasFlag(FLAG_MULTI_PAGE)) { 1367 mForceDrawAdjacentPages = true; 1368 } 1369 invalidate(); // This will call dispatchDraw(), which calls getVisiblePages(). 1370 1371 ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1); 1372 stepAnimator.addUpdateListener(listener); 1373 stepAnimator.addListener(listener); 1374 animation.add(stepAnimator); 1375 } 1376 getStateTransitionAnimation()1377 public WorkspaceStateTransitionAnimation getStateTransitionAnimation() { 1378 return mStateTransitionAnimation; 1379 } 1380 updateAccessibilityFlags()1381 public void updateAccessibilityFlags() { 1382 // TODO: Update the accessibility flags appropriately when dragging. 1383 int accessibilityFlag = 1384 mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_INACCESSIBLE) 1385 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS 1386 : IMPORTANT_FOR_ACCESSIBILITY_AUTO; 1387 if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { 1388 int total = getPageCount(); 1389 for (int i = 0; i < total; i++) { 1390 updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i)); 1391 } 1392 setImportantForAccessibility(accessibilityFlag); 1393 } 1394 } 1395 1396 @Override createAccessibilityNodeInfo()1397 public AccessibilityNodeInfo createAccessibilityNodeInfo() { 1398 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { 1399 // TAPL tests verify that workspace is not present in Overview and AllApps states. 1400 // TAPL can work only if UIDevice is set up as setCompressedLayoutHeirarchy(false). 1401 // Hiding workspace from the tests when it's 1402 // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS. 1403 return AccessibilityNodeInfo.obtain(); 1404 } 1405 return super.createAccessibilityNodeInfo(); 1406 } 1407 updateAccessibilityFlags(int accessibilityFlag, CellLayout page)1408 private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) { 1409 page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 1410 page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag); 1411 page.setContentDescription(null); 1412 page.setAccessibilityDelegate(null); 1413 } 1414 startDrag(CellLayout.CellInfo cellInfo, DragOptions options)1415 public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) { 1416 View child = cellInfo.cell; 1417 1418 mDragInfo = cellInfo; 1419 child.setVisibility(INVISIBLE); 1420 1421 if (options.isAccessibleDrag) { 1422 mDragController.addDragListener( 1423 new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) { 1424 @Override 1425 protected void enableAccessibleDrag(boolean enable) { 1426 super.enableAccessibleDrag(enable); 1427 setEnableForLayout(mLauncher.getHotseat(), enable); 1428 } 1429 }); 1430 } 1431 1432 beginDragShared(child, this, options); 1433 } 1434 beginDragShared(View child, DragSource source, DragOptions options)1435 public void beginDragShared(View child, DragSource source, DragOptions options) { 1436 Object dragObject = child.getTag(); 1437 if (!(dragObject instanceof ItemInfo)) { 1438 String msg = "Drag started with a view that has no tag set. This " 1439 + "will cause a crash (issue 11627249) down the line. " 1440 + "View: " + child + " tag: " + child.getTag(); 1441 throw new IllegalStateException(msg); 1442 } 1443 beginDragShared(child, null, source, (ItemInfo) dragObject, 1444 new DragPreviewProvider(child), options); 1445 } 1446 1447 /** 1448 * Core functionality for beginning a drag operation for an item that will be dropped within 1449 * the workspace 1450 */ beginDragShared(View child, DraggableView draggableView, DragSource source, ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions)1451 public DragView beginDragShared(View child, DraggableView draggableView, DragSource source, 1452 ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) { 1453 1454 float iconScale = 1f; 1455 if (child instanceof BubbleTextView) { 1456 Drawable icon = ((BubbleTextView) child).getIcon(); 1457 if (icon instanceof FastBitmapDrawable) { 1458 iconScale = ((FastBitmapDrawable) icon).getAnimatedScale(); 1459 } 1460 } 1461 1462 // Clear the pressed state if necessary 1463 child.clearFocus(); 1464 child.setPressed(false); 1465 if (child instanceof BubbleTextView) { 1466 BubbleTextView icon = (BubbleTextView) child; 1467 icon.clearPressedBackground(); 1468 } 1469 1470 mOutlineProvider = previewProvider; 1471 1472 if (draggableView == null && child instanceof DraggableView) { 1473 draggableView = (DraggableView) child; 1474 } 1475 1476 final View contentView = previewProvider.getContentView(); 1477 final float scale; 1478 // The draggable drawable follows the touch point around on the screen 1479 final Drawable drawable; 1480 if (contentView == null) { 1481 drawable = previewProvider.createDrawable(); 1482 scale = previewProvider.getScaleAndPosition(drawable, mTempXY); 1483 } else { 1484 drawable = null; 1485 scale = previewProvider.getScaleAndPosition(contentView, mTempXY); 1486 } 1487 1488 int halfPadding = previewProvider.previewPadding / 2; 1489 int dragLayerX = mTempXY[0]; 1490 int dragLayerY = mTempXY[1]; 1491 1492 Point dragVisualizeOffset = null; 1493 Rect dragRect = new Rect(); 1494 1495 if (draggableView != null) { 1496 draggableView.getSourceVisualDragBounds(dragRect); 1497 dragLayerY += dragRect.top; 1498 dragVisualizeOffset = new Point(- halfPadding, halfPadding); 1499 } 1500 1501 1502 if (child.getParent() instanceof ShortcutAndWidgetContainer) { 1503 mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); 1504 } 1505 1506 if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) { 1507 PopupContainerWithArrow popupContainer = PopupContainerWithArrow 1508 .showForIcon((BubbleTextView) child); 1509 if (popupContainer != null) { 1510 dragOptions.preDragCondition = popupContainer.createPreDragCondition(); 1511 } 1512 } 1513 1514 final DragView dv; 1515 if (contentView instanceof View) { 1516 if (contentView instanceof LauncherAppWidgetHostView) { 1517 mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher)); 1518 } 1519 dv = mDragController.startDrag( 1520 contentView, 1521 draggableView, 1522 dragLayerX, 1523 dragLayerY, 1524 source, 1525 dragObject, 1526 dragVisualizeOffset, 1527 dragRect, 1528 scale * iconScale, 1529 scale, 1530 dragOptions); 1531 } else { 1532 dv = mDragController.startDrag( 1533 drawable, 1534 draggableView, 1535 dragLayerX, 1536 dragLayerY, 1537 source, 1538 dragObject, 1539 dragVisualizeOffset, 1540 dragRect, 1541 scale * iconScale, 1542 scale, 1543 dragOptions); 1544 } 1545 return dv; 1546 } 1547 transitionStateShouldAllowDrop()1548 private boolean transitionStateShouldAllowDrop() { 1549 return (!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) && 1550 workspaceIconsCanBeDragged(); 1551 } 1552 1553 /** 1554 * {@inheritDoc} 1555 */ 1556 @Override acceptDrop(DragObject d)1557 public boolean acceptDrop(DragObject d) { 1558 // If it's an external drop (e.g. from All Apps), check if it should be accepted 1559 CellLayout dropTargetLayout = mDropToLayout; 1560 if (d.dragSource != this) { 1561 // Don't accept the drop if we're not over a screen at time of drop 1562 if (dropTargetLayout == null) { 1563 return false; 1564 } 1565 if (!transitionStateShouldAllowDrop()) return false; 1566 1567 mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); 1568 1569 // We want the point to be mapped to the dragTarget. 1570 mapPointFromDropLayout(dropTargetLayout, mDragViewVisualCenter); 1571 1572 int spanX; 1573 int spanY; 1574 if (mDragInfo != null) { 1575 final CellLayout.CellInfo dragCellInfo = mDragInfo; 1576 spanX = dragCellInfo.spanX; 1577 spanY = dragCellInfo.spanY; 1578 } else { 1579 spanX = d.dragInfo.spanX; 1580 spanY = d.dragInfo.spanY; 1581 } 1582 1583 int minSpanX = spanX; 1584 int minSpanY = spanY; 1585 if (d.dragInfo instanceof PendingAddWidgetInfo) { 1586 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX; 1587 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY; 1588 } 1589 1590 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 1591 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout, 1592 mTargetCell); 1593 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 1594 mDragViewVisualCenter[1], mTargetCell); 1595 if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo, 1596 dropTargetLayout, mTargetCell, distance, true)) { 1597 return true; 1598 } 1599 1600 if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo, 1601 dropTargetLayout, mTargetCell, distance)) { 1602 return true; 1603 } 1604 1605 int[] resultSpan = new int[2]; 1606 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0], 1607 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 1608 null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP); 1609 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 1610 1611 // Don't accept the drop if there's no room for the item 1612 if (!foundCell) { 1613 onNoCellFound(dropTargetLayout); 1614 return false; 1615 } 1616 } 1617 1618 int screenId = getIdForScreen(dropTargetLayout); 1619 if (screenId == EXTRA_EMPTY_SCREEN_ID) { 1620 commitExtraEmptyScreen(); 1621 } 1622 1623 return true; 1624 } 1625 willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float distance, boolean considerTimeout)1626 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, 1627 float distance, boolean considerTimeout) { 1628 if (distance > mMaxDistanceForFolderCreation) return false; 1629 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 1630 return willCreateUserFolder(info, dropOverView, considerTimeout); 1631 } 1632 willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout)1633 boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) { 1634 if (dropOverView != null) { 1635 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 1636 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) { 1637 return false; 1638 } 1639 } 1640 1641 boolean hasntMoved = false; 1642 if (mDragInfo != null) { 1643 hasntMoved = dropOverView == mDragInfo.cell; 1644 } 1645 1646 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) { 1647 return false; 1648 } 1649 1650 boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo 1651 && ((WorkspaceItemInfo) dropOverView.getTag()).container 1652 != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION); 1653 boolean willBecomeShortcut = 1654 (info.itemType == ITEM_TYPE_APPLICATION || 1655 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT || 1656 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT); 1657 1658 return (aboveShortcut && willBecomeShortcut); 1659 } 1660 willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, float distance)1661 boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, 1662 float distance) { 1663 if (distance > mMaxDistanceForFolderCreation) return false; 1664 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 1665 return willAddToExistingUserFolder(dragInfo, dropOverView); 1666 1667 } willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView)1668 boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) { 1669 if (dropOverView != null) { 1670 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 1671 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) { 1672 return false; 1673 } 1674 } 1675 1676 if (dropOverView instanceof FolderIcon) { 1677 FolderIcon fi = (FolderIcon) dropOverView; 1678 if (fi.acceptDrop(dragInfo)) { 1679 return true; 1680 } 1681 } 1682 return false; 1683 } 1684 createUserFolderIfNecessary(View newView, int container, CellLayout target, int[] targetCell, float distance, boolean external, DragObject d)1685 boolean createUserFolderIfNecessary(View newView, int container, CellLayout target, 1686 int[] targetCell, float distance, boolean external, DragObject d) { 1687 if (distance > mMaxDistanceForFolderCreation) return false; 1688 View v = target.getChildAt(targetCell[0], targetCell[1]); 1689 1690 boolean hasntMoved = false; 1691 if (mDragInfo != null) { 1692 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); 1693 hasntMoved = (mDragInfo.cellX == targetCell[0] && 1694 mDragInfo.cellY == targetCell[1]) && (cellParent == target); 1695 } 1696 1697 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; 1698 mCreateUserFolderOnDrop = false; 1699 final int screenId = getIdForScreen(target); 1700 1701 boolean aboveShortcut = (v.getTag() instanceof WorkspaceItemInfo); 1702 boolean willBecomeShortcut = (newView.getTag() instanceof WorkspaceItemInfo); 1703 1704 if (aboveShortcut && willBecomeShortcut) { 1705 WorkspaceItemInfo sourceInfo = (WorkspaceItemInfo) newView.getTag(); 1706 WorkspaceItemInfo destInfo = (WorkspaceItemInfo) v.getTag(); 1707 // if the drag started here, we need to remove it from the workspace 1708 if (!external) { 1709 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 1710 } 1711 1712 Rect folderLocation = new Rect(); 1713 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation); 1714 target.removeView(v); 1715 mStatsLogManager.logger().withItemInfo(destInfo).withInstanceId(d.logInstanceId) 1716 .log(LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED); 1717 FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0], 1718 targetCell[1]); 1719 destInfo.cellX = -1; 1720 destInfo.cellY = -1; 1721 sourceInfo.cellX = -1; 1722 sourceInfo.cellY = -1; 1723 1724 // If the dragView is null, we can't animate 1725 boolean animate = d != null; 1726 if (animate) { 1727 // In order to keep everything continuous, we hand off the currently rendered 1728 // folder background to the newly created icon. This preserves animation state. 1729 fi.setFolderBackground(mFolderCreateBg); 1730 mFolderCreateBg = new PreviewBackground(); 1731 fi.performCreateAnimation(destInfo, v, sourceInfo, d, folderLocation, scale); 1732 } else { 1733 fi.prepareCreateAnimation(v); 1734 fi.addItem(destInfo); 1735 fi.addItem(sourceInfo); 1736 } 1737 return true; 1738 } 1739 return false; 1740 } 1741 addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, float distance, DragObject d, boolean external)1742 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, 1743 float distance, DragObject d, boolean external) { 1744 if (distance > mMaxDistanceForFolderCreation) return false; 1745 1746 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 1747 if (!mAddToExistingFolderOnDrop) return false; 1748 mAddToExistingFolderOnDrop = false; 1749 1750 if (dropOverView instanceof FolderIcon) { 1751 FolderIcon fi = (FolderIcon) dropOverView; 1752 if (fi.acceptDrop(d.dragInfo)) { 1753 mStatsLogManager.logger().withItemInfo(fi.mInfo).withInstanceId(d.logInstanceId) 1754 .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED_ON_FOLDER_ICON); 1755 fi.onDrop(d, false /* itemReturnedOnFailedDrop */); 1756 // if the drag started here, we need to remove it from the workspace 1757 if (!external) { 1758 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 1759 } 1760 return true; 1761 } 1762 } 1763 return false; 1764 } 1765 1766 @Override prepareAccessibilityDrop()1767 public void prepareAccessibilityDrop() { } 1768 1769 @Override onDrop(final DragObject d, DragOptions options)1770 public void onDrop(final DragObject d, DragOptions options) { 1771 mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); 1772 CellLayout dropTargetLayout = mDropToLayout; 1773 1774 // We want the point to be mapped to the dragTarget. 1775 if (dropTargetLayout != null) { 1776 mapPointFromDropLayout(dropTargetLayout, mDragViewVisualCenter); 1777 } 1778 1779 boolean droppedOnOriginalCell = false; 1780 1781 int snapScreen = -1; 1782 boolean resizeOnDrop = false; 1783 if (d.dragSource != this || mDragInfo == null) { 1784 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], 1785 (int) mDragViewVisualCenter[1] }; 1786 onDropExternal(touchXY, dropTargetLayout, d); 1787 } else { 1788 final View cell = mDragInfo.cell; 1789 final DragView dragView = d.dragView; 1790 boolean droppedOnOriginalCellDuringTransition = false; 1791 Runnable onCompleteRunnable = dragView::resumeColorExtraction; 1792 1793 dragView.disableColorExtraction(); 1794 1795 if (dropTargetLayout != null && !d.cancelled) { 1796 // Move internally 1797 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); 1798 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 1799 int container = hasMovedIntoHotseat ? 1800 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 1801 LauncherSettings.Favorites.CONTAINER_DESKTOP; 1802 int screenId = (mTargetCell[0] < 0) ? 1803 mDragInfo.screenId : getIdForScreen(dropTargetLayout); 1804 int spanX = mDragInfo != null ? mDragInfo.spanX : 1; 1805 int spanY = mDragInfo != null ? mDragInfo.spanY : 1; 1806 // First we find the cell nearest to point at which the item is 1807 // dropped, without any consideration to whether there is an item there. 1808 1809 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) 1810 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); 1811 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 1812 mDragViewVisualCenter[1], mTargetCell); 1813 1814 // If the item being dropped is a shortcut and the nearest drop 1815 // cell also contains a shortcut, then create a folder with the two shortcuts. 1816 if (createUserFolderIfNecessary(cell, container, 1817 dropTargetLayout, mTargetCell, distance, false, d) 1818 || addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, 1819 distance, d, false)) { 1820 mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); 1821 return; 1822 } 1823 1824 // Aside from the special case where we're dropping a shortcut onto a shortcut, 1825 // we need to find the nearest cell location that is vacant 1826 ItemInfo item = d.dragInfo; 1827 int minSpanX = item.spanX; 1828 int minSpanY = item.spanY; 1829 if (item.minSpanX > 0 && item.minSpanY > 0) { 1830 minSpanX = item.minSpanX; 1831 minSpanY = item.minSpanY; 1832 } 1833 1834 droppedOnOriginalCell = item.screenId == screenId && item.container == container 1835 && item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1]; 1836 droppedOnOriginalCellDuringTransition = droppedOnOriginalCell && mIsSwitchingState; 1837 1838 // When quickly moving an item, a user may accidentally rearrange their 1839 // workspace. So instead we move the icon back safely to its original position. 1840 boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState() 1841 && !droppedOnOriginalCellDuringTransition && !dropTargetLayout 1842 .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY); 1843 int[] resultSpan = new int[2]; 1844 if (returnToOriginalCellToPreventShuffling) { 1845 mTargetCell[0] = mTargetCell[1] = -1; 1846 } else { 1847 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0], 1848 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, 1849 mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); 1850 } 1851 1852 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 1853 1854 // if the widget resizes on drop 1855 if (foundCell && (cell instanceof AppWidgetHostView) && 1856 (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) { 1857 resizeOnDrop = true; 1858 item.spanX = resultSpan[0]; 1859 item.spanY = resultSpan[1]; 1860 AppWidgetHostView awhv = (AppWidgetHostView) cell; 1861 WidgetSizes.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], 1862 resultSpan[1]); 1863 } 1864 1865 if (foundCell) { 1866 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) { 1867 snapScreen = getPageIndexForScreenId(screenId); 1868 snapToPage(snapScreen); 1869 } 1870 1871 final ItemInfo info = (ItemInfo) cell.getTag(); 1872 if (hasMovedLayouts) { 1873 // Reparent the view 1874 CellLayout parentCell = getParentCellLayoutForView(cell); 1875 if (parentCell != null) { 1876 parentCell.removeView(cell); 1877 } else if (mDragInfo.cell instanceof LauncherAppWidgetHostView) { 1878 d.dragView.detachContentView(/* reattachToPreviousParent= */ false); 1879 } else if (FeatureFlags.IS_STUDIO_BUILD) { 1880 throw new NullPointerException("mDragInfo.cell has null parent"); 1881 } 1882 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], 1883 info.spanX, info.spanY); 1884 } 1885 1886 // update the item's position after drop 1887 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 1888 lp.cellX = lp.tmpCellX = mTargetCell[0]; 1889 lp.cellY = lp.tmpCellY = mTargetCell[1]; 1890 lp.cellHSpan = item.spanX; 1891 lp.cellVSpan = item.spanY; 1892 lp.isLockedToGrid = true; 1893 1894 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && 1895 cell instanceof LauncherAppWidgetHostView) { 1896 final CellLayout cellLayout = dropTargetLayout; 1897 // We post this call so that the widget has a chance to be placed 1898 // in its final location 1899 1900 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; 1901 AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo(); 1902 if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE 1903 && !options.isAccessibleDrag) { 1904 final Runnable previousRunnable = onCompleteRunnable; 1905 onCompleteRunnable = () -> { 1906 previousRunnable.run(); 1907 if (!isPageInTransition()) { 1908 AppWidgetResizeFrame.showForWidget(hostView, cellLayout); 1909 } 1910 }; 1911 } 1912 } 1913 mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId, 1914 lp.cellX, lp.cellY, item.spanX, item.spanY); 1915 } else { 1916 if (!returnToOriginalCellToPreventShuffling) { 1917 onNoCellFound(dropTargetLayout); 1918 } 1919 if (mDragInfo.cell instanceof LauncherAppWidgetHostView) { 1920 d.dragView.detachContentView(/* reattachToPreviousParent= */ true); 1921 } 1922 1923 // If we can't find a drop location, we return the item to its original position 1924 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 1925 mTargetCell[0] = lp.cellX; 1926 mTargetCell[1] = lp.cellY; 1927 CellLayout layout = (CellLayout) cell.getParent().getParent(); 1928 layout.markCellsAsOccupiedForView(cell); 1929 } 1930 } else { 1931 // When drag is cancelled, reattach content view back to its original parent. 1932 if (mDragInfo.cell instanceof LauncherAppWidgetHostView) { 1933 d.dragView.detachContentView(/* reattachToPreviousParent= */ true); 1934 } 1935 } 1936 1937 final CellLayout parent = (CellLayout) cell.getParent().getParent(); 1938 if (d.dragView.hasDrawn()) { 1939 if (droppedOnOriginalCellDuringTransition) { 1940 // Animate the item to its original position, while simultaneously exiting 1941 // spring-loaded mode so the page meets the icon where it was picked up. 1942 final RunnableList callbackList = new RunnableList(); 1943 final Runnable onCompleteCallback = onCompleteRunnable; 1944 mLauncher.getDragController().animateDragViewToOriginalPosition( 1945 /* onComplete= */ callbackList::executeAllAndDestroy, cell, 1946 SPRING_LOADED.getTransitionDuration(mLauncher)); 1947 mLauncher.getStateManager().goToState(NORMAL, /* delay= */ 0, 1948 onCompleteCallback == null 1949 ? null 1950 : forSuccessCallback( 1951 () -> callbackList.add(onCompleteCallback))); 1952 mLauncher.getDropTargetBar().onDragEnd(); 1953 parent.onDropChild(cell); 1954 return; 1955 } 1956 final ItemInfo info = (ItemInfo) cell.getTag(); 1957 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET 1958 || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 1959 if (isWidget) { 1960 int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : 1961 ANIMATE_INTO_POSITION_AND_DISAPPEAR; 1962 animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false); 1963 } else { 1964 int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; 1965 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, 1966 this); 1967 } 1968 } else { 1969 d.deferDragViewCleanupPostAnimation = false; 1970 cell.setVisibility(VISIBLE); 1971 } 1972 parent.onDropChild(cell); 1973 1974 mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY, 1975 forSuccessCallback(onCompleteRunnable)); 1976 mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId) 1977 .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED); 1978 } 1979 1980 if (d.stateAnnouncer != null && !droppedOnOriginalCell) { 1981 d.stateAnnouncer.completeAction(R.string.item_moved); 1982 } 1983 } 1984 1985 public void onNoCellFound(View dropTargetLayout) { 1986 int strId = mLauncher.isHotseatLayout(dropTargetLayout) 1987 ? R.string.hotseat_out_of_space : R.string.out_of_space; 1988 Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show(); 1989 } 1990 1991 /** 1992 * Computes and returns the area relative to dragLayer which is used to display a page. 1993 * In case we have multiple pages displayed at the same time, we return the union of the areas. 1994 */ 1995 public Rect getPageAreaRelativeToDragLayer() { 1996 Rect area = new Rect(); 1997 int nextPage = getNextPage(); 1998 int panelCount = getPanelCount(); 1999 for (int page = nextPage; page < nextPage + panelCount; page++) { 2000 CellLayout child = (CellLayout) getChildAt(page); 2001 if (child == null) { 2002 break; 2003 } 2004 2005 ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets(); 2006 Rect tmpRect = new Rect(); 2007 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(boundingLayout, tmpRect); 2008 area.union(tmpRect); 2009 } 2010 2011 return area; 2012 } 2013 2014 @Override 2015 public void onDragEnter(DragObject d) { 2016 if (ENFORCE_DRAG_EVENT_ORDER) { 2017 enforceDragParity("onDragEnter", 1, 1); 2018 } 2019 2020 mCreateUserFolderOnDrop = false; 2021 mAddToExistingFolderOnDrop = false; 2022 2023 mDropToLayout = null; 2024 mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); 2025 setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1]); 2026 } 2027 2028 @Override 2029 public void onDragExit(DragObject d) { 2030 if (ENFORCE_DRAG_EVENT_ORDER) { 2031 enforceDragParity("onDragExit", -1, 0); 2032 } 2033 2034 // Here we store the final page that will be dropped to, if the workspace in fact 2035 // receives the drop 2036 mDropToLayout = mDragTargetLayout; 2037 if (mDragMode == DRAG_MODE_CREATE_FOLDER) { 2038 mCreateUserFolderOnDrop = true; 2039 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) { 2040 mAddToExistingFolderOnDrop = true; 2041 } 2042 2043 // Reset the previous drag target 2044 setCurrentDropLayout(null); 2045 setCurrentDragOverlappingLayout(null); 2046 2047 mSpringLoadedDragController.cancel(); 2048 } 2049 2050 private void enforceDragParity(String event, int update, int expectedValue) { 2051 enforceDragParity(this, event, update, expectedValue); 2052 for (int i = 0; i < getChildCount(); i++) { 2053 enforceDragParity(getChildAt(i), event, update, expectedValue); 2054 } 2055 } 2056 2057 private void enforceDragParity(View v, String event, int update, int expectedValue) { 2058 Object tag = v.getTag(R.id.drag_event_parity); 2059 int value = tag == null ? 0 : (Integer) tag; 2060 value += update; 2061 v.setTag(R.id.drag_event_parity, value); 2062 2063 if (value != expectedValue) { 2064 Log.e(TAG, event + ": Drag contract violated: " + value); 2065 } 2066 } 2067 2068 void setCurrentDropLayout(CellLayout layout) { 2069 if (mDragTargetLayout != null) { 2070 mDragTargetLayout.revertTempState(); 2071 mDragTargetLayout.onDragExit(); 2072 } 2073 mDragTargetLayout = layout; 2074 if (mDragTargetLayout != null) { 2075 mDragTargetLayout.onDragEnter(); 2076 } 2077 cleanupReorder(true); 2078 cleanupFolderCreation(); 2079 setCurrentDropOverCell(-1, -1); 2080 } 2081 2082 void setCurrentDragOverlappingLayout(CellLayout layout) { 2083 if (mDragOverlappingLayout != null) { 2084 mDragOverlappingLayout.setIsDragOverlapping(false); 2085 } 2086 mDragOverlappingLayout = layout; 2087 if (mDragOverlappingLayout != null) { 2088 mDragOverlappingLayout.setIsDragOverlapping(true); 2089 } 2090 } 2091 2092 public CellLayout getCurrentDragOverlappingLayout() { 2093 return mDragOverlappingLayout; 2094 } 2095 2096 void setCurrentDropOverCell(int x, int y) { 2097 if (x != mDragOverX || y != mDragOverY) { 2098 mDragOverX = x; 2099 mDragOverY = y; 2100 setDragMode(DRAG_MODE_NONE); 2101 } 2102 } 2103 2104 void setDragMode(int dragMode) { 2105 if (dragMode != mDragMode) { 2106 if (dragMode == DRAG_MODE_NONE) { 2107 cleanupAddToFolder(); 2108 // We don't want to cancel the re-order alarm every time the target cell changes 2109 // as this feels to slow / unresponsive. 2110 cleanupReorder(false); 2111 cleanupFolderCreation(); 2112 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) { 2113 cleanupReorder(true); 2114 cleanupFolderCreation(); 2115 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) { 2116 cleanupAddToFolder(); 2117 cleanupReorder(true); 2118 } else if (dragMode == DRAG_MODE_REORDER) { 2119 cleanupAddToFolder(); 2120 cleanupFolderCreation(); 2121 } 2122 mDragMode = dragMode; 2123 } 2124 } 2125 2126 private void cleanupFolderCreation() { 2127 if (mFolderCreateBg != null) { 2128 mFolderCreateBg.animateToRest(); 2129 } 2130 } 2131 2132 private void cleanupAddToFolder() { 2133 if (mDragOverFolderIcon != null) { 2134 mDragOverFolderIcon.onDragExit(); 2135 mDragOverFolderIcon = null; 2136 } 2137 } 2138 2139 private void cleanupReorder(boolean cancelAlarm) { 2140 // Any pending reorders are canceled 2141 if (cancelAlarm) { 2142 mReorderAlarm.cancelAlarm(); 2143 } 2144 mLastReorderX = -1; 2145 mLastReorderY = -1; 2146 } 2147 2148 /* 2149 * 2150 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 2151 * coordinate space. The argument xy is modified with the return result. 2152 */ 2153 private void mapPointFromSelfToChild(View v, float[] xy) { 2154 xy[0] = xy[0] - v.getLeft(); 2155 xy[1] = xy[1] - v.getTop(); 2156 } 2157 2158 boolean isPointInSelfOverHotseat(int x, int y) { 2159 mTempFXY[0] = x; 2160 mTempFXY[1] = y; 2161 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true); 2162 View hotseat = mLauncher.getHotseat(); 2163 return mTempFXY[0] >= hotseat.getLeft() 2164 && mTempFXY[0] <= hotseat.getRight() 2165 && mTempFXY[1] >= hotseat.getTop() 2166 && mTempFXY[1] <= hotseat.getBottom(); 2167 } 2168 2169 /** 2170 * Updates the point in {@param xy} to point to the co-ordinate space of {@param layout} 2171 * @param layout either hotseat of a page in workspace 2172 * @param xy the point location in workspace co-ordinate space 2173 */ 2174 private void mapPointFromDropLayout(CellLayout layout, float[] xy) { 2175 if (mLauncher.isHotseatLayout(layout)) { 2176 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true); 2177 mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy); 2178 } else { 2179 mapPointFromSelfToChild(layout, xy); 2180 } 2181 } 2182 2183 private boolean isDragWidget(DragObject d) { 2184 return (d.dragInfo instanceof LauncherAppWidgetInfo || 2185 d.dragInfo instanceof PendingAddWidgetInfo); 2186 } 2187 2188 public void onDragOver(DragObject d) { 2189 // Skip drag over events while we are dragging over side pages 2190 if (!transitionStateShouldAllowDrop()) return; 2191 2192 ItemInfo item = d.dragInfo; 2193 if (item == null) { 2194 if (FeatureFlags.IS_STUDIO_BUILD) { 2195 throw new NullPointerException("DragObject has null info"); 2196 } 2197 return; 2198 } 2199 2200 // Ensure that we have proper spans for the item that we are dropping 2201 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); 2202 mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); 2203 2204 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 2205 if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) { 2206 if (mLauncher.isHotseatLayout(mDragTargetLayout)) { 2207 mSpringLoadedDragController.cancel(); 2208 } else { 2209 mSpringLoadedDragController.setAlarm(mDragTargetLayout); 2210 } 2211 } 2212 2213 // Handle the drag over 2214 if (mDragTargetLayout != null) { 2215 // We want the point to be mapped to the dragTarget. 2216 mapPointFromDropLayout(mDragTargetLayout, mDragViewVisualCenter); 2217 2218 int minSpanX = item.spanX; 2219 int minSpanY = item.spanY; 2220 if (item.minSpanX > 0 && item.minSpanY > 0) { 2221 minSpanX = item.minSpanX; 2222 minSpanY = item.minSpanY; 2223 } 2224 2225 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2226 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, 2227 mDragTargetLayout, mTargetCell); 2228 int reorderX = mTargetCell[0]; 2229 int reorderY = mTargetCell[1]; 2230 2231 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]); 2232 2233 float targetCellDistance = mDragTargetLayout.getDistanceFromCell( 2234 mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); 2235 2236 manageFolderFeedback(targetCellDistance, d); 2237 2238 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) 2239 mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, 2240 item.spanY, child, mTargetCell); 2241 2242 if (!nearestDropOccupied) { 2243 mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1], 2244 item.spanX, item.spanY, d); 2245 } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) 2246 && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX || 2247 mLastReorderY != reorderY)) { 2248 2249 int[] resultSpan = new int[2]; 2250 mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0], 2251 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY, 2252 child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT); 2253 2254 // Otherwise, if we aren't adding to or creating a folder and there's no pending 2255 // reorder, then we schedule a reorder 2256 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, 2257 minSpanX, minSpanY, item.spanX, item.spanY, d, child); 2258 mReorderAlarm.setOnAlarmListener(listener); 2259 mReorderAlarm.setAlarm(REORDER_TIMEOUT); 2260 } 2261 2262 if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER || 2263 !nearestDropOccupied) { 2264 if (mDragTargetLayout != null) { 2265 mDragTargetLayout.revertTempState(); 2266 } 2267 } 2268 } 2269 } 2270 2271 /** 2272 * Updates {@link #mDragTargetLayout} and {@link #mDragOverlappingLayout} 2273 * based on the DragObject's position. 2274 * 2275 * The layout will be: 2276 * - The Hotseat if the drag object is over it 2277 * - A side page if we are in spring-loaded mode and the drag object is over it 2278 * - The current page otherwise 2279 * 2280 * @return whether the layout is different from the current {@link #mDragTargetLayout}. 2281 */ 2282 private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) { 2283 CellLayout layout = null; 2284 // Test to see if we are over the hotseat first 2285 if (mLauncher.getHotseat() != null && !isDragWidget(d)) { 2286 if (isPointInSelfOverHotseat(d.x, d.y)) { 2287 layout = mLauncher.getHotseat(); 2288 } 2289 } 2290 2291 int nextPage = getNextPage(); 2292 if (layout == null && !isPageInTransition()) { 2293 // Check if the item is dragged over currentPage - 1 page 2294 mTempTouchCoordinates[0] = Math.min(centerX, d.x); 2295 mTempTouchCoordinates[1] = d.y; 2296 layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), mTempTouchCoordinates); 2297 } 2298 2299 if (layout == null && !isPageInTransition()) { 2300 // Check if the item is dragged over currentPage + 1 page 2301 mTempTouchCoordinates[0] = Math.max(centerX, d.x); 2302 mTempTouchCoordinates[1] = d.y; 2303 layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), mTempTouchCoordinates); 2304 } 2305 2306 // If two panel is enabled, users can also drag items to currentPage + 2 2307 if (isTwoPanelEnabled() && layout == null && !isPageInTransition()) { 2308 // Check if the item is dragged over currentPage + 2 page 2309 mTempTouchCoordinates[0] = Math.max(centerX, d.x); 2310 mTempTouchCoordinates[1] = d.y; 2311 layout = verifyInsidePage(nextPage + (mIsRtl ? -2 : 2), mTempTouchCoordinates); 2312 } 2313 2314 // Always pick the current page. 2315 if (layout == null && nextPage >= 0 && nextPage < getPageCount()) { 2316 layout = (CellLayout) getChildAt(nextPage); 2317 } 2318 if (layout != mDragTargetLayout) { 2319 setCurrentDropLayout(layout); 2320 setCurrentDragOverlappingLayout(layout); 2321 return true; 2322 } 2323 return false; 2324 } 2325 2326 /** 2327 * Returns the child CellLayout if the point is inside the page coordinates, null otherwise. 2328 */ 2329 private CellLayout verifyInsidePage(int pageNo, float[] touchXy) { 2330 if (pageNo >= 0 && pageNo < getPageCount()) { 2331 CellLayout cl = (CellLayout) getChildAt(pageNo); 2332 mapPointFromSelfToChild(cl, touchXy); 2333 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 2334 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 2335 // This point is inside the cell layout 2336 return cl; 2337 } 2338 } 2339 return null; 2340 } 2341 2342 private void manageFolderFeedback(float distance, DragObject dragObject) { 2343 if (distance > mMaxDistanceForFolderCreation) { 2344 if ((mDragMode == DRAG_MODE_ADD_TO_FOLDER 2345 || mDragMode == DRAG_MODE_CREATE_FOLDER)) { 2346 setDragMode(DRAG_MODE_NONE); 2347 } 2348 return; 2349 } 2350 2351 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]); 2352 ItemInfo info = dragObject.dragInfo; 2353 boolean userFolderPending = willCreateUserFolder(info, dragOverView, false); 2354 if (mDragMode == DRAG_MODE_NONE && userFolderPending) { 2355 2356 mFolderCreateBg = new PreviewBackground(); 2357 mFolderCreateBg.setup(mLauncher, mLauncher, null, 2358 dragOverView.getMeasuredWidth(), dragOverView.getPaddingTop()); 2359 2360 // The full preview background should appear behind the icon 2361 mFolderCreateBg.isClipping = false; 2362 2363 mFolderCreateBg.animateToAccept(mDragTargetLayout, mTargetCell[0], mTargetCell[1]); 2364 mDragTargetLayout.clearDragOutlines(); 2365 setDragMode(DRAG_MODE_CREATE_FOLDER); 2366 2367 if (dragObject.stateAnnouncer != null) { 2368 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper 2369 .getDescriptionForDropOver(dragOverView, getContext())); 2370 } 2371 return; 2372 } 2373 2374 boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView); 2375 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) { 2376 mDragOverFolderIcon = ((FolderIcon) dragOverView); 2377 mDragOverFolderIcon.onDragEnter(info); 2378 if (mDragTargetLayout != null) { 2379 mDragTargetLayout.clearDragOutlines(); 2380 } 2381 setDragMode(DRAG_MODE_ADD_TO_FOLDER); 2382 2383 if (dragObject.stateAnnouncer != null) { 2384 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper 2385 .getDescriptionForDropOver(dragOverView, getContext())); 2386 } 2387 return; 2388 } 2389 2390 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) { 2391 setDragMode(DRAG_MODE_NONE); 2392 } 2393 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) { 2394 setDragMode(DRAG_MODE_NONE); 2395 } 2396 } 2397 2398 class ReorderAlarmListener implements OnAlarmListener { 2399 final float[] dragViewCenter; 2400 final int minSpanX, minSpanY, spanX, spanY; 2401 final DragObject dragObject; 2402 final View child; 2403 2404 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, 2405 int spanY, DragObject dragObject, View child) { 2406 this.dragViewCenter = dragViewCenter; 2407 this.minSpanX = minSpanX; 2408 this.minSpanY = minSpanY; 2409 this.spanX = spanX; 2410 this.spanY = spanY; 2411 this.child = child; 2412 this.dragObject = dragObject; 2413 } 2414 2415 public void onAlarm(Alarm alarm) { 2416 int[] resultSpan = new int[2]; 2417 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2418 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout, 2419 mTargetCell); 2420 mLastReorderX = mTargetCell[0]; 2421 mLastReorderY = mTargetCell[1]; 2422 2423 mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0], 2424 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 2425 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER); 2426 2427 if (mTargetCell[0] < 0 || mTargetCell[1] < 0) { 2428 mDragTargetLayout.revertTempState(); 2429 } else { 2430 setDragMode(DRAG_MODE_REORDER); 2431 } 2432 2433 boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; 2434 mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1], 2435 resultSpan[0], resultSpan[1], dragObject); 2436 } 2437 } 2438 2439 @Override 2440 public void getHitRectRelativeToDragLayer(Rect outRect) { 2441 // We want the workspace to have the whole area of the display (it will find the correct 2442 // cell layout to drop to in the existing drag/drop logic. 2443 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect); 2444 } 2445 2446 /** 2447 * Drop an item that didn't originate on one of the workspace screens. 2448 * It may have come from Launcher (e.g. from all apps or customize), or it may have 2449 * come from another app altogether. 2450 * 2451 * NOTE: This can also be called when we are outside of a drag event, when we want 2452 * to add an item to one of the workspace screens. 2453 */ 2454 private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) { 2455 if (d.dragInfo instanceof PendingAddShortcutInfo) { 2456 WorkspaceItemInfo si = ((PendingAddShortcutInfo) d.dragInfo) 2457 .activityInfo.createWorkspaceItemInfo(); 2458 if (si != null) { 2459 d.dragInfo = si; 2460 } 2461 } 2462 2463 ItemInfo info = d.dragInfo; 2464 int spanX = info.spanX; 2465 int spanY = info.spanY; 2466 if (mDragInfo != null) { 2467 spanX = mDragInfo.spanX; 2468 spanY = mDragInfo.spanY; 2469 } 2470 2471 final int container = mLauncher.isHotseatLayout(cellLayout) 2472 ? LauncherSettings.Favorites.CONTAINER_HOTSEAT 2473 : LauncherSettings.Favorites.CONTAINER_DESKTOP; 2474 final int screenId = getIdForScreen(cellLayout); 2475 if (!mLauncher.isHotseatLayout(cellLayout) 2476 && screenId != getScreenIdForPageIndex(mCurrentPage) 2477 && !mLauncher.isInState(SPRING_LOADED)) { 2478 snapToPage(getPageIndexForScreenId(screenId)); 2479 } 2480 2481 if (info instanceof PendingAddItemInfo) { 2482 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info; 2483 2484 boolean findNearestVacantCell = true; 2485 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 2486 mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY, 2487 cellLayout, mTargetCell); 2488 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2489 mDragViewVisualCenter[1], mTargetCell); 2490 if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true) 2491 || willAddToExistingUserFolder( 2492 d.dragInfo, cellLayout, mTargetCell, distance)) { 2493 findNearestVacantCell = false; 2494 } 2495 } 2496 2497 final ItemInfo item = d.dragInfo; 2498 boolean updateWidgetSize = false; 2499 if (findNearestVacantCell) { 2500 int minSpanX = item.spanX; 2501 int minSpanY = item.spanY; 2502 if (item.minSpanX > 0 && item.minSpanY > 0) { 2503 minSpanX = item.minSpanX; 2504 minSpanY = item.minSpanY; 2505 } 2506 int[] resultSpan = new int[2]; 2507 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0], 2508 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY, 2509 null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL); 2510 2511 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) { 2512 updateWidgetSize = true; 2513 } 2514 item.spanX = resultSpan[0]; 2515 item.spanY = resultSpan[1]; 2516 } 2517 2518 Runnable onAnimationCompleteRunnable = new Runnable() { 2519 @Override 2520 public void run() { 2521 // Normally removeExtraEmptyScreen is called in Workspace#onDrop, but when 2522 // adding an item that may not be dropped right away (due to a config activity) 2523 // we defer the removal until the activity returns. 2524 deferRemoveExtraEmptyScreen(); 2525 2526 // When dragging and dropping from customization tray, we deal with creating 2527 // widgets/shortcuts/folders in a slightly different way 2528 mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell, 2529 item.spanX, item.spanY); 2530 mStatsLogManager.logger().withItemInfo(d.dragInfo) 2531 .withInstanceId(d.logInstanceId) 2532 .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED); 2533 } 2534 }; 2535 boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET 2536 || pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 2537 2538 AppWidgetHostView finalView = isWidget ? 2539 ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; 2540 2541 if (finalView != null && updateWidgetSize) { 2542 WidgetSizes.updateWidgetSizeRanges(finalView, mLauncher, item.spanX, item.spanY); 2543 } 2544 2545 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR; 2546 if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null && 2547 ((PendingAddWidgetInfo) pendingInfo).getHandler().needsConfigure()) { 2548 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN; 2549 } 2550 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, 2551 animationStyle, finalView, true); 2552 } else { 2553 // This is for other drag/drop cases, like dragging from All Apps 2554 mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); 2555 2556 View view; 2557 2558 switch (info.itemType) { 2559 case ITEM_TYPE_APPLICATION: 2560 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 2561 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 2562 if (info instanceof AppInfo) { 2563 // Came from all apps -- make a copy 2564 info = ((AppInfo) info).makeWorkspaceItem(); 2565 d.dragInfo = info; 2566 } 2567 view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info); 2568 break; 2569 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 2570 view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, mLauncher, cellLayout, 2571 (FolderInfo) info); 2572 break; 2573 default: 2574 throw new IllegalStateException("Unknown item type: " + info.itemType); 2575 } 2576 2577 // First we find the cell nearest to point at which the item is 2578 // dropped, without any consideration to whether there is an item there. 2579 if (touchXY != null) { 2580 mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY, 2581 cellLayout, mTargetCell); 2582 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2583 mDragViewVisualCenter[1], mTargetCell); 2584 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, 2585 true, d)) { 2586 return; 2587 } 2588 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, 2589 true)) { 2590 return; 2591 } 2592 } 2593 2594 if (touchXY != null) { 2595 // when dragging and dropping, just find the closest free spot 2596 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0], 2597 (int) mDragViewVisualCenter[1], 1, 1, 1, 1, 2598 null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL); 2599 } else { 2600 cellLayout.findCellForSpan(mTargetCell, 1, 1); 2601 } 2602 // Add the item to DB before adding to screen ensures that the container and other 2603 // values of the info is properly updated. 2604 mLauncher.getModelWriter().addOrMoveItemInDatabase(info, container, screenId, 2605 mTargetCell[0], mTargetCell[1]); 2606 2607 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], 2608 info.spanX, info.spanY); 2609 cellLayout.onDropChild(view); 2610 cellLayout.getShortcutsAndWidgets().measureChild(view); 2611 2612 if (d.dragView != null) { 2613 // We wrap the animation call in the temporary set and reset of the current 2614 // cellLayout to its final transform -- this means we animate the drag view to 2615 // the correct final location. 2616 setFinalTransitionTransform(); 2617 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this); 2618 resetTransitionTransform(); 2619 } 2620 mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId) 2621 .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED); 2622 } 2623 2624 } 2625 2626 private Drawable createWidgetDrawable(ItemInfo widgetInfo, View layout) { 2627 int[] unScaledSize = estimateItemSize(widgetInfo); 2628 int visibility = layout.getVisibility(); 2629 layout.setVisibility(VISIBLE); 2630 2631 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY); 2632 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY); 2633 layout.measure(width, height); 2634 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]); 2635 Bitmap b = BitmapRenderer.createHardwareBitmap( 2636 unScaledSize[0], unScaledSize[1], layout::draw); 2637 layout.setVisibility(visibility); 2638 return new FastBitmapDrawable(b); 2639 } 2640 2641 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY, 2642 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale) { 2643 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final 2644 // location and size on the home screen. 2645 int spanX = info.spanX; 2646 int spanY = info.spanY; 2647 2648 Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY); 2649 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) { 2650 DeviceProfile profile = mLauncher.getDeviceProfile(); 2651 Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y); 2652 } 2653 2654 mTempFXY[0] = r.left; 2655 mTempFXY[1] = r.top; 2656 setFinalTransitionTransform(); 2657 float cellLayoutScale = 2658 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, mTempFXY, true); 2659 resetTransitionTransform(); 2660 Utilities.roundArray(mTempFXY, loc); 2661 2662 if (scale) { 2663 float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); 2664 float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight(); 2665 2666 // The animation will scale the dragView about its center, so we need to center about 2667 // the final location. 2668 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2 2669 - Math.ceil(layout.getUnusedHorizontalSpace() / 2f); 2670 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2; 2671 scaleXY[0] = dragViewScaleX * cellLayoutScale; 2672 scaleXY[1] = dragViewScaleY * cellLayoutScale; 2673 } else { 2674 // Since we are not cross-fading the dragView, align the drag view to the 2675 // final cell position. 2676 float dragScale = dragView.getInitialScale() * cellLayoutScale; 2677 loc[0] += (dragScale - 1) * dragView.getWidth() / 2; 2678 loc[1] += (dragScale - 1) * dragView.getHeight() / 2; 2679 scaleXY[0] = scaleXY[1] = dragScale; 2680 2681 // If a dragRegion was provided, offset the final position accordingly. 2682 Rect dragRegion = dragView.getDragRegion(); 2683 if (dragRegion != null) { 2684 loc[0] += cellLayoutScale * dragRegion.left; 2685 loc[1] += cellLayoutScale * dragRegion.top; 2686 } 2687 } 2688 } 2689 2690 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView, 2691 final Runnable onCompleteRunnable, int animationType, final View finalView, 2692 boolean external) { 2693 Rect from = new Rect(); 2694 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from); 2695 2696 int[] finalPos = new int[2]; 2697 float scaleXY[] = new float[2]; 2698 boolean scalePreview = !(info instanceof PendingAddShortcutInfo); 2699 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell, 2700 scalePreview); 2701 2702 Resources res = mLauncher.getResources(); 2703 final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200; 2704 2705 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET || 2706 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 2707 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) 2708 && finalView != null 2709 && dragView.getContentView() != finalView) { 2710 Drawable crossFadeDrawable = createWidgetDrawable(info, finalView); 2711 dragView.crossFadeContent(crossFadeDrawable, (int) (duration * 0.8f)); 2712 } else if (isWidget && external) { 2713 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]); 2714 } 2715 2716 DragLayer dragLayer = mLauncher.getDragLayer(); 2717 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) { 2718 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f, 2719 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration); 2720 } else { 2721 int endStyle; 2722 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) { 2723 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE; 2724 } else { 2725 endStyle = DragLayer.ANIMATION_END_DISAPPEAR; 2726 } 2727 2728 Runnable onComplete = new Runnable() { 2729 @Override 2730 public void run() { 2731 if (finalView != null) { 2732 finalView.setVisibility(VISIBLE); 2733 } 2734 if (onCompleteRunnable != null) { 2735 onCompleteRunnable.run(); 2736 } 2737 } 2738 }; 2739 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0], 2740 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle, 2741 duration, this); 2742 } 2743 } 2744 2745 public void setFinalTransitionTransform() { 2746 if (isSwitchingState()) { 2747 mCurrentScale = getScaleX(); 2748 setScaleX(mStateTransitionAnimation.getFinalScale()); 2749 setScaleY(mStateTransitionAnimation.getFinalScale()); 2750 } 2751 } 2752 public void resetTransitionTransform() { 2753 if (isSwitchingState()) { 2754 setScaleX(mCurrentScale); 2755 setScaleY(mCurrentScale); 2756 } 2757 } 2758 2759 /** 2760 * Return the current CellInfo describing our current drag; this method exists 2761 * so that Launcher can sync this object with the correct info when the activity is created/ 2762 * destroyed 2763 * 2764 */ 2765 public CellLayout.CellInfo getDragInfo() { 2766 return mDragInfo; 2767 } 2768 2769 /** 2770 * Calculate the nearest cell where the given object would be dropped. 2771 * 2772 * pixelX and pixelY should be in the coordinate system of layout 2773 */ 2774 @Thunk int[] findNearestArea(int pixelX, int pixelY, 2775 int spanX, int spanY, CellLayout layout, int[] recycle) { 2776 return layout.findNearestArea( 2777 pixelX, pixelY, spanX, spanY, recycle); 2778 } 2779 2780 void setup(DragController dragController) { 2781 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); 2782 mDragController = dragController; 2783 2784 // hardware layers on children are enabled on startup, but should be disabled until 2785 // needed 2786 updateChildrenLayersEnabled(); 2787 } 2788 2789 /** 2790 * Called at the end of a drag which originated on the workspace. 2791 */ 2792 public void onDropCompleted(final View target, final DragObject d, 2793 final boolean success) { 2794 if (success) { 2795 if (target != this && mDragInfo != null) { 2796 removeWorkspaceItem(mDragInfo.cell); 2797 } 2798 } else if (mDragInfo != null) { 2799 // When drag is cancelled, reattach content view back to its original parent. 2800 if (mDragInfo.cell instanceof LauncherAppWidgetHostView && d.dragView != null) { 2801 d.dragView.detachContentView(/* reattachToPreviousParent= */ true); 2802 } 2803 final CellLayout cellLayout = mLauncher.getCellLayout( 2804 mDragInfo.container, mDragInfo.screenId); 2805 if (cellLayout != null) { 2806 cellLayout.onDropChild(mDragInfo.cell); 2807 } else if (FeatureFlags.IS_STUDIO_BUILD) { 2808 throw new RuntimeException("Invalid state: cellLayout == null in " 2809 + "Workspace#onDropCompleted. Please file a bug. "); 2810 } 2811 } 2812 View cell = getHomescreenIconByItemId(d.originalDragInfo.id); 2813 if (d.cancelled && cell != null) { 2814 cell.setVisibility(VISIBLE); 2815 } 2816 mDragInfo = null; 2817 } 2818 2819 /** 2820 * For opposite operation. See {@link #addInScreen}. 2821 */ 2822 public void removeWorkspaceItem(View v) { 2823 CellLayout parentCell = getParentCellLayoutForView(v); 2824 if (parentCell != null) { 2825 parentCell.removeView(v); 2826 } else if (FeatureFlags.IS_STUDIO_BUILD) { 2827 // When an app is uninstalled using the drop target, we wait until resume to remove 2828 // the icon. We also remove all the corresponding items from the workspace at 2829 // {@link Launcher#bindComponentsRemoved}. That call can come before or after 2830 // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is. 2831 Log.e(TAG, "mDragInfo.cell has null parent"); 2832 } 2833 if (v instanceof DropTarget) { 2834 mDragController.removeDropTarget((DropTarget) v); 2835 } 2836 } 2837 2838 /** 2839 * Removed widget from workspace by appWidgetId 2840 * @param appWidgetId 2841 */ 2842 public void removeWidget(int appWidgetId) { 2843 mapOverItems((info, view) -> { 2844 if (info instanceof LauncherAppWidgetInfo) { 2845 LauncherAppWidgetInfo appWidgetInfo = (LauncherAppWidgetInfo) info; 2846 if (appWidgetInfo.appWidgetId == appWidgetId) { 2847 mLauncher.removeItem(view, appWidgetInfo, true); 2848 return true; 2849 } 2850 } 2851 return false; 2852 }); 2853 } 2854 2855 /** 2856 * Removes all folder listeners 2857 */ 2858 public void removeFolderListeners() { 2859 mapOverItems(new ItemOperator() { 2860 @Override 2861 public boolean evaluate(ItemInfo info, View view) { 2862 if (view instanceof FolderIcon) { 2863 ((FolderIcon) view).removeListeners(); 2864 } 2865 return false; 2866 } 2867 }); 2868 } 2869 2870 public boolean isDropEnabled() { 2871 return true; 2872 } 2873 2874 @Override 2875 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 2876 // We don't dispatch restoreInstanceState to our children using this code path. 2877 // Some pages will be restored immediately as their items are bound immediately, and 2878 // others we will need to wait until after their items are bound. 2879 mSavedStates = container; 2880 } 2881 2882 public void restoreInstanceStateForChild(int child) { 2883 if (mSavedStates != null) { 2884 mRestoredPages.add(child); 2885 CellLayout cl = (CellLayout) getChildAt(child); 2886 if (cl != null) { 2887 cl.restoreInstanceState(mSavedStates); 2888 } 2889 } 2890 } 2891 2892 public void restoreInstanceStateForRemainingPages() { 2893 int count = getChildCount(); 2894 for (int i = 0; i < count; i++) { 2895 if (!mRestoredPages.contains(i)) { 2896 restoreInstanceStateForChild(i); 2897 } 2898 } 2899 mRestoredPages.clear(); 2900 mSavedStates = null; 2901 } 2902 2903 @Override 2904 public boolean scrollLeft() { 2905 boolean result = false; 2906 if (!mIsSwitchingState && workspaceInScrollableState()) { 2907 result = super.scrollLeft(); 2908 } 2909 Folder openFolder = Folder.getOpen(mLauncher); 2910 if (openFolder != null) { 2911 openFolder.completeDragExit(); 2912 } 2913 return result; 2914 } 2915 2916 @Override 2917 public boolean scrollRight() { 2918 boolean result = false; 2919 if (!mIsSwitchingState && workspaceInScrollableState()) { 2920 result = super.scrollRight(); 2921 } 2922 Folder openFolder = Folder.getOpen(mLauncher); 2923 if (openFolder != null) { 2924 openFolder.completeDragExit(); 2925 } 2926 return result; 2927 } 2928 2929 /** 2930 * Returns a specific CellLayout 2931 */ 2932 CellLayout getParentCellLayoutForView(View v) { 2933 for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { 2934 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) { 2935 return layout; 2936 } 2937 } 2938 return null; 2939 } 2940 2941 /** 2942 * Returns a list of all the CellLayouts on the Homescreen. 2943 */ 2944 private CellLayout[] getWorkspaceAndHotseatCellLayouts() { 2945 int screenCount = getChildCount(); 2946 final CellLayout[] layouts; 2947 if (mLauncher.getHotseat() != null) { 2948 layouts = new CellLayout[screenCount + 1]; 2949 layouts[screenCount] = mLauncher.getHotseat(); 2950 } else { 2951 layouts = new CellLayout[screenCount]; 2952 } 2953 for (int screen = 0; screen < screenCount; screen++) { 2954 layouts[screen] = (CellLayout) getChildAt(screen); 2955 } 2956 return layouts; 2957 } 2958 2959 /** 2960 * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close 2961 * animation. 2962 * 2963 * @param preferredItemId The id of the preferred item to match to if it exists. 2964 * @param packageName The package name of the app to match. 2965 * @param user The user of the app to match. 2966 */ 2967 public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) { 2968 final Workspace.ItemOperator preferredItem = (ItemInfo info, View view) -> 2969 info != null && info.id == preferredItemId; 2970 final Workspace.ItemOperator preferredItemInFolder = (info, view) -> { 2971 if (info instanceof FolderInfo) { 2972 FolderInfo folderInfo = (FolderInfo) info; 2973 for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) { 2974 if (preferredItem.evaluate(shortcutInfo, view)) { 2975 return true; 2976 } 2977 } 2978 } 2979 return false; 2980 }; 2981 final Workspace.ItemOperator packageAndUserAndApp = (ItemInfo info, View view) -> 2982 info != null 2983 && info.itemType == ITEM_TYPE_APPLICATION 2984 && info.user.equals(user) 2985 && info.getTargetComponent() != null 2986 && TextUtils.equals(info.getTargetComponent().getPackageName(), 2987 packageName); 2988 final Workspace.ItemOperator packageAndUserAndAppInFolder = (info, view) -> { 2989 if (info instanceof FolderInfo) { 2990 FolderInfo folderInfo = (FolderInfo) info; 2991 for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) { 2992 if (packageAndUserAndApp.evaluate(shortcutInfo, view)) { 2993 return true; 2994 } 2995 } 2996 } 2997 return false; 2998 }; 2999 3000 List<CellLayout> cellLayouts = new ArrayList<>(getPanelCount() + 1); 3001 cellLayouts.add(getHotseat()); 3002 forEachVisiblePage(page -> cellLayouts.add((CellLayout) page)); 3003 3004 // Order: Preferred item, App icons in hotseat/workspace, app in folder in hotseat/workspace 3005 if (ADAPTIVE_ICON_WINDOW_ANIM.get()) { 3006 return getFirstMatch(cellLayouts, preferredItem, preferredItemInFolder, 3007 packageAndUserAndApp, packageAndUserAndAppInFolder); 3008 } else { 3009 // Do not use Folder as a criteria, since it'll cause a crash when trying to draw 3010 // FolderAdaptiveIcon as the background. 3011 return getFirstMatch(cellLayouts, preferredItem, packageAndUserAndApp); 3012 } 3013 } 3014 getHomescreenIconByItemId(final int id)3015 public View getHomescreenIconByItemId(final int id) { 3016 return getFirstMatch((info, v) -> info != null && info.id == id); 3017 } 3018 getWidgetForAppWidgetId(final int appWidgetId)3019 public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) { 3020 return (LauncherAppWidgetHostView) getFirstMatch((info, v) -> 3021 (info instanceof LauncherAppWidgetInfo) && 3022 ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId); 3023 } 3024 getFirstMatch(final ItemOperator operator)3025 public View getFirstMatch(final ItemOperator operator) { 3026 final View[] value = new View[1]; 3027 mapOverItems(new ItemOperator() { 3028 @Override 3029 public boolean evaluate(ItemInfo info, View v) { 3030 if (operator.evaluate(info, v)) { 3031 value[0] = v; 3032 return true; 3033 } 3034 return false; 3035 } 3036 }); 3037 return value[0]; 3038 } 3039 3040 /** 3041 * Finds the first view matching the ordered operators across the given cell layouts by order. 3042 * @param cellLayouts List of CellLayouts to scan, in order of preference. 3043 * @param operators List of operators, in order starting from best matching operator. 3044 */ getFirstMatch(Iterable<CellLayout> cellLayouts, final ItemOperator... operators)3045 View getFirstMatch(Iterable<CellLayout> cellLayouts, final ItemOperator... operators) { 3046 for (ItemOperator operator : operators) { 3047 for (CellLayout cellLayout : cellLayouts) { 3048 View match = mapOverCellLayout(cellLayout, operator); 3049 if (match != null) { 3050 return match; 3051 } 3052 } 3053 } 3054 return null; 3055 } 3056 clearDropTargets()3057 void clearDropTargets() { 3058 mapOverItems(new ItemOperator() { 3059 @Override 3060 public boolean evaluate(ItemInfo info, View v) { 3061 if (v instanceof DropTarget) { 3062 mDragController.removeDropTarget((DropTarget) v); 3063 } 3064 // not done, process all the shortcuts 3065 return false; 3066 } 3067 }); 3068 } 3069 3070 /** 3071 * Removes items that match the {@param matcher}. When applications are removed 3072 * as a part of an update, this is called to ensure that other widgets and application 3073 * shortcuts are not removed. 3074 */ removeItemsByMatcher(final ItemInfoMatcher matcher)3075 public void removeItemsByMatcher(final ItemInfoMatcher matcher) { 3076 for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { 3077 ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets(); 3078 // Iterate in reverse order as we are removing items 3079 for (int i = container.getChildCount() - 1; i >= 0; i--) { 3080 View child = container.getChildAt(i); 3081 ItemInfo info = (ItemInfo) child.getTag(); 3082 3083 if (matcher.matchesInfo(info)) { 3084 layout.removeViewInLayout(child); 3085 if (child instanceof DropTarget) { 3086 mDragController.removeDropTarget((DropTarget) child); 3087 } 3088 } else if (child instanceof FolderIcon) { 3089 FolderInfo folderInfo = (FolderInfo) info; 3090 List<WorkspaceItemInfo> matches = folderInfo.contents.stream() 3091 .filter(matcher::matchesInfo) 3092 .collect(Collectors.toList()); 3093 if (!matches.isEmpty()) { 3094 folderInfo.removeAll(matches, false); 3095 if (((FolderIcon) child).getFolder().isOpen()) { 3096 ((FolderIcon) child).getFolder().close(false /* animate */); 3097 } 3098 } 3099 } 3100 } 3101 } 3102 3103 // Strip all the empty screens 3104 stripEmptyScreens(); 3105 } 3106 3107 public interface ItemOperator { 3108 /** 3109 * Process the next itemInfo, possibly with side-effect on the next item. 3110 * 3111 * @param info info for the shortcut 3112 * @param view view for the shortcut 3113 * @return true if done, false to continue the map 3114 */ 3115 boolean evaluate(ItemInfo info, View view); 3116 } 3117 3118 /** 3119 * Map the operator over the shortcuts and widgets, return the first-non-null value. 3120 * 3121 * @param op the operator to map over the shortcuts 3122 */ mapOverItems(ItemOperator op)3123 public void mapOverItems(ItemOperator op) { 3124 for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { 3125 if (mapOverCellLayout(layout, op) != null) { 3126 return; 3127 } 3128 } 3129 } 3130 mapOverCellLayout(CellLayout layout, ItemOperator op)3131 private View mapOverCellLayout(CellLayout layout, ItemOperator op) { 3132 // TODO(b/128460496) Potential race condition where layout is not yet loaded 3133 if (layout == null) { 3134 return null; 3135 } 3136 ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets(); 3137 // map over all the shortcuts on the workspace 3138 final int itemCount = container.getChildCount(); 3139 for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { 3140 View item = container.getChildAt(itemIdx); 3141 if (op.evaluate((ItemInfo) item.getTag(), item)) { 3142 return item; 3143 } 3144 } 3145 return null; 3146 } 3147 updateShortcuts(List<WorkspaceItemInfo> shortcuts)3148 void updateShortcuts(List<WorkspaceItemInfo> shortcuts) { 3149 final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts); 3150 ItemOperator op = (info, v) -> { 3151 if (v instanceof BubbleTextView && updates.contains(info)) { 3152 WorkspaceItemInfo si = (WorkspaceItemInfo) info; 3153 BubbleTextView shortcut = (BubbleTextView) v; 3154 Drawable oldIcon = shortcut.getIcon(); 3155 boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) 3156 && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); 3157 shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState); 3158 } else if (info instanceof FolderInfo && v instanceof FolderIcon) { 3159 ((FolderIcon) v).updatePreviewItems(updates::contains); 3160 } 3161 3162 // Iterate all items 3163 return false; 3164 }; 3165 3166 mapOverItems(op); 3167 Folder openFolder = Folder.getOpen(mLauncher); 3168 if (openFolder != null) { 3169 openFolder.iterateOverItems(op); 3170 } 3171 } 3172 updateNotificationDots(Predicate<PackageUserKey> updatedDots)3173 public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) { 3174 final PackageUserKey packageUserKey = new PackageUserKey(null, null); 3175 Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info) 3176 || updatedDots.test(packageUserKey); 3177 3178 ItemOperator op = (info, v) -> { 3179 if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) { 3180 if (matcher.test(info)) { 3181 ((BubbleTextView) v).applyDotState(info, true /* animate */); 3182 } 3183 } else if (info instanceof FolderInfo && v instanceof FolderIcon) { 3184 FolderInfo fi = (FolderInfo) info; 3185 if (fi.contents.stream().anyMatch(matcher)) { 3186 FolderDotInfo folderDotInfo = new FolderDotInfo(); 3187 for (WorkspaceItemInfo si : fi.contents) { 3188 folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si)); 3189 } 3190 ((FolderIcon) v).setDotInfo(folderDotInfo); 3191 } 3192 } 3193 3194 // process all the shortcuts 3195 return false; 3196 }; 3197 3198 mapOverItems(op); 3199 Folder folder = Folder.getOpen(mLauncher); 3200 if (folder != null) { 3201 folder.iterateOverItems(op); 3202 } 3203 } 3204 removeAbandonedPromise(String packageName, UserHandle user)3205 public void removeAbandonedPromise(String packageName, UserHandle user) { 3206 ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages( 3207 Collections.singleton(packageName), user); 3208 mLauncher.getModelWriter().deleteItemsFromDatabase(matcher); 3209 removeItemsByMatcher(matcher); 3210 } 3211 updateRestoreItems(final HashSet<ItemInfo> updates)3212 public void updateRestoreItems(final HashSet<ItemInfo> updates) { 3213 ItemOperator op = (info, v) -> { 3214 if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView 3215 && updates.contains(info)) { 3216 ((BubbleTextView) v).applyLoadingState(false /* promiseStateChanged */); 3217 } else if (v instanceof PendingAppWidgetHostView 3218 && info instanceof LauncherAppWidgetInfo 3219 && updates.contains(info)) { 3220 ((PendingAppWidgetHostView) v).applyState(); 3221 } else if (v instanceof FolderIcon && info instanceof FolderInfo) { 3222 ((FolderIcon) v).updatePreviewItems(updates::contains); 3223 } 3224 // process all the shortcuts 3225 return false; 3226 }; 3227 mapOverItems(op); 3228 Folder folder = Folder.getOpen(mLauncher); 3229 if (folder != null) { 3230 folder.iterateOverItems(op); 3231 } 3232 } 3233 widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo)3234 public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) { 3235 if (!changedInfo.isEmpty()) { 3236 DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo, 3237 mLauncher.getAppWidgetHost()); 3238 3239 LauncherAppWidgetInfo item = changedInfo.get(0); 3240 final AppWidgetProviderInfo widgetInfo; 3241 WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext()); 3242 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { 3243 widgetInfo = widgetHelper.findProvider(item.providerName, item.user); 3244 } else { 3245 widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId); 3246 } 3247 3248 if (widgetInfo != null) { 3249 // Re-inflate the widgets which have changed status 3250 widgetRefresh.run(); 3251 } else { 3252 // widgetRefresh will automatically run when the packages are updated. 3253 // For now just update the progress bars 3254 mapOverItems(new ItemOperator() { 3255 @Override 3256 public boolean evaluate(ItemInfo info, View view) { 3257 if (view instanceof PendingAppWidgetHostView 3258 && changedInfo.contains(info)) { 3259 ((LauncherAppWidgetInfo) info).installProgress = 100; 3260 ((PendingAppWidgetHostView) view).applyState(); 3261 } 3262 // process all the shortcuts 3263 return false; 3264 } 3265 }); 3266 } 3267 } 3268 } 3269 isOverlayShown()3270 public boolean isOverlayShown() { 3271 return mOverlayShown; 3272 } 3273 3274 /** Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests focus on it. */ moveToDefaultScreen()3275 public void moveToDefaultScreen() { 3276 int page = DEFAULT_PAGE; 3277 if (!workspaceInModalState() && getNextPage() != page) { 3278 snapToPage(page); 3279 } 3280 View child = getChildAt(page); 3281 if (child != null) { 3282 child.requestFocus(); 3283 } 3284 } 3285 3286 /** 3287 * Set the given view's pivot point to match the workspace's, so that it scales together. Since 3288 * both this view and workspace can move, transform the point manually instead of using 3289 * dragLayer.getDescendantCoordRelativeToSelf and related methods. 3290 */ setPivotToScaleWithSelf(View sibling)3291 public void setPivotToScaleWithSelf(View sibling) { 3292 sibling.setPivotY(getPivotY() + getTop() 3293 - sibling.getTop() - sibling.getTranslationY()); 3294 sibling.setPivotX(getPivotX() + getLeft() 3295 - sibling.getLeft() - sibling.getTranslationX()); 3296 } 3297 3298 @Override getExpectedHeight()3299 public int getExpectedHeight() { 3300 return getMeasuredHeight() <= 0 || !mIsLayoutValid 3301 ? mLauncher.getDeviceProfile().heightPx : getMeasuredHeight(); 3302 } 3303 3304 @Override getExpectedWidth()3305 public int getExpectedWidth() { 3306 return getMeasuredWidth() <= 0 || !mIsLayoutValid 3307 ? mLauncher.getDeviceProfile().widthPx : getMeasuredWidth(); 3308 } 3309 3310 @Override canAnnouncePageDescription()3311 protected boolean canAnnouncePageDescription() { 3312 // Disable announcements while overscrolling potentially to overlay screen because if we end 3313 // up on the overlay screen, it will take care of announcing itself. 3314 return Float.compare(mOverlayTranslation, 0f) == 0; 3315 } 3316 3317 @Override getCurrentPageDescription()3318 protected String getCurrentPageDescription() { 3319 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 3320 return getPageDescription(page); 3321 } 3322 getPageDescription(int page)3323 private String getPageDescription(int page) { 3324 int nScreens = getChildCount(); 3325 int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); 3326 if (extraScreenId >= 0 && nScreens > 1) { 3327 if (page == extraScreenId) { 3328 return getContext().getString(R.string.workspace_new_page); 3329 } 3330 nScreens--; 3331 } 3332 if (nScreens == 0) { 3333 // When the workspace is not loaded, we do not know how many screen will be bound. 3334 return getContext().getString(R.string.home_screen); 3335 } 3336 return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens); 3337 } 3338 3339 /** 3340 * Used as a workaround to ensure that the AppWidgetService receives the 3341 * PACKAGE_ADDED broadcast before updating widgets. 3342 */ 3343 private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener { 3344 private final ArrayList<LauncherAppWidgetInfo> mInfos; 3345 private final LauncherAppWidgetHost mHost; 3346 private final Handler mHandler; 3347 3348 private boolean mRefreshPending; 3349 DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos, LauncherAppWidgetHost host)3350 DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos, 3351 LauncherAppWidgetHost host) { 3352 mInfos = infos; 3353 mHost = host; 3354 mHandler = mLauncher.mHandler; 3355 mRefreshPending = true; 3356 3357 mHost.addProviderChangeListener(this); 3358 // Force refresh after 10 seconds, if we don't get the provider changed event. 3359 // This could happen when the provider is no longer available in the app. 3360 Message msg = Message.obtain(mHandler, this); 3361 msg.obj = DeferredWidgetRefresh.class; 3362 mHandler.sendMessageDelayed(msg, 10000); 3363 } 3364 3365 @Override run()3366 public void run() { 3367 mHost.removeProviderChangeListener(this); 3368 mHandler.removeCallbacks(this); 3369 3370 if (!mRefreshPending) { 3371 return; 3372 } 3373 3374 mRefreshPending = false; 3375 3376 ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size()); 3377 mapOverItems((info, view) -> { 3378 if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) { 3379 views.add((PendingAppWidgetHostView) view); 3380 } 3381 // process all children 3382 return false; 3383 }); 3384 for (PendingAppWidgetHostView view : views) { 3385 view.reInflate(); 3386 } 3387 } 3388 3389 @Override notifyWidgetProvidersChanged()3390 public void notifyWidgetProvidersChanged() { 3391 run(); 3392 } 3393 } 3394 3395 private class StateTransitionListener extends AnimatorListenerAdapter 3396 implements AnimatorUpdateListener { 3397 3398 private final LauncherState mToState; 3399 StateTransitionListener(LauncherState toState)3400 StateTransitionListener(LauncherState toState) { 3401 mToState = toState; 3402 } 3403 3404 @Override onAnimationUpdate(ValueAnimator anim)3405 public void onAnimationUpdate(ValueAnimator anim) { 3406 mTransitionProgress = anim.getAnimatedFraction(); 3407 } 3408 3409 @Override onAnimationStart(Animator animation)3410 public void onAnimationStart(Animator animation) { 3411 onStartStateTransition(mToState); 3412 } 3413 3414 @Override onAnimationEnd(Animator animation)3415 public void onAnimationEnd(Animator animation) { 3416 onEndStateTransition(); 3417 } 3418 } 3419 } 3420