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