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