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 android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.LayoutTransition; 23 import android.animation.ObjectAnimator; 24 import android.animation.PropertyValuesHolder; 25 import android.animation.ValueAnimator; 26 import android.animation.ValueAnimator.AnimatorUpdateListener; 27 import android.annotation.SuppressLint; 28 import android.app.WallpaperManager; 29 import android.appwidget.AppWidgetHostView; 30 import android.appwidget.AppWidgetProviderInfo; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.res.Resources; 34 import android.graphics.Bitmap; 35 import android.graphics.Canvas; 36 import android.graphics.Matrix; 37 import android.graphics.Point; 38 import android.graphics.PointF; 39 import android.graphics.Rect; 40 import android.graphics.drawable.Drawable; 41 import android.os.Handler; 42 import android.os.IBinder; 43 import android.os.Parcelable; 44 import android.util.AttributeSet; 45 import android.util.Log; 46 import android.util.Property; 47 import android.util.SparseArray; 48 import android.view.MotionEvent; 49 import android.view.View; 50 import android.view.ViewDebug; 51 import android.view.ViewGroup; 52 import android.view.accessibility.AccessibilityManager; 53 import android.view.animation.DecelerateInterpolator; 54 import android.view.animation.Interpolator; 55 import android.widget.TextView; 56 57 import com.android.launcher3.Launcher.CustomContentCallbacks; 58 import com.android.launcher3.Launcher.LauncherOverlay; 59 import com.android.launcher3.UninstallDropTarget.DropTargetSource; 60 import com.android.launcher3.accessibility.AccessibileDragListenerAdapter; 61 import com.android.launcher3.accessibility.OverviewAccessibilityDelegate; 62 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; 63 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; 64 import com.android.launcher3.compat.AppWidgetManagerCompat; 65 import com.android.launcher3.compat.UserHandleCompat; 66 import com.android.launcher3.config.FeatureFlags; 67 import com.android.launcher3.config.ProviderConfig; 68 import com.android.launcher3.dragndrop.DragController; 69 import com.android.launcher3.dragndrop.DragLayer; 70 import com.android.launcher3.dragndrop.DragOptions; 71 import com.android.launcher3.dragndrop.DragScroller; 72 import com.android.launcher3.dragndrop.DragView; 73 import com.android.launcher3.dragndrop.SpringLoadedDragController; 74 import com.android.launcher3.folder.Folder; 75 import com.android.launcher3.folder.FolderIcon; 76 import com.android.launcher3.graphics.DragPreviewProvider; 77 import com.android.launcher3.userevent.nano.LauncherLogProto; 78 import com.android.launcher3.userevent.nano.LauncherLogProto.Target; 79 import com.android.launcher3.util.ItemInfoMatcher; 80 import com.android.launcher3.util.LongArrayMap; 81 import com.android.launcher3.util.MultiStateAlphaController; 82 import com.android.launcher3.util.Thunk; 83 import com.android.launcher3.util.VerticalFlingDetector; 84 import com.android.launcher3.util.WallpaperOffsetInterpolator; 85 import com.android.launcher3.widget.PendingAddShortcutInfo; 86 import com.android.launcher3.widget.PendingAddWidgetInfo; 87 88 import java.util.ArrayList; 89 import java.util.HashMap; 90 import java.util.HashSet; 91 92 /** 93 * The workspace is a wide area with a wallpaper and a finite number of pages. 94 * Each page contains a number of icons, folders or widgets the user can 95 * interact with. A workspace is meant to be used with a fixed width only. 96 */ 97 public class Workspace extends PagedView 98 implements DropTarget, DragSource, DragScroller, View.OnTouchListener, 99 DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener, 100 Insettable, DropTargetSource { 101 private static final String TAG = "Launcher.Workspace"; 102 103 private static boolean ENFORCE_DRAG_EVENT_ORDER = false; 104 105 private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400; 106 private static final int FADE_EMPTY_SCREEN_DURATION = 150; 107 108 private static final int ADJACENT_SCREEN_DROP_DURATION = 300; 109 110 private static final boolean MAP_NO_RECURSE = false; 111 private static final boolean MAP_RECURSE = true; 112 113 // The screen id used for the empty screen always present to the right. 114 public static final long EXTRA_EMPTY_SCREEN_ID = -201; 115 // The is the first screen. It is always present, even if its empty. 116 public static final long FIRST_SCREEN_ID = 0; 117 118 private final static long CUSTOM_CONTENT_SCREEN_ID = -301; 119 120 private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200; 121 private long mTouchDownTime = -1; 122 private long mCustomContentShowTime = -1; 123 124 private LayoutTransition mLayoutTransition; 125 @Thunk final WallpaperManager mWallpaperManager; 126 127 private ShortcutAndWidgetContainer mDragSourceInternal; 128 129 @Thunk LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>(); 130 @Thunk ArrayList<Long> mScreenOrder = new ArrayList<Long>(); 131 132 @Thunk Runnable mRemoveEmptyScreenRunnable; 133 @Thunk boolean mDeferRemoveExtraEmptyScreen = false; 134 135 /** 136 * CellInfo for the cell that is currently being dragged 137 */ 138 private CellLayout.CellInfo mDragInfo; 139 140 /** 141 * Target drop area calculated during last acceptDrop call. 142 */ 143 @Thunk int[] mTargetCell = new int[2]; 144 private int mDragOverX = -1; 145 private int mDragOverY = -1; 146 147 CustomContentCallbacks mCustomContentCallbacks; 148 boolean mCustomContentShowing; 149 private float mLastCustomContentScrollProgress = -1f; 150 private String mCustomContentDescription = ""; 151 152 /** 153 * The CellLayout that is currently being dragged over 154 */ 155 @Thunk CellLayout mDragTargetLayout = null; 156 /** 157 * The CellLayout that we will show as highlighted 158 */ 159 private CellLayout mDragOverlappingLayout = null; 160 161 /** 162 * The CellLayout which will be dropped to 163 */ 164 private CellLayout mDropToLayout = null; 165 166 @Thunk Launcher mLauncher; 167 @Thunk IconCache mIconCache; 168 @Thunk DragController mDragController; 169 170 // These are temporary variables to prevent having to allocate a new object just to 171 // return an (x, y) value from helper functions. Do NOT use them to maintain other state. 172 private static final Rect sTempRect = new Rect(); 173 private final int[] mTempXY = new int[2]; 174 @Thunk float[] mDragViewVisualCenter = new float[2]; 175 private float[] mTempCellLayoutCenterCoordinates = new float[2]; 176 private int[] mTempVisiblePagesRange = new int[2]; 177 private Matrix mTempMatrix = new Matrix(); 178 179 private SpringLoadedDragController mSpringLoadedDragController; 180 private float mOverviewModeShrinkFactor; 181 182 // State variable that indicates whether the pages are small (ie when you're 183 // in all apps or customize mode) 184 185 public enum State { 186 NORMAL (false, false), 187 NORMAL_HIDDEN (false, false), 188 SPRING_LOADED (false, true), 189 OVERVIEW (true, true), 190 OVERVIEW_HIDDEN (true, false); 191 192 public final boolean shouldUpdateWidget; 193 public final boolean hasMultipleVisiblePages; 194 State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages)195 State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages) { 196 this.shouldUpdateWidget = shouldUpdateWidget; 197 this.hasMultipleVisiblePages = hasMultipleVisiblePages; 198 } 199 } 200 201 // Direction used for moving the workspace and hotseat UI 202 public enum Direction { 203 X (TRANSLATION_X), 204 Y (TRANSLATION_Y); 205 206 private final Property<View, Float> viewProperty; 207 Direction(Property<View, Float> viewProperty)208 Direction(Property<View, Float> viewProperty) { 209 this.viewProperty = viewProperty; 210 } 211 } 212 213 private static final int HOTSEAT_STATE_ALPHA_INDEX = 2; 214 215 /** 216 * These values correspond to {@link Direction#X} & {@link Direction#Y} 217 */ 218 private float[] mPageAlpha = new float[] {1, 1}; 219 /** 220 * Hotseat alpha can be changed when moving horizontally, vertically, changing states. 221 * The values correspond to {@link Direction#X}, {@link Direction#Y} & 222 * {@link #HOTSEAT_STATE_ALPHA_INDEX} respectively. 223 */ 224 private float[] mHotseatAlpha = new float[] {1, 1, 1}; 225 226 public static final int QSB_ALPHA_INDEX_STATE_CHANGE = 0; 227 public static final int QSB_ALPHA_INDEX_Y_TRANSLATION = 1; 228 public static final int QSB_ALPHA_INDEX_PAGE_SCROLL = 2; 229 public static final int QSB_ALPHA_INDEX_OVERLAY_SCROLL = 3; 230 231 232 MultiStateAlphaController mQsbAlphaController; 233 234 @ViewDebug.ExportedProperty(category = "launcher") 235 private State mState = State.NORMAL; 236 private boolean mIsSwitchingState = false; 237 238 boolean mAnimatingViewIntoPlace = false; 239 boolean mChildrenLayersEnabled = true; 240 241 private boolean mStripScreensOnPageStopMoving = false; 242 243 /** Is the user is dragging an item near the edge of a page? */ 244 private boolean mInScrollArea = false; 245 246 private DragPreviewProvider mOutlineProvider = null; 247 public static final int DRAG_BITMAP_PADDING = DragPreviewProvider.DRAG_BITMAP_PADDING; 248 private boolean mWorkspaceFadeInAdjacentScreens; 249 250 final WallpaperOffsetInterpolator mWallpaperOffset; 251 private boolean mUnlockWallpaperFromDefaultPageOnLayout; 252 253 @Thunk Runnable mDelayedResizeRunnable; 254 private Runnable mDelayedSnapToPageRunnable; 255 256 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts 257 private static final int FOLDER_CREATION_TIMEOUT = 0; 258 public static final int REORDER_TIMEOUT = 350; 259 private final Alarm mFolderCreationAlarm = new Alarm(); 260 private final Alarm mReorderAlarm = new Alarm(); 261 private FolderIcon.PreviewBackground mFolderCreateBg; 262 private FolderIcon mDragOverFolderIcon = null; 263 private boolean mCreateUserFolderOnDrop = false; 264 private boolean mAddToExistingFolderOnDrop = false; 265 private float mMaxDistanceForFolderCreation; 266 267 private final Canvas mCanvas = new Canvas(); 268 269 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget) 270 private float mXDown; 271 private float mYDown; 272 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6; 273 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3; 274 final static float TOUCH_SLOP_DAMPING_FACTOR = 4; 275 276 // Relating to the animation of items being dropped externally 277 public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0; 278 public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1; 279 public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2; 280 public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3; 281 public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4; 282 283 // Related to dragging, folder creation and reordering 284 private static final int DRAG_MODE_NONE = 0; 285 private static final int DRAG_MODE_CREATE_FOLDER = 1; 286 private static final int DRAG_MODE_ADD_TO_FOLDER = 2; 287 private static final int DRAG_MODE_REORDER = 3; 288 private int mDragMode = DRAG_MODE_NONE; 289 @Thunk int mLastReorderX = -1; 290 @Thunk int mLastReorderY = -1; 291 292 private SparseArray<Parcelable> mSavedStates; 293 private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>(); 294 295 private float mCurrentScale; 296 private float mTransitionProgress; 297 298 @Thunk Runnable mDeferredAction; 299 private boolean mDeferDropAfterUninstall; 300 private boolean mUninstallSuccessful; 301 302 // State related to Launcher Overlay 303 LauncherOverlay mLauncherOverlay; 304 boolean mScrollInteractionBegan; 305 boolean mStartedSendingScrollEvents; 306 float mLastOverlaySroll = 0; 307 // Total over scrollX in the overlay direction. 308 private int mUnboundedScrollX; 309 private boolean mForceDrawAdjacentPages = false; 310 // Total over scrollX in the overlay direction. 311 private float mOverlayTranslation; 312 private int mFirstPageScrollX; 313 private boolean mIgnoreQsbScroll; 314 315 // Handles workspace state transitions 316 private WorkspaceStateTransitionAnimation mStateTransitionAnimation; 317 318 private AccessibilityDelegate mPagesAccessibilityDelegate; 319 private OnStateChangeListener mOnStateChangeListener; 320 321 /** 322 * Used to inflate the Workspace from XML. 323 * 324 * @param context The application's context. 325 * @param attrs The attributes set containing the Workspace's customization values. 326 */ Workspace(Context context, AttributeSet attrs)327 public Workspace(Context context, AttributeSet attrs) { 328 this(context, attrs, 0); 329 } 330 331 /** 332 * Used to inflate the Workspace from XML. 333 * 334 * @param context The application's context. 335 * @param attrs The attributes set containing the Workspace's customization values. 336 * @param defStyle Unused. 337 */ Workspace(Context context, AttributeSet attrs, int defStyle)338 public Workspace(Context context, AttributeSet attrs, int defStyle) { 339 super(context, attrs, defStyle); 340 341 mLauncher = Launcher.getLauncher(context); 342 mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); 343 final Resources res = getResources(); 344 DeviceProfile grid = mLauncher.getDeviceProfile(); 345 mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens(); 346 mWallpaperManager = WallpaperManager.getInstance(context); 347 348 mWallpaperOffset = new WallpaperOffsetInterpolator(this); 349 mOverviewModeShrinkFactor = 350 res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f; 351 352 setOnHierarchyChangeListener(this); 353 setHapticFeedbackEnabled(false); 354 355 initWorkspace(); 356 357 // Disable multitouch across the workspace/all apps/customize tray 358 setMotionEventSplittingEnabled(true); 359 } 360 361 @Override setInsets(Rect insets)362 public void setInsets(Rect insets) { 363 mInsets.set(insets); 364 365 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID); 366 if (customScreen != null) { 367 View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0); 368 if (customContent instanceof Insettable) { 369 ((Insettable) customContent).setInsets(mInsets); 370 } 371 } 372 } 373 setOnStateChangeListener(OnStateChangeListener listener)374 public void setOnStateChangeListener(OnStateChangeListener listener) { 375 mOnStateChangeListener = listener; 376 } 377 378 // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each 379 // dimension if unsuccessful estimateItemSize(ItemInfo itemInfo, boolean springLoaded)380 public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) { 381 float shrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor; 382 int[] size = new int[2]; 383 if (getChildCount() > 0) { 384 // Use the first non-custom page to estimate the child position 385 CellLayout cl = (CellLayout) getChildAt(numCustomPages()); 386 Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY); 387 size[0] = r.width(); 388 size[1] = r.height(); 389 if (springLoaded) { 390 size[0] *= shrinkFactor; 391 size[1] *= shrinkFactor; 392 } 393 return size; 394 } else { 395 size[0] = Integer.MAX_VALUE; 396 size[1] = Integer.MAX_VALUE; 397 return size; 398 } 399 } 400 estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan)401 public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) { 402 Rect r = new Rect(); 403 cl.cellToRect(hCell, vCell, hSpan, vSpan, r); 404 return r; 405 } 406 407 @Override onDragStart(DropTarget.DragObject dragObject, DragOptions options)408 public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { 409 if (ENFORCE_DRAG_EVENT_ORDER) { 410 enfoceDragParity("onDragStart", 0, 0); 411 } 412 413 if (mOutlineProvider != null) { 414 // The outline is used to visualize where the item will land if dropped 415 mOutlineProvider.generateDragOutline(mCanvas); 416 } 417 418 updateChildrenLayersEnabled(false); 419 mLauncher.onDragStarted(); 420 mLauncher.lockScreenOrientation(); 421 mLauncher.onInteractionBegin(); 422 // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging 423 InstallShortcutReceiver.enableInstallQueue(); 424 425 // Do not add a new page if it is a accessible drag which was not started by the workspace. 426 // We do not support accessibility drag from other sources and instead provide a direct 427 // action for move/add to homescreen. 428 // When a accessible drag is started by the folder, we only allow rearranging withing the 429 // folder. 430 boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this); 431 432 if (addNewPage) { 433 mDeferRemoveExtraEmptyScreen = false; 434 addExtraEmptyScreenOnDrag(); 435 436 if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET 437 && dragObject.dragSource != this) { 438 // When dragging a widget from different source, move to a page which has 439 // enough space to place this widget (after rearranging/resizing). We special case 440 // widgets as they cannot be placed inside a folder. 441 // Start at the current page and search right (on LTR) until finding a page with 442 // enough space. Since an empty screen is the furthest right, a page must be found. 443 int currentPage = getPageNearestToCenterOfScreen(); 444 for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) { 445 CellLayout page = (CellLayout) getPageAt(pageIndex); 446 if (page.hasReorderSolution(dragObject.dragInfo)) { 447 setCurrentPage(pageIndex); 448 break; 449 } 450 } 451 } 452 } 453 454 if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { 455 // Always enter the spring loaded mode 456 mLauncher.enterSpringLoadedDragMode(); 457 } 458 } 459 deferRemoveExtraEmptyScreen()460 public void deferRemoveExtraEmptyScreen() { 461 mDeferRemoveExtraEmptyScreen = true; 462 } 463 464 @Override onDragEnd()465 public void onDragEnd() { 466 if (ENFORCE_DRAG_EVENT_ORDER) { 467 enfoceDragParity("onDragEnd", 0, 0); 468 } 469 470 if (!mDeferRemoveExtraEmptyScreen) { 471 removeExtraEmptyScreen(true, mDragSourceInternal != null); 472 } 473 474 updateChildrenLayersEnabled(false); 475 mLauncher.unlockScreenOrientation(false); 476 477 // Re-enable any Un/InstallShortcutReceiver and now process any queued items 478 InstallShortcutReceiver.disableAndFlushInstallQueue(getContext()); 479 480 mDragSourceInternal = null; 481 mLauncher.onInteractionEnd(); 482 } 483 484 /** 485 * Initializes various states for this workspace. 486 */ initWorkspace()487 protected void initWorkspace() { 488 mCurrentPage = getDefaultPage(); 489 LauncherAppState app = LauncherAppState.getInstance(); 490 DeviceProfile grid = mLauncher.getDeviceProfile(); 491 mIconCache = app.getIconCache(); 492 setWillNotDraw(false); 493 setClipChildren(false); 494 setClipToPadding(false); 495 setChildrenDrawnWithCacheEnabled(true); 496 497 setMinScale(mOverviewModeShrinkFactor); 498 setupLayoutTransition(); 499 500 mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx); 501 502 // Set the wallpaper dimensions when Launcher starts up 503 setWallpaperDimension(); 504 505 setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color)); 506 } 507 508 @Override initParentViews(View parent)509 public void initParentViews(View parent) { 510 super.initParentViews(parent); 511 mPageIndicator.setAccessibilityDelegate(new OverviewAccessibilityDelegate()); 512 mQsbAlphaController = new MultiStateAlphaController(mLauncher.getQsbContainer(), 4); 513 } 514 getDefaultPage()515 private int getDefaultPage() { 516 return numCustomPages(); 517 } 518 setupLayoutTransition()519 private void setupLayoutTransition() { 520 // We want to show layout transitions when pages are deleted, to close the gap. 521 mLayoutTransition = new LayoutTransition(); 522 mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING); 523 mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); 524 mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING); 525 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); 526 setLayoutTransition(mLayoutTransition); 527 } 528 enableLayoutTransitions()529 void enableLayoutTransitions() { 530 setLayoutTransition(mLayoutTransition); 531 } disableLayoutTransitions()532 void disableLayoutTransitions() { 533 setLayoutTransition(null); 534 } 535 536 @Override onChildViewAdded(View parent, View child)537 public void onChildViewAdded(View parent, View child) { 538 if (!(child instanceof CellLayout)) { 539 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 540 } 541 CellLayout cl = ((CellLayout) child); 542 cl.setOnInterceptTouchListener(this); 543 cl.setClickable(true); 544 cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 545 super.onChildViewAdded(parent, child); 546 } 547 shouldDrawChild(View child)548 protected boolean shouldDrawChild(View child) { 549 final CellLayout cl = (CellLayout) child; 550 return super.shouldDrawChild(child) && 551 (mIsSwitchingState || 552 cl.getShortcutsAndWidgets().getAlpha() > 0 || 553 cl.getBackgroundAlpha() > 0); 554 } 555 556 /** 557 * @return The open folder on the current screen, or null if there is none 558 */ getOpenFolder()559 public Folder getOpenFolder() { 560 DragLayer dragLayer = mLauncher.getDragLayer(); 561 // Iterate in reverse order. Folder is added later to the dragLayer, 562 // and will be one of the last views. 563 for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { 564 View child = dragLayer.getChildAt(i); 565 if (child instanceof Folder) { 566 Folder folder = (Folder) child; 567 if (folder.getInfo().opened) 568 return folder; 569 } 570 } 571 return null; 572 } 573 isTouchActive()574 boolean isTouchActive() { 575 return mTouchState != TOUCH_STATE_REST; 576 } 577 getEmbeddedQsbId()578 private int getEmbeddedQsbId() { 579 return mLauncher.getDeviceProfile().isVerticalBarLayout() 580 ? R.id.qsb_container : R.id.workspace_blocked_row; 581 } 582 583 /** 584 * Initializes and binds the first page 585 * @param qsb an exisitng qsb to recycle or null. 586 */ bindAndInitFirstWorkspaceScreen(View qsb)587 public void bindAndInitFirstWorkspaceScreen(View qsb) { 588 if (!FeatureFlags.QSB_ON_FIRST_SCREEN) { 589 return; 590 } 591 // Add the first page 592 CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0); 593 if (FeatureFlags.PULLDOWN_SEARCH) { 594 firstPage.setOnTouchListener(new VerticalFlingDetector(mLauncher) { 595 // detect fling when touch started from empty space 596 @Override 597 public boolean onTouch(View v, MotionEvent ev) { 598 if (workspaceInModalState()) return false; 599 if (shouldConsumeTouch(v)) return true; 600 if (super.onTouch(v, ev)) { 601 mLauncher.startSearch("", false, null, false); 602 return true; 603 } 604 return false; 605 } 606 }); 607 firstPage.setOnInterceptTouchListener(new VerticalFlingDetector(mLauncher) { 608 // detect fling when touch started from on top of the icons 609 @Override 610 public boolean onTouch(View v, MotionEvent ev) { 611 if (shouldConsumeTouch(v)) return true; 612 if (super.onTouch(v, ev)) { 613 mLauncher.startSearch("", false, null, false); 614 return true; 615 } 616 return false; 617 } 618 }); 619 } 620 // Always add a QSB on the first screen. 621 if (qsb == null) { 622 // In transposed layout, we add the QSB in the Grid. As workspace does not touch the 623 // edges, we do not need a full width QSB. 624 qsb = mLauncher.getLayoutInflater().inflate( 625 mLauncher.getDeviceProfile().isVerticalBarLayout() 626 ? R.layout.qsb_container : R.layout.qsb_blocker_view, 627 firstPage, false); 628 } 629 630 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1); 631 lp.canReorder = false; 632 if (!firstPage.addViewToCellLayout(qsb, 0, getEmbeddedQsbId(), lp, true)) { 633 Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout"); 634 } 635 } 636 637 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)638 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 639 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 640 641 // Update the QSB to match the cell height. This is treating the QSB essentially as a child 642 // of workspace despite that it's not a true child. 643 // Note that it relies on the strict ordering of measuring the workspace before the QSB 644 // at the dragLayer level. 645 if (getChildCount() > 0) { 646 CellLayout firstPage = (CellLayout) getChildAt(0); 647 int cellHeight = firstPage.getCellHeight(); 648 649 View qsbContainer = mLauncher.getQsbContainer(); 650 ViewGroup.LayoutParams lp = qsbContainer.getLayoutParams(); 651 if (cellHeight > 0 && lp.height != cellHeight) { 652 lp.height = cellHeight; 653 qsbContainer.setLayoutParams(lp); 654 } 655 } 656 } 657 removeAllWorkspaceScreens()658 public void removeAllWorkspaceScreens() { 659 // Disable all layout transitions before removing all pages to ensure that we don't get the 660 // transition animations competing with us changing the scroll when we add pages or the 661 // custom content screen 662 disableLayoutTransitions(); 663 664 // Since we increment the current page when we call addCustomContentPage via bindScreens 665 // (and other places), we need to adjust the current page back when we clear the pages 666 if (hasCustomContent()) { 667 removeCustomContentPage(); 668 } 669 670 // Recycle the QSB widget 671 View qsb = findViewById(getEmbeddedQsbId()); 672 if (qsb != null) { 673 ((ViewGroup) qsb.getParent()).removeView(qsb); 674 } 675 676 // Remove the pages and clear the screen models 677 removeAllViews(); 678 mScreenOrder.clear(); 679 mWorkspaceScreens.clear(); 680 681 // Ensure that the first page is always present 682 bindAndInitFirstWorkspaceScreen(qsb); 683 684 // Re-enable the layout transitions 685 enableLayoutTransitions(); 686 } 687 insertNewWorkspaceScreenBeforeEmptyScreen(long screenId)688 public void insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) { 689 // Find the index to insert this view into. If the empty screen exists, then 690 // insert it before that. 691 int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); 692 if (insertIndex < 0) { 693 insertIndex = mScreenOrder.size(); 694 } 695 insertNewWorkspaceScreen(screenId, insertIndex); 696 } 697 insertNewWorkspaceScreen(long screenId)698 public void insertNewWorkspaceScreen(long screenId) { 699 insertNewWorkspaceScreen(screenId, getChildCount()); 700 } 701 insertNewWorkspaceScreen(long screenId, int insertIndex)702 public CellLayout insertNewWorkspaceScreen(long screenId, int insertIndex) { 703 if (mWorkspaceScreens.containsKey(screenId)) { 704 throw new RuntimeException("Screen id " + screenId + " already exists!"); 705 } 706 707 // Inflate the cell layout, but do not add it automatically so that we can get the newly 708 // created CellLayout. 709 CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate( 710 R.layout.workspace_screen, this, false /* attachToRoot */); 711 newScreen.setOnLongClickListener(mLongClickListener); 712 newScreen.setOnClickListener(mLauncher); 713 newScreen.setSoundEffectsEnabled(false); 714 mWorkspaceScreens.put(screenId, newScreen); 715 mScreenOrder.add(insertIndex, screenId); 716 addView(newScreen, insertIndex); 717 718 if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { 719 newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); 720 } 721 722 return newScreen; 723 } 724 createCustomContentContainer()725 public void createCustomContentContainer() { 726 CellLayout customScreen = (CellLayout) 727 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, this, false); 728 customScreen.disableDragTarget(); 729 customScreen.disableJailContent(); 730 731 mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen); 732 mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID); 733 734 // We want no padding on the custom content 735 customScreen.setPadding(0, 0, 0, 0); 736 737 addFullScreenPage(customScreen); 738 739 // Update the custom content hint 740 if (mRestorePage != INVALID_RESTORE_PAGE) { 741 mRestorePage = mRestorePage + 1; 742 } else { 743 setCurrentPage(getCurrentPage() + 1); 744 } 745 } 746 removeCustomContentPage()747 public void removeCustomContentPage() { 748 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID); 749 if (customScreen == null) { 750 throw new RuntimeException("Expected custom content screen to exist"); 751 } 752 753 mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID); 754 mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID); 755 removeView(customScreen); 756 757 if (mCustomContentCallbacks != null) { 758 mCustomContentCallbacks.onScrollProgressChanged(0); 759 mCustomContentCallbacks.onHide(); 760 } 761 762 mCustomContentCallbacks = null; 763 764 // Update the custom content hint 765 if (mRestorePage != INVALID_RESTORE_PAGE) { 766 mRestorePage = mRestorePage - 1; 767 } else { 768 setCurrentPage(getCurrentPage() - 1); 769 } 770 } 771 addToCustomContentPage(View customContent, CustomContentCallbacks callbacks, String description)772 public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks, 773 String description) { 774 if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) { 775 throw new RuntimeException("Expected custom content screen to exist"); 776 } 777 778 // Add the custom content to the full screen custom page 779 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID); 780 int spanX = customScreen.getCountX(); 781 int spanY = customScreen.getCountY(); 782 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY); 783 lp.canReorder = false; 784 lp.isFullscreen = true; 785 if (customContent instanceof Insettable) { 786 ((Insettable)customContent).setInsets(mInsets); 787 } 788 789 // Verify that the child is removed from any existing parent. 790 if (customContent.getParent() instanceof ViewGroup) { 791 ViewGroup parent = (ViewGroup) customContent.getParent(); 792 parent.removeView(customContent); 793 } 794 customScreen.removeAllViews(); 795 customContent.setFocusable(true); 796 customContent.setOnKeyListener(new FullscreenKeyEventListener()); 797 customContent.setOnFocusChangeListener(mLauncher.mFocusHandler 798 .getHideIndicatorOnFocusListener()); 799 customScreen.addViewToCellLayout(customContent, 0, 0, lp, true); 800 mCustomContentDescription = description; 801 802 mCustomContentCallbacks = callbacks; 803 } 804 addExtraEmptyScreenOnDrag()805 public void addExtraEmptyScreenOnDrag() { 806 boolean lastChildOnScreen = false; 807 boolean childOnFinalScreen = false; 808 809 // Cancel any pending removal of empty screen 810 mRemoveEmptyScreenRunnable = null; 811 812 if (mDragSourceInternal != null) { 813 if (mDragSourceInternal.getChildCount() == 1) { 814 lastChildOnScreen = true; 815 } 816 CellLayout cl = (CellLayout) mDragSourceInternal.getParent(); 817 if (indexOfChild(cl) == getChildCount() - 1) { 818 childOnFinalScreen = true; 819 } 820 } 821 822 // If this is the last item on the final screen 823 if (lastChildOnScreen && childOnFinalScreen) { 824 return; 825 } 826 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) { 827 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID); 828 } 829 } 830 addExtraEmptyScreen()831 public boolean addExtraEmptyScreen() { 832 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) { 833 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID); 834 return true; 835 } 836 return false; 837 } 838 convertFinalScreenToEmptyScreenIfNecessary()839 private void convertFinalScreenToEmptyScreenIfNecessary() { 840 if (mLauncher.isWorkspaceLoading()) { 841 // Invalid and dangerous operation if workspace is loading 842 return; 843 } 844 845 if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return; 846 long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1); 847 848 if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return; 849 CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId); 850 851 // If the final screen is empty, convert it to the extra empty screen 852 if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 && 853 !finalScreen.isDropPending()) { 854 mWorkspaceScreens.remove(finalScreenId); 855 mScreenOrder.remove(finalScreenId); 856 857 // if this is the last non-custom content screen, convert it to the empty screen 858 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen); 859 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); 860 861 // Update the model if we have changed any screens 862 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder); 863 } 864 } 865 removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens)866 public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) { 867 removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens); 868 } 869 removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete, final int delay, final boolean stripEmptyScreens)870 public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete, 871 final int delay, final boolean stripEmptyScreens) { 872 if (mLauncher.isWorkspaceLoading()) { 873 // Don't strip empty screens if the workspace is still loading 874 return; 875 } 876 877 if (delay > 0) { 878 postDelayed(new Runnable() { 879 @Override 880 public void run() { 881 removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens); 882 } 883 }, delay); 884 return; 885 } 886 887 convertFinalScreenToEmptyScreenIfNecessary(); 888 if (hasExtraEmptyScreen()) { 889 int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); 890 if (getNextPage() == emptyIndex) { 891 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION); 892 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION, 893 onComplete, stripEmptyScreens); 894 } else { 895 snapToPage(getNextPage(), 0); 896 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION, 897 onComplete, stripEmptyScreens); 898 } 899 return; 900 } else if (stripEmptyScreens) { 901 // If we're not going to strip the empty screens after removing 902 // the extra empty screen, do it right away. 903 stripEmptyScreens(); 904 } 905 906 if (onComplete != null) { 907 onComplete.run(); 908 } 909 } 910 fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete, final boolean stripEmptyScreens)911 private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete, 912 final boolean stripEmptyScreens) { 913 // XXX: Do we need to update LM workspace screens below? 914 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f); 915 PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f); 916 917 final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID); 918 919 mRemoveEmptyScreenRunnable = new Runnable() { 920 @Override 921 public void run() { 922 if (hasExtraEmptyScreen()) { 923 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID); 924 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID); 925 removeView(cl); 926 if (stripEmptyScreens) { 927 stripEmptyScreens(); 928 } 929 // Update the page indicator to reflect the removed page. 930 showPageIndicatorAtCurrentScroll(); 931 } 932 } 933 }; 934 935 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha); 936 oa.setDuration(duration); 937 oa.setStartDelay(delay); 938 oa.addListener(new AnimatorListenerAdapter() { 939 @Override 940 public void onAnimationEnd(Animator animation) { 941 if (mRemoveEmptyScreenRunnable != null) { 942 mRemoveEmptyScreenRunnable.run(); 943 } 944 if (onComplete != null) { 945 onComplete.run(); 946 } 947 } 948 }); 949 oa.start(); 950 } 951 hasExtraEmptyScreen()952 public boolean hasExtraEmptyScreen() { 953 int nScreens = getChildCount(); 954 nScreens = nScreens - numCustomPages(); 955 return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1; 956 } 957 commitExtraEmptyScreen()958 public long commitExtraEmptyScreen() { 959 if (mLauncher.isWorkspaceLoading()) { 960 // Invalid and dangerous operation if workspace is loading 961 return -1; 962 } 963 964 int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID); 965 CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID); 966 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID); 967 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID); 968 969 long newId = LauncherSettings.Settings.call(getContext().getContentResolver(), 970 LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) 971 .getLong(LauncherSettings.Settings.EXTRA_VALUE); 972 mWorkspaceScreens.put(newId, cl); 973 mScreenOrder.add(newId); 974 975 // Update the model for the new screen 976 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder); 977 978 return newId; 979 } 980 getScreenWithId(long screenId)981 public CellLayout getScreenWithId(long screenId) { 982 return mWorkspaceScreens.get(screenId); 983 } 984 getIdForScreen(CellLayout layout)985 public long getIdForScreen(CellLayout layout) { 986 int index = mWorkspaceScreens.indexOfValue(layout); 987 if (index != -1) { 988 return mWorkspaceScreens.keyAt(index); 989 } 990 return -1; 991 } 992 getPageIndexForScreenId(long screenId)993 public int getPageIndexForScreenId(long screenId) { 994 return indexOfChild(mWorkspaceScreens.get(screenId)); 995 } 996 getScreenIdForPageIndex(int index)997 public long getScreenIdForPageIndex(int index) { 998 if (0 <= index && index < mScreenOrder.size()) { 999 return mScreenOrder.get(index); 1000 } 1001 return -1; 1002 } 1003 getScreenOrder()1004 public ArrayList<Long> getScreenOrder() { 1005 return mScreenOrder; 1006 } 1007 stripEmptyScreens()1008 public void stripEmptyScreens() { 1009 if (mLauncher.isWorkspaceLoading()) { 1010 // Don't strip empty screens if the workspace is still loading. 1011 // This is dangerous and can result in data loss. 1012 return; 1013 } 1014 1015 if (isPageMoving()) { 1016 mStripScreensOnPageStopMoving = true; 1017 return; 1018 } 1019 1020 int currentPage = getNextPage(); 1021 ArrayList<Long> removeScreens = new ArrayList<Long>(); 1022 int total = mWorkspaceScreens.size(); 1023 for (int i = 0; i < total; i++) { 1024 long id = mWorkspaceScreens.keyAt(i); 1025 CellLayout cl = mWorkspaceScreens.valueAt(i); 1026 // FIRST_SCREEN_ID can never be removed. 1027 if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID) 1028 && cl.getShortcutsAndWidgets().getChildCount() == 0) { 1029 removeScreens.add(id); 1030 } 1031 } 1032 1033 boolean isInAccessibleDrag = mLauncher.getAccessibilityDelegate().isInAccessibleDrag(); 1034 1035 // We enforce at least one page to add new items to. In the case that we remove the last 1036 // such screen, we convert the last screen to the empty screen 1037 int minScreens = 1 + numCustomPages(); 1038 1039 int pageShift = 0; 1040 for (Long id: removeScreens) { 1041 CellLayout cl = mWorkspaceScreens.get(id); 1042 mWorkspaceScreens.remove(id); 1043 mScreenOrder.remove(id); 1044 1045 if (getChildCount() > minScreens) { 1046 if (indexOfChild(cl) < currentPage) { 1047 pageShift++; 1048 } 1049 1050 if (isInAccessibleDrag) { 1051 cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); 1052 } 1053 1054 removeView(cl); 1055 } else { 1056 // if this is the last non-custom content screen, convert it to the empty screen 1057 mRemoveEmptyScreenRunnable = null; 1058 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl); 1059 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); 1060 } 1061 } 1062 1063 if (!removeScreens.isEmpty()) { 1064 // Update the model if we have changed any screens 1065 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder); 1066 } 1067 1068 if (pageShift >= 0) { 1069 setCurrentPage(currentPage - pageShift); 1070 } 1071 } 1072 1073 // See implementation for parameter definition. addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY)1074 void addInScreen(View child, long container, long screenId, 1075 int x, int y, int spanX, int spanY) { 1076 addInScreen(child, container, screenId, x, y, spanX, spanY, false, false); 1077 } 1078 1079 // At bind time, we use the rank (screenId) to compute x and y for hotseat items. 1080 // See implementation for parameter definition. addInScreenFromBind(View child, long container, long screenId, int x, int y, int spanX, int spanY)1081 public void addInScreenFromBind(View child, long container, long screenId, int x, int y, 1082 int spanX, int spanY) { 1083 addInScreen(child, container, screenId, x, y, spanX, spanY, false, true); 1084 } 1085 1086 // See implementation for parameter definition. addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, boolean insert)1087 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, 1088 boolean insert) { 1089 addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false); 1090 } 1091 1092 /** 1093 * Adds the specified child in the specified screen. The position and dimension of 1094 * the child are defined by x, y, spanX and spanY. 1095 * 1096 * @param child The child to add in one of the workspace's screens. 1097 * @param screenId The screen in which to add the child. 1098 * @param x The X position of the child in the screen's grid. 1099 * @param y The Y position of the child in the screen's grid. 1100 * @param spanX The number of cells spanned horizontally by the child. 1101 * @param spanY The number of cells spanned vertically by the child. 1102 * @param insert When true, the child is inserted at the beginning of the children list. 1103 * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute 1104 * the x and y position in which to place hotseat items. Otherwise 1105 * we use the x and y position to compute the rank. 1106 */ addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, boolean insert, boolean computeXYFromRank)1107 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, 1108 boolean insert, boolean computeXYFromRank) { 1109 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1110 if (getScreenWithId(screenId) == null) { 1111 Log.e(TAG, "Skipping child, screenId " + screenId + " not found"); 1112 // DEBUGGING - Print out the stack trace to see where we are adding from 1113 new Throwable().printStackTrace(); 1114 return; 1115 } 1116 } 1117 if (screenId == EXTRA_EMPTY_SCREEN_ID) { 1118 // This should never happen 1119 throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID"); 1120 } 1121 1122 final CellLayout layout; 1123 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1124 layout = mLauncher.getHotseat().getLayout(); 1125 child.setOnKeyListener(new HotseatIconKeyEventListener()); 1126 1127 // Hide folder title in the hotseat 1128 if (child instanceof FolderIcon) { 1129 ((FolderIcon) child).setTextVisible(false); 1130 } 1131 1132 if (computeXYFromRank) { 1133 x = mLauncher.getHotseat().getCellXFromOrder((int) screenId); 1134 y = mLauncher.getHotseat().getCellYFromOrder((int) screenId); 1135 } else { 1136 screenId = mLauncher.getHotseat().getOrderInHotseat(x, y); 1137 } 1138 } else { 1139 // Show folder title if not in the hotseat 1140 if (child instanceof FolderIcon) { 1141 ((FolderIcon) child).setTextVisible(true); 1142 } 1143 layout = getScreenWithId(screenId); 1144 child.setOnKeyListener(new IconKeyEventListener()); 1145 } 1146 1147 ViewGroup.LayoutParams genericLp = child.getLayoutParams(); 1148 CellLayout.LayoutParams lp; 1149 if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) { 1150 lp = new CellLayout.LayoutParams(x, y, spanX, spanY); 1151 } else { 1152 lp = (CellLayout.LayoutParams) genericLp; 1153 lp.cellX = x; 1154 lp.cellY = y; 1155 lp.cellHSpan = spanX; 1156 lp.cellVSpan = spanY; 1157 } 1158 1159 if (spanX < 0 && spanY < 0) { 1160 lp.isLockedToGrid = false; 1161 } 1162 1163 // Get the canonical child id to uniquely represent this view in this screen 1164 ItemInfo info = (ItemInfo) child.getTag(); 1165 int childId = mLauncher.getViewIdForItem(info); 1166 1167 boolean markCellsAsOccupied = !(child instanceof Folder); 1168 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) { 1169 // TODO: This branch occurs when the workspace is adding views 1170 // outside of the defined grid 1171 // maybe we should be deleting these items from the LauncherModel? 1172 Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout"); 1173 } 1174 1175 if (!(child instanceof Folder)) { 1176 child.setHapticFeedbackEnabled(false); 1177 child.setOnLongClickListener(mLongClickListener); 1178 } 1179 if (child instanceof DropTarget) { 1180 mDragController.addDropTarget((DropTarget) child); 1181 } 1182 } 1183 1184 /** 1185 * Called directly from a CellLayout (not by the framework), after we've been added as a 1186 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout 1187 * that it should intercept touch events, which is not something that is normally supported. 1188 */ 1189 @SuppressLint("ClickableViewAccessibility") 1190 @Override onTouch(View v, MotionEvent event)1191 public boolean onTouch(View v, MotionEvent event) { 1192 return shouldConsumeTouch(v); 1193 } 1194 shouldConsumeTouch(View v)1195 private boolean shouldConsumeTouch(View v) { 1196 return (workspaceInModalState() || !isFinishedSwitchingState()) 1197 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage); 1198 } 1199 isSwitchingState()1200 public boolean isSwitchingState() { 1201 return mIsSwitchingState; 1202 } 1203 1204 /** This differs from isSwitchingState in that we take into account how far the transition 1205 * has completed. */ isFinishedSwitchingState()1206 public boolean isFinishedSwitchingState() { 1207 return !mIsSwitchingState || (mTransitionProgress > 0.5f); 1208 } 1209 onWindowVisibilityChanged(int visibility)1210 protected void onWindowVisibilityChanged (int visibility) { 1211 mLauncher.onWindowVisibilityChanged(visibility); 1212 } 1213 1214 @Override dispatchUnhandledMove(View focused, int direction)1215 public boolean dispatchUnhandledMove(View focused, int direction) { 1216 if (workspaceInModalState() || !isFinishedSwitchingState()) { 1217 // when the home screens are shrunken, shouldn't allow side-scrolling 1218 return false; 1219 } 1220 return super.dispatchUnhandledMove(focused, direction); 1221 } 1222 1223 @Override onInterceptTouchEvent(MotionEvent ev)1224 public boolean onInterceptTouchEvent(MotionEvent ev) { 1225 switch (ev.getAction() & MotionEvent.ACTION_MASK) { 1226 case MotionEvent.ACTION_DOWN: 1227 mXDown = ev.getX(); 1228 mYDown = ev.getY(); 1229 mTouchDownTime = System.currentTimeMillis(); 1230 break; 1231 case MotionEvent.ACTION_POINTER_UP: 1232 case MotionEvent.ACTION_UP: 1233 if (mTouchState == TOUCH_STATE_REST) { 1234 final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); 1235 if (currentPage != null) { 1236 onWallpaperTap(ev); 1237 } 1238 } 1239 } 1240 return super.onInterceptTouchEvent(ev); 1241 } 1242 1243 @Override onGenericMotionEvent(MotionEvent event)1244 public boolean onGenericMotionEvent(MotionEvent event) { 1245 // Ignore pointer scroll events if the custom content doesn't allow scrolling. 1246 if ((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID) 1247 && (mCustomContentCallbacks != null) 1248 && !mCustomContentCallbacks.isScrollingAllowed()) { 1249 return false; 1250 } 1251 return super.onGenericMotionEvent(event); 1252 } 1253 reinflateWidgetsIfNecessary()1254 protected void reinflateWidgetsIfNecessary() { 1255 final int clCount = getChildCount(); 1256 for (int i = 0; i < clCount; i++) { 1257 CellLayout cl = (CellLayout) getChildAt(i); 1258 ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets(); 1259 final int itemCount = swc.getChildCount(); 1260 for (int j = 0; j < itemCount; j++) { 1261 View v = swc.getChildAt(j); 1262 1263 if (v instanceof LauncherAppWidgetHostView 1264 && v.getTag() instanceof LauncherAppWidgetInfo) { 1265 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag(); 1266 LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) v; 1267 if (lahv.isReinflateRequired()) { 1268 // Remove and rebind the current widget (which was inflated in the wrong 1269 // orientation), but don't delete it from the database 1270 mLauncher.removeItem(lahv, info, false /* deleteFromDb */); 1271 mLauncher.bindAppWidget(info); 1272 } 1273 } 1274 } 1275 } 1276 } 1277 1278 @Override determineScrollingStart(MotionEvent ev)1279 protected void determineScrollingStart(MotionEvent ev) { 1280 if (!isFinishedSwitchingState()) return; 1281 1282 float deltaX = ev.getX() - mXDown; 1283 float absDeltaX = Math.abs(deltaX); 1284 float absDeltaY = Math.abs(ev.getY() - mYDown); 1285 1286 if (Float.compare(absDeltaX, 0f) == 0) return; 1287 1288 float slope = absDeltaY / absDeltaX; 1289 float theta = (float) Math.atan(slope); 1290 1291 if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) { 1292 cancelCurrentPageLongPress(); 1293 } 1294 1295 boolean passRightSwipesToCustomContent = 1296 (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY; 1297 1298 boolean swipeInIgnoreDirection = mIsRtl ? deltaX < 0 : deltaX > 0; 1299 boolean onCustomContentScreen = 1300 getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID; 1301 if (swipeInIgnoreDirection && onCustomContentScreen && passRightSwipesToCustomContent) { 1302 // Pass swipes to the right to the custom content page. 1303 return; 1304 } 1305 1306 if (onCustomContentScreen && (mCustomContentCallbacks != null) 1307 && !mCustomContentCallbacks.isScrollingAllowed()) { 1308 // Don't allow workspace scrolling if the current custom content screen doesn't allow 1309 // scrolling. 1310 return; 1311 } 1312 1313 if (theta > MAX_SWIPE_ANGLE) { 1314 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace 1315 return; 1316 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) { 1317 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to 1318 // increase the touch slop to make it harder to begin scrolling the workspace. This 1319 // results in vertically scrolling widgets to more easily. The higher the angle, the 1320 // more we increase touch slop. 1321 theta -= START_DAMPING_TOUCH_SLOP_ANGLE; 1322 float extraRatio = (float) 1323 Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE))); 1324 super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio); 1325 } else { 1326 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special 1327 super.determineScrollingStart(ev); 1328 } 1329 } 1330 onPageBeginMoving()1331 protected void onPageBeginMoving() { 1332 super.onPageBeginMoving(); 1333 1334 if (isHardwareAccelerated()) { 1335 updateChildrenLayersEnabled(false); 1336 } else { 1337 if (mNextPage != INVALID_PAGE) { 1338 // we're snapping to a particular screen 1339 enableChildrenCache(mCurrentPage, mNextPage); 1340 } else { 1341 // this is when user is actively dragging a particular screen, they might 1342 // swipe it either left or right (but we won't advance by more than one screen) 1343 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1); 1344 } 1345 } 1346 } 1347 onPageEndMoving()1348 protected void onPageEndMoving() { 1349 super.onPageEndMoving(); 1350 1351 if (isHardwareAccelerated()) { 1352 updateChildrenLayersEnabled(false); 1353 } else { 1354 clearChildrenCache(); 1355 } 1356 1357 if (mDragController.isDragging()) { 1358 if (workspaceInModalState()) { 1359 // If we are in springloaded mode, then force an event to check if the current touch 1360 // is under a new page (to scroll to) 1361 mDragController.forceTouchMove(); 1362 } 1363 } 1364 1365 if (mDelayedResizeRunnable != null && !mIsSwitchingState) { 1366 mDelayedResizeRunnable.run(); 1367 mDelayedResizeRunnable = null; 1368 } 1369 1370 if (mDelayedSnapToPageRunnable != null) { 1371 mDelayedSnapToPageRunnable.run(); 1372 mDelayedSnapToPageRunnable = null; 1373 } 1374 if (mStripScreensOnPageStopMoving) { 1375 stripEmptyScreens(); 1376 mStripScreensOnPageStopMoving = false; 1377 } 1378 } 1379 onScrollInteractionBegin()1380 protected void onScrollInteractionBegin() { 1381 super.onScrollInteractionEnd(); 1382 mScrollInteractionBegan = true; 1383 } 1384 onScrollInteractionEnd()1385 protected void onScrollInteractionEnd() { 1386 super.onScrollInteractionEnd(); 1387 mScrollInteractionBegan = false; 1388 if (mStartedSendingScrollEvents) { 1389 mStartedSendingScrollEvents = false; 1390 mLauncherOverlay.onScrollInteractionEnd(); 1391 } 1392 } 1393 setLauncherOverlay(LauncherOverlay overlay)1394 public void setLauncherOverlay(LauncherOverlay overlay) { 1395 mLauncherOverlay = overlay; 1396 // A new overlay has been set. Reset event tracking 1397 mStartedSendingScrollEvents = false; 1398 onOverlayScrollChanged(0); 1399 } 1400 1401 @Override getUnboundedScrollX()1402 protected int getUnboundedScrollX() { 1403 if (isScrollingOverlay()) { 1404 return mUnboundedScrollX; 1405 } 1406 1407 return super.getUnboundedScrollX(); 1408 } 1409 isScrollingOverlay()1410 private boolean isScrollingOverlay() { 1411 return mLauncherOverlay != null && 1412 ((mIsRtl && mUnboundedScrollX > mMaxScrollX) || (!mIsRtl && mUnboundedScrollX < 0)); 1413 } 1414 1415 @Override snapToDestination()1416 protected void snapToDestination() { 1417 // If we're overscrolling the overlay, we make sure to immediately reset the PagedView 1418 // to it's baseline position instead of letting the overscroll settle. The overlay handles 1419 // it's own settling, and every gesture to the overlay should be self-contained and start 1420 // from 0, so we zero it out here. 1421 if (isScrollingOverlay()) { 1422 int finalScroll = mIsRtl ? mMaxScrollX : 0; 1423 1424 // We reset mWasInOverscroll so that PagedView doesn't zero out the overscroll 1425 // interaction when we call scrollTo. 1426 mWasInOverscroll = false; 1427 scrollTo(finalScroll, getScrollY()); 1428 } else { 1429 super.snapToDestination(); 1430 } 1431 } 1432 1433 @Override scrollTo(int x, int y)1434 public void scrollTo(int x, int y) { 1435 mUnboundedScrollX = x; 1436 super.scrollTo(x, y); 1437 } 1438 onWorkspaceOverallScrollChanged()1439 private void onWorkspaceOverallScrollChanged() { 1440 if (!mIgnoreQsbScroll) { 1441 mLauncher.getQsbContainer().setTranslationX( 1442 mOverlayTranslation + mFirstPageScrollX - getScrollX()); 1443 } 1444 } 1445 1446 @Override onScrollChanged(int l, int t, int oldl, int oldt)1447 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 1448 super.onScrollChanged(l, t, oldl, oldt); 1449 onWorkspaceOverallScrollChanged(); 1450 1451 // Update the page indicator progress. 1452 boolean isTransitioning = mIsSwitchingState 1453 || (getLayoutTransition() != null && getLayoutTransition().isRunning()); 1454 if (!isTransitioning) { 1455 showPageIndicatorAtCurrentScroll(); 1456 } 1457 } 1458 showPageIndicatorAtCurrentScroll()1459 private void showPageIndicatorAtCurrentScroll() { 1460 if (mPageIndicator != null) { 1461 mPageIndicator.setScroll(getScrollX(), computeMaxScrollX()); 1462 } 1463 } 1464 1465 @Override overScroll(float amount)1466 protected void overScroll(float amount) { 1467 boolean shouldOverScroll = (amount <= 0 && (!hasCustomContent() || mIsRtl)) || 1468 (amount >= 0 && (!hasCustomContent() || !mIsRtl)); 1469 1470 boolean shouldScrollOverlay = mLauncherOverlay != null && 1471 ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl)); 1472 1473 boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlaySroll != 0 && 1474 ((amount >= 0 && !mIsRtl) || (amount <= 0 && mIsRtl)); 1475 1476 if (shouldScrollOverlay) { 1477 if (!mStartedSendingScrollEvents && mScrollInteractionBegan) { 1478 mStartedSendingScrollEvents = true; 1479 mLauncherOverlay.onScrollInteractionBegin(); 1480 } 1481 1482 mLastOverlaySroll = Math.abs(amount / getViewportWidth()); 1483 mLauncherOverlay.onScrollChange(mLastOverlaySroll, mIsRtl); 1484 } else if (shouldOverScroll) { 1485 dampedOverScroll(amount); 1486 } 1487 1488 if (shouldZeroOverlay) { 1489 mLauncherOverlay.onScrollChange(0, mIsRtl); 1490 } 1491 } 1492 1493 private final Interpolator mAlphaInterpolator = new DecelerateInterpolator(3f); 1494 1495 /** 1496 * The overlay scroll is being controlled locally, just update our overlay effect 1497 */ onOverlayScrollChanged(float scroll)1498 public void onOverlayScrollChanged(float scroll) { 1499 float offset = 0f; 1500 float slip = 0f; 1501 1502 scroll = Math.max(scroll - offset, 0); 1503 scroll = Math.min(1, scroll / (1 - offset)); 1504 1505 float alpha = 1 - mAlphaInterpolator.getInterpolation(scroll); 1506 float transX = mLauncher.getDragLayer().getMeasuredWidth() * scroll; 1507 transX *= 1 - slip; 1508 1509 if (mIsRtl) { 1510 transX = -transX; 1511 } 1512 mOverlayTranslation = transX; 1513 1514 // TODO(adamcohen): figure out a final effect here. We may need to recommend 1515 // different effects based on device performance. On at least one relatively high-end 1516 // device I've tried, translating the launcher causes things to get quite laggy. 1517 setWorkspaceTranslationAndAlpha(Direction.X, transX, alpha); 1518 setHotseatTranslationAndAlpha(Direction.X, transX, alpha); 1519 onWorkspaceOverallScrollChanged(); 1520 1521 mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_OVERLAY_SCROLL); 1522 } 1523 1524 /** 1525 * Moves the workspace UI in the Y direction. 1526 * @param translation the amount of shift. 1527 * @param alpha the alpha for the workspace page 1528 */ setWorkspaceYTranslationAndAlpha(float translation, float alpha)1529 public void setWorkspaceYTranslationAndAlpha(float translation, float alpha) { 1530 setWorkspaceTranslationAndAlpha(Direction.Y, translation, alpha); 1531 1532 mLauncher.getQsbContainer().setTranslationY(translation); 1533 mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_Y_TRANSLATION); 1534 } 1535 1536 /** 1537 * Moves the workspace UI in the provided direction. 1538 * @param direction the direction to move the workspace 1539 * @param translation the amount of shift. 1540 * @param alpha the alpha for the workspace page 1541 */ setWorkspaceTranslationAndAlpha(Direction direction, float translation, float alpha)1542 private void setWorkspaceTranslationAndAlpha(Direction direction, float translation, float alpha) { 1543 Property<View, Float> property = direction.viewProperty; 1544 mPageAlpha[direction.ordinal()] = alpha; 1545 float finalAlpha = mPageAlpha[0] * mPageAlpha[1]; 1546 1547 View currentChild = getChildAt(getCurrentPage()); 1548 if (currentChild != null) { 1549 property.set(currentChild, translation); 1550 currentChild.setAlpha(finalAlpha); 1551 } 1552 1553 // When the animation finishes, reset all pages, just in case we missed a page. 1554 if (Float.compare(translation, 0) == 0) { 1555 for (int i = getChildCount() - 1; i >= 0; i--) { 1556 View child = getChildAt(i); 1557 property.set(child, translation); 1558 child.setAlpha(finalAlpha); 1559 } 1560 } 1561 } 1562 1563 /** 1564 * Moves the Hotseat UI in the provided direction. 1565 * @param direction the direction to move the workspace 1566 * @param translation the amount of shift. 1567 * @param alpha the alpha for the hotseat page 1568 */ setHotseatTranslationAndAlpha(Direction direction, float translation, float alpha)1569 public void setHotseatTranslationAndAlpha(Direction direction, float translation, float alpha) { 1570 Property<View, Float> property = direction.viewProperty; 1571 // Skip the page indicator movement in the vertical bar layout 1572 if (direction != Direction.Y || !mLauncher.getDeviceProfile().isVerticalBarLayout()) { 1573 property.set(mPageIndicator, translation); 1574 } 1575 property.set(mLauncher.getHotseat(), translation); 1576 setHotseatAlphaAtIndex(alpha, direction.ordinal()); 1577 } 1578 setHotseatAlphaAtIndex(float alpha, int index)1579 private void setHotseatAlphaAtIndex(float alpha, int index) { 1580 mHotseatAlpha[index] = alpha; 1581 final float hotseatAlpha = mHotseatAlpha[0] * mHotseatAlpha[1] * mHotseatAlpha[2]; 1582 final float pageIndicatorAlpha = mHotseatAlpha[0] * mHotseatAlpha[2]; 1583 1584 mLauncher.getHotseat().setAlpha(hotseatAlpha); 1585 mPageIndicator.setAlpha(pageIndicatorAlpha); 1586 } 1587 createHotseatAlphaAnimator(float finalValue)1588 public ValueAnimator createHotseatAlphaAnimator(float finalValue) { 1589 if (Float.compare(finalValue, mHotseatAlpha[HOTSEAT_STATE_ALPHA_INDEX]) == 0) { 1590 // Return a dummy animator to avoid null checks. 1591 return ValueAnimator.ofFloat(0, 0); 1592 } else { 1593 ValueAnimator animator = ValueAnimator 1594 .ofFloat(mHotseatAlpha[HOTSEAT_STATE_ALPHA_INDEX], finalValue); 1595 animator.addUpdateListener(new AnimatorUpdateListener() { 1596 @Override 1597 public void onAnimationUpdate(ValueAnimator valueAnimator) { 1598 float value = (Float) valueAnimator.getAnimatedValue(); 1599 setHotseatAlphaAtIndex(value, HOTSEAT_STATE_ALPHA_INDEX); 1600 } 1601 }); 1602 1603 AccessibilityManager am = (AccessibilityManager) 1604 mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE); 1605 final boolean accessibilityEnabled = am.isEnabled(); 1606 animator.addUpdateListener( 1607 new AlphaUpdateListener(mLauncher.getHotseat(), accessibilityEnabled)); 1608 animator.addUpdateListener( 1609 new AlphaUpdateListener(mPageIndicator, accessibilityEnabled)); 1610 return animator; 1611 } 1612 } 1613 1614 @Override getPageShiftMatrix()1615 protected Matrix getPageShiftMatrix() { 1616 if (Float.compare(mOverlayTranslation, 0) != 0) { 1617 // The pages are translated by mOverlayTranslation. incorporate that in the 1618 // visible page calculation by shifting everything back by that same amount. 1619 mTempMatrix.set(getMatrix()); 1620 mTempMatrix.postTranslate(-mOverlayTranslation, 0); 1621 return mTempMatrix; 1622 } 1623 return super.getPageShiftMatrix(); 1624 } 1625 1626 @Override getEdgeVerticalPostion(int[] pos)1627 protected void getEdgeVerticalPostion(int[] pos) { 1628 View child = getChildAt(getPageCount() - 1); 1629 pos[0] = child.getTop(); 1630 pos[1] = child.getBottom(); 1631 } 1632 1633 @Override notifyPageSwitchListener()1634 protected void notifyPageSwitchListener() { 1635 super.notifyPageSwitchListener(); 1636 1637 if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) { 1638 mCustomContentShowing = true; 1639 if (mCustomContentCallbacks != null) { 1640 mCustomContentCallbacks.onShow(false); 1641 mCustomContentShowTime = System.currentTimeMillis(); 1642 } 1643 } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) { 1644 mCustomContentShowing = false; 1645 if (mCustomContentCallbacks != null) { 1646 mCustomContentCallbacks.onHide(); 1647 } 1648 } 1649 } 1650 getCustomContentCallbacks()1651 protected CustomContentCallbacks getCustomContentCallbacks() { 1652 return mCustomContentCallbacks; 1653 } 1654 setWallpaperDimension()1655 protected void setWallpaperDimension() { 1656 Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() { 1657 @Override 1658 public void run() { 1659 final Point size = LauncherAppState.getInstance() 1660 .getInvariantDeviceProfile().defaultWallpaperSize; 1661 if (size.x != mWallpaperManager.getDesiredMinimumWidth() 1662 || size.y != mWallpaperManager.getDesiredMinimumHeight()) { 1663 mWallpaperManager.suggestDesiredDimensions(size.x, size.y); 1664 } 1665 } 1666 }); 1667 } 1668 lockWallpaperToDefaultPage()1669 public void lockWallpaperToDefaultPage() { 1670 mWallpaperOffset.setLockToDefaultPage(true); 1671 } 1672 unlockWallpaperFromDefaultPageOnNextLayout()1673 public void unlockWallpaperFromDefaultPageOnNextLayout() { 1674 if (mWallpaperOffset.isLockedToDefaultPage()) { 1675 mUnlockWallpaperFromDefaultPageOnLayout = true; 1676 requestLayout(); 1677 } 1678 } 1679 snapToPage(int whichPage, Runnable r)1680 protected void snapToPage(int whichPage, Runnable r) { 1681 snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r); 1682 } 1683 snapToPage(int whichPage, int duration, Runnable r)1684 protected void snapToPage(int whichPage, int duration, Runnable r) { 1685 if (mDelayedSnapToPageRunnable != null) { 1686 mDelayedSnapToPageRunnable.run(); 1687 } 1688 mDelayedSnapToPageRunnable = r; 1689 snapToPage(whichPage, duration); 1690 } 1691 snapToScreenId(long screenId)1692 public void snapToScreenId(long screenId) { 1693 snapToScreenId(screenId, null); 1694 } 1695 snapToScreenId(long screenId, Runnable r)1696 protected void snapToScreenId(long screenId, Runnable r) { 1697 snapToPage(getPageIndexForScreenId(screenId), r); 1698 } 1699 1700 @Override computeScroll()1701 public void computeScroll() { 1702 super.computeScroll(); 1703 mWallpaperOffset.syncWithScroll(); 1704 } 1705 computeScrollWithoutInvalidation()1706 public void computeScrollWithoutInvalidation() { 1707 computeScrollHelper(false); 1708 } 1709 1710 @Override determineScrollingStart(MotionEvent ev, float touchSlopScale)1711 protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { 1712 if (!isSwitchingState()) { 1713 super.determineScrollingStart(ev, touchSlopScale); 1714 } 1715 } 1716 1717 @Override announceForAccessibility(CharSequence text)1718 public void announceForAccessibility(CharSequence text) { 1719 // Don't announce if apps is on top of us. 1720 if (!mLauncher.isAppsViewVisible()) { 1721 super.announceForAccessibility(text); 1722 } 1723 } 1724 showOutlinesTemporarily()1725 public void showOutlinesTemporarily() { 1726 if (!mIsPageMoving && !isTouchActive()) { 1727 snapToPage(mCurrentPage); 1728 } 1729 } 1730 updatePageAlphaValues(int screenCenter)1731 private void updatePageAlphaValues(int screenCenter) { 1732 if (mWorkspaceFadeInAdjacentScreens && 1733 !workspaceInModalState() && 1734 !mIsSwitchingState) { 1735 for (int i = numCustomPages(); i < getChildCount(); i++) { 1736 CellLayout child = (CellLayout) getChildAt(i); 1737 if (child != null) { 1738 float scrollProgress = getScrollProgress(screenCenter, child, i); 1739 float alpha = 1 - Math.abs(scrollProgress); 1740 child.getShortcutsAndWidgets().setAlpha(alpha); 1741 1742 if (isQsbContainerPage(i)) { 1743 mQsbAlphaController.setAlphaAtIndex(alpha, QSB_ALPHA_INDEX_PAGE_SCROLL); 1744 } 1745 } 1746 } 1747 } 1748 } 1749 hasCustomContent()1750 public boolean hasCustomContent() { 1751 return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID); 1752 } 1753 numCustomPages()1754 public int numCustomPages() { 1755 return hasCustomContent() ? 1 : 0; 1756 } 1757 isOnOrMovingToCustomContent()1758 public boolean isOnOrMovingToCustomContent() { 1759 return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE; 1760 } 1761 updateStateForCustomContent(int screenCenter)1762 private void updateStateForCustomContent(int screenCenter) { 1763 float translationX = 0; 1764 float progress = 0; 1765 if (hasCustomContent()) { 1766 int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID); 1767 1768 int scrollDelta = getScrollX() - getScrollForPage(index) - 1769 getLayoutTransitionOffsetForPage(index); 1770 float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index); 1771 translationX = scrollRange - scrollDelta; 1772 progress = (scrollRange - scrollDelta) / scrollRange; 1773 1774 if (mIsRtl) { 1775 translationX = Math.min(0, translationX); 1776 } else { 1777 translationX = Math.max(0, translationX); 1778 } 1779 progress = Math.max(0, progress); 1780 } 1781 1782 if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return; 1783 1784 CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID); 1785 if (progress > 0 && cc.getVisibility() != VISIBLE && !workspaceInModalState()) { 1786 cc.setVisibility(VISIBLE); 1787 } 1788 1789 mLastCustomContentScrollProgress = progress; 1790 1791 // We should only update the drag layer background alpha if we are not in all apps or the 1792 // widgets tray 1793 if (mState == State.NORMAL) { 1794 mLauncher.getDragLayer().setBackgroundAlpha(progress == 1 ? 0 : progress * 0.8f); 1795 } 1796 1797 if (mLauncher.getHotseat() != null) { 1798 mLauncher.getHotseat().setTranslationX(translationX); 1799 } 1800 1801 if (mPageIndicator != null) { 1802 mPageIndicator.setTranslationX(translationX); 1803 } 1804 1805 if (mCustomContentCallbacks != null) { 1806 mCustomContentCallbacks.onScrollProgressChanged(progress); 1807 } 1808 } 1809 1810 @Override screenScrolled(int screenCenter)1811 protected void screenScrolled(int screenCenter) { 1812 updatePageAlphaValues(screenCenter); 1813 updateStateForCustomContent(screenCenter); 1814 enableHwLayersOnVisiblePages(); 1815 } 1816 onAttachedToWindow()1817 protected void onAttachedToWindow() { 1818 super.onAttachedToWindow(); 1819 IBinder windowToken = getWindowToken(); 1820 mWallpaperOffset.setWindowToken(windowToken); 1821 computeScroll(); 1822 mDragController.setWindowToken(windowToken); 1823 } 1824 onDetachedFromWindow()1825 protected void onDetachedFromWindow() { 1826 super.onDetachedFromWindow(); 1827 mWallpaperOffset.setWindowToken(null); 1828 } 1829 onResume()1830 protected void onResume() { 1831 // Update wallpaper dimensions if they were changed since last onResume 1832 // (we also always set the wallpaper dimensions in the constructor) 1833 if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) { 1834 setWallpaperDimension(); 1835 } 1836 mWallpaperOffset.onResume(); 1837 } 1838 1839 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1840 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1841 if (mUnlockWallpaperFromDefaultPageOnLayout) { 1842 mWallpaperOffset.setLockToDefaultPage(false); 1843 mUnlockWallpaperFromDefaultPageOnLayout = false; 1844 } 1845 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { 1846 mWallpaperOffset.syncWithScroll(); 1847 mWallpaperOffset.jumpToFinal(); 1848 } 1849 super.onLayout(changed, left, top, right, bottom); 1850 mFirstPageScrollX = getScrollForPage(0); 1851 onWorkspaceOverallScrollChanged(); 1852 1853 final LayoutTransition transition = getLayoutTransition(); 1854 // If the transition is running defer updating max scroll, as some empty pages could 1855 // still be present, and a max scroll change could cause sudden jumps in scroll. 1856 if (transition != null && transition.isRunning()) { 1857 transition.addTransitionListener(new LayoutTransition.TransitionListener() { 1858 1859 @Override 1860 public void startTransition(LayoutTransition transition, ViewGroup container, 1861 View view, int transitionType) { 1862 mIgnoreQsbScroll = true; 1863 } 1864 1865 @Override 1866 public void endTransition(LayoutTransition transition, ViewGroup container, 1867 View view, int transitionType) { 1868 // Wait until all transitions are complete. 1869 if (!transition.isRunning()) { 1870 mIgnoreQsbScroll = false; 1871 transition.removeTransitionListener(this); 1872 mFirstPageScrollX = getScrollForPage(0); 1873 onWorkspaceOverallScrollChanged(); 1874 } 1875 } 1876 }); 1877 } 1878 1879 } 1880 1881 @Override getDescendantFocusability()1882 public int getDescendantFocusability() { 1883 if (workspaceInModalState()) { 1884 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 1885 } 1886 return super.getDescendantFocusability(); 1887 } 1888 workspaceInModalState()1889 public boolean workspaceInModalState() { 1890 return mState != State.NORMAL; 1891 } 1892 enableChildrenCache(int fromPage, int toPage)1893 void enableChildrenCache(int fromPage, int toPage) { 1894 if (fromPage > toPage) { 1895 final int temp = fromPage; 1896 fromPage = toPage; 1897 toPage = temp; 1898 } 1899 1900 final int screenCount = getChildCount(); 1901 1902 fromPage = Math.max(fromPage, 0); 1903 toPage = Math.min(toPage, screenCount - 1); 1904 1905 for (int i = fromPage; i <= toPage; i++) { 1906 final CellLayout layout = (CellLayout) getChildAt(i); 1907 layout.setChildrenDrawnWithCacheEnabled(true); 1908 layout.setChildrenDrawingCacheEnabled(true); 1909 } 1910 } 1911 clearChildrenCache()1912 void clearChildrenCache() { 1913 final int screenCount = getChildCount(); 1914 for (int i = 0; i < screenCount; i++) { 1915 final CellLayout layout = (CellLayout) getChildAt(i); 1916 layout.setChildrenDrawnWithCacheEnabled(false); 1917 // In software mode, we don't want the items to continue to be drawn into bitmaps 1918 if (!isHardwareAccelerated()) { 1919 layout.setChildrenDrawingCacheEnabled(false); 1920 } 1921 } 1922 } 1923 updateChildrenLayersEnabled(boolean force)1924 @Thunk void updateChildrenLayersEnabled(boolean force) { 1925 boolean small = mState == State.OVERVIEW || mIsSwitchingState; 1926 boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving(); 1927 1928 if (enableChildrenLayers != mChildrenLayersEnabled) { 1929 mChildrenLayersEnabled = enableChildrenLayers; 1930 if (mChildrenLayersEnabled) { 1931 enableHwLayersOnVisiblePages(); 1932 } else { 1933 for (int i = 0; i < getPageCount(); i++) { 1934 final CellLayout cl = (CellLayout) getChildAt(i); 1935 cl.enableHardwareLayer(false); 1936 } 1937 } 1938 } 1939 } 1940 enableHwLayersOnVisiblePages()1941 private void enableHwLayersOnVisiblePages() { 1942 if (mChildrenLayersEnabled) { 1943 final int screenCount = getChildCount(); 1944 getVisiblePages(mTempVisiblePagesRange); 1945 int leftScreen = mTempVisiblePagesRange[0]; 1946 int rightScreen = mTempVisiblePagesRange[1]; 1947 if (leftScreen == rightScreen) { 1948 // make sure we're caching at least two pages always 1949 if (rightScreen < screenCount - 1) { 1950 rightScreen++; 1951 } else if (leftScreen > 0) { 1952 leftScreen--; 1953 } 1954 } 1955 1956 final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID); 1957 for (int i = 0; i < screenCount; i++) { 1958 final CellLayout layout = (CellLayout) getPageAt(i); 1959 1960 // enable layers between left and right screen inclusive, except for the 1961 // customScreen, which may animate its content during transitions. 1962 boolean enableLayer = layout != customScreen && 1963 leftScreen <= i && i <= rightScreen && shouldDrawChild(layout); 1964 layout.enableHardwareLayer(enableLayer); 1965 } 1966 } 1967 } 1968 buildPageHardwareLayers()1969 public void buildPageHardwareLayers() { 1970 // force layers to be enabled just for the call to buildLayer 1971 updateChildrenLayersEnabled(true); 1972 if (getWindowToken() != null) { 1973 final int childCount = getChildCount(); 1974 for (int i = 0; i < childCount; i++) { 1975 CellLayout cl = (CellLayout) getChildAt(i); 1976 cl.buildHardwareLayer(); 1977 } 1978 } 1979 updateChildrenLayersEnabled(false); 1980 } 1981 1982 @Override getVisiblePages(int[] range)1983 protected void getVisiblePages(int[] range) { 1984 super.getVisiblePages(range); 1985 if (mForceDrawAdjacentPages) { 1986 // In overview mode, make sure that the two side pages are visible. 1987 range[0] = Utilities.boundToRange(getCurrentPage() - 1, numCustomPages(), range[1]); 1988 range[1] = Utilities.boundToRange(getCurrentPage() + 1, range[0], getPageCount() - 1); 1989 } 1990 } 1991 onWallpaperTap(MotionEvent ev)1992 protected void onWallpaperTap(MotionEvent ev) { 1993 final int[] position = mTempXY; 1994 getLocationOnScreen(position); 1995 1996 int pointerIndex = ev.getActionIndex(); 1997 position[0] += (int) ev.getX(pointerIndex); 1998 position[1] += (int) ev.getY(pointerIndex); 1999 2000 mWallpaperManager.sendWallpaperCommand(getWindowToken(), 2001 ev.getAction() == MotionEvent.ACTION_UP 2002 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP, 2003 position[0], position[1], 0, null); 2004 } 2005 prepareDragWithProvider(DragPreviewProvider outlineProvider)2006 public void prepareDragWithProvider(DragPreviewProvider outlineProvider) { 2007 mOutlineProvider = outlineProvider; 2008 } 2009 exitWidgetResizeMode()2010 public void exitWidgetResizeMode() { 2011 DragLayer dragLayer = mLauncher.getDragLayer(); 2012 dragLayer.clearAllResizeFrames(); 2013 } 2014 2015 @Override getFreeScrollPageRange(int[] range)2016 protected void getFreeScrollPageRange(int[] range) { 2017 getOverviewModePages(range); 2018 } 2019 getOverviewModePages(int[] range)2020 private void getOverviewModePages(int[] range) { 2021 int start = numCustomPages(); 2022 int end = getChildCount() - 1; 2023 2024 range[0] = Math.max(0, Math.min(start, getChildCount() - 1)); 2025 range[1] = Math.max(0, end); 2026 } 2027 onStartReordering()2028 public void onStartReordering() { 2029 super.onStartReordering(); 2030 // Reordering handles its own animations, disable the automatic ones. 2031 disableLayoutTransitions(); 2032 } 2033 onEndReordering()2034 public void onEndReordering() { 2035 super.onEndReordering(); 2036 2037 if (mLauncher.isWorkspaceLoading()) { 2038 // Invalid and dangerous operation if workspace is loading 2039 return; 2040 } 2041 2042 mScreenOrder.clear(); 2043 int count = getChildCount(); 2044 for (int i = 0; i < count; i++) { 2045 CellLayout cl = ((CellLayout) getChildAt(i)); 2046 mScreenOrder.add(getIdForScreen(cl)); 2047 } 2048 2049 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder); 2050 2051 // Re-enable auto layout transitions for page deletion. 2052 enableLayoutTransitions(); 2053 } 2054 isInOverviewMode()2055 public boolean isInOverviewMode() { 2056 return mState == State.OVERVIEW; 2057 } 2058 snapToPageFromOverView(int whichPage)2059 public void snapToPageFromOverView(int whichPage) { 2060 mStateTransitionAnimation.snapToPageFromOverView(whichPage); 2061 } 2062 getOverviewModeTranslationY()2063 int getOverviewModeTranslationY() { 2064 DeviceProfile grid = mLauncher.getDeviceProfile(); 2065 int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight(); 2066 2067 int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight()); 2068 Rect workspacePadding = grid.getWorkspacePadding(sTempRect); 2069 int workspaceTop = mInsets.top + workspacePadding.top; 2070 int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom; 2071 int overviewTop = mInsets.top; 2072 int overviewBottom = getViewportHeight() - mInsets.bottom - overviewButtonBarHeight; 2073 int workspaceOffsetTopEdge = workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2; 2074 int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2; 2075 return -workspaceOffsetTopEdge + overviewOffsetTopEdge; 2076 } 2077 getSpringLoadedTranslationY()2078 float getSpringLoadedTranslationY() { 2079 DeviceProfile grid = mLauncher.getDeviceProfile(); 2080 if (grid.isVerticalBarLayout() || getChildCount() == 0) { 2081 return 0; 2082 } 2083 2084 float scaledHeight = grid.workspaceSpringLoadShrinkFactor * getNormalChildHeight(); 2085 float shrunkTop = mInsets.top + grid.dropTargetBarSizePx; 2086 float shrunkBottom = getViewportHeight() - mInsets.bottom 2087 - grid.getWorkspacePadding(sTempRect).bottom 2088 - grid.workspaceSpringLoadedBottomSpace; 2089 float totalShrunkSpace = shrunkBottom - shrunkTop; 2090 2091 float desiredCellTop = shrunkTop + (totalShrunkSpace - scaledHeight) / 2; 2092 2093 float halfHeight = getHeight() / 2; 2094 float myCenter = getTop() + halfHeight; 2095 float cellTopFromCenter = halfHeight - getChildAt(0).getTop(); 2096 float actualCellTop = myCenter - cellTopFromCenter * grid.workspaceSpringLoadShrinkFactor; 2097 return (desiredCellTop - actualCellTop) / grid.workspaceSpringLoadShrinkFactor; 2098 } 2099 getOverviewModeShrinkFactor()2100 float getOverviewModeShrinkFactor() { 2101 return mOverviewModeShrinkFactor; 2102 } 2103 2104 /** 2105 * Sets the current workspace {@link State}, returning an animation transitioning the workspace 2106 * to that new state. 2107 */ setStateWithAnimation(State toState, boolean animated, HashMap<View, Integer> layerViews)2108 public Animator setStateWithAnimation(State toState, boolean animated, 2109 HashMap<View, Integer> layerViews) { 2110 // Create the animation to the new state 2111 AnimatorSet workspaceAnim = mStateTransitionAnimation.getAnimationToState(mState, 2112 toState, animated, layerViews); 2113 2114 boolean shouldNotifyWidgetChange = !mState.shouldUpdateWidget 2115 && toState.shouldUpdateWidget; 2116 // Update the current state 2117 mState = toState; 2118 updateAccessibilityFlags(); 2119 2120 if (shouldNotifyWidgetChange) { 2121 mLauncher.notifyWidgetProvidersChanged(); 2122 } 2123 2124 if (mOnStateChangeListener != null) { 2125 mOnStateChangeListener.prepareStateChange(toState, animated ? workspaceAnim : null); 2126 } 2127 2128 return workspaceAnim; 2129 } 2130 getState()2131 public State getState() { 2132 return mState; 2133 } 2134 updateAccessibilityFlags()2135 public void updateAccessibilityFlags() { 2136 // TODO: Update the accessibility flags appropriately when dragging. 2137 if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { 2138 if (Utilities.ATLEAST_LOLLIPOP) { 2139 int total = getPageCount(); 2140 for (int i = numCustomPages(); i < total; i++) { 2141 updateAccessibilityFlags((CellLayout) getPageAt(i), i); 2142 } 2143 setImportantForAccessibility((mState == State.NORMAL || mState == State.OVERVIEW) 2144 ? IMPORTANT_FOR_ACCESSIBILITY_AUTO 2145 : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 2146 } else { 2147 int accessible = mState == State.NORMAL ? 2148 IMPORTANT_FOR_ACCESSIBILITY_AUTO : 2149 IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; 2150 setImportantForAccessibility(accessible); 2151 } 2152 } 2153 } 2154 updateAccessibilityFlags(CellLayout page, int pageNo)2155 private void updateAccessibilityFlags(CellLayout page, int pageNo) { 2156 if (mState == State.OVERVIEW) { 2157 page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 2158 page.getShortcutsAndWidgets().setImportantForAccessibility( 2159 IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 2160 page.setContentDescription(getPageDescription(pageNo)); 2161 2162 // No custom action for the first page. 2163 if (!FeatureFlags.QSB_ON_FIRST_SCREEN || pageNo > 0) { 2164 if (mPagesAccessibilityDelegate == null) { 2165 mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this); 2166 } 2167 page.setAccessibilityDelegate(mPagesAccessibilityDelegate); 2168 } 2169 } else { 2170 int accessible = mState == State.NORMAL ? 2171 IMPORTANT_FOR_ACCESSIBILITY_AUTO : 2172 IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; 2173 page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 2174 page.getShortcutsAndWidgets().setImportantForAccessibility(accessible); 2175 page.setContentDescription(null); 2176 page.setAccessibilityDelegate(null); 2177 } 2178 } 2179 2180 @Override onLauncherTransitionPrepare(Launcher l, boolean animated, boolean multiplePagesVisible)2181 public void onLauncherTransitionPrepare(Launcher l, boolean animated, 2182 boolean multiplePagesVisible) { 2183 mIsSwitchingState = true; 2184 mTransitionProgress = 0; 2185 2186 if (multiplePagesVisible) { 2187 mForceDrawAdjacentPages = true; 2188 } 2189 invalidate(); // This will call dispatchDraw(), which calls getVisiblePages(). 2190 2191 updateChildrenLayersEnabled(false); 2192 hideCustomContentIfNecessary(); 2193 } 2194 2195 @Override onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace)2196 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { 2197 if (mPageIndicator != null) { 2198 boolean isNewStateSpringLoaded = mState == State.SPRING_LOADED; 2199 mPageIndicator.setShouldAutoHide(!isNewStateSpringLoaded); 2200 if (isNewStateSpringLoaded) { 2201 // Show the page indicator at the same time as the rest of the transition. 2202 showPageIndicatorAtCurrentScroll(); 2203 } 2204 } 2205 } 2206 2207 @Override onLauncherTransitionStep(Launcher l, float t)2208 public void onLauncherTransitionStep(Launcher l, float t) { 2209 mTransitionProgress = t; 2210 } 2211 2212 @Override onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace)2213 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { 2214 mIsSwitchingState = false; 2215 updateChildrenLayersEnabled(false); 2216 showCustomContentIfNecessary(); 2217 mForceDrawAdjacentPages = false; 2218 if (mState == State.SPRING_LOADED) { 2219 showPageIndicatorAtCurrentScroll(); 2220 } 2221 } 2222 updateCustomContentVisibility()2223 void updateCustomContentVisibility() { 2224 int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE; 2225 setCustomContentVisibility(visibility); 2226 } 2227 setCustomContentVisibility(int visibility)2228 void setCustomContentVisibility(int visibility) { 2229 if (hasCustomContent()) { 2230 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility); 2231 } 2232 } 2233 showCustomContentIfNecessary()2234 void showCustomContentIfNecessary() { 2235 boolean show = mState == Workspace.State.NORMAL; 2236 if (show && hasCustomContent()) { 2237 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE); 2238 } 2239 } 2240 hideCustomContentIfNecessary()2241 void hideCustomContentIfNecessary() { 2242 boolean hide = mState != Workspace.State.NORMAL; 2243 if (hide && hasCustomContent()) { 2244 disableLayoutTransitions(); 2245 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE); 2246 enableLayoutTransitions(); 2247 } 2248 } 2249 2250 /** 2251 * Returns the drawable for the given text view. 2252 */ getTextViewIcon(TextView tv)2253 public static Drawable getTextViewIcon(TextView tv) { 2254 final Drawable[] drawables = tv.getCompoundDrawables(); 2255 for (int i = 0; i < drawables.length; i++) { 2256 if (drawables[i] != null) { 2257 return drawables[i]; 2258 } 2259 } 2260 return null; 2261 } 2262 startDrag(CellLayout.CellInfo cellInfo, DragOptions options)2263 public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) { 2264 View child = cellInfo.cell; 2265 2266 // Make sure the drag was started by a long press as opposed to a long click. 2267 if (!child.isInTouchMode()) { 2268 return; 2269 } 2270 2271 mDragInfo = cellInfo; 2272 child.setVisibility(INVISIBLE); 2273 CellLayout layout = (CellLayout) child.getParent().getParent(); 2274 layout.prepareChildForDrag(child); 2275 2276 if (options.isAccessibleDrag) { 2277 mDragController.addDragListener(new AccessibileDragListenerAdapter( 2278 this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) { 2279 @Override 2280 protected void enableAccessibleDrag(boolean enable) { 2281 super.enableAccessibleDrag(enable); 2282 setEnableForLayout(mLauncher.getHotseat().getLayout(),enable); 2283 2284 // We need to allow our individual children to become click handlers in this 2285 // case, so temporarily unset the click handlers. 2286 setOnClickListener(enable ? null : mLauncher); 2287 } 2288 }); 2289 } 2290 2291 beginDragShared(child, this, options); 2292 } 2293 beginDragShared(View child, DragSource source, DragOptions options)2294 public void beginDragShared(View child, DragSource source, DragOptions options) { 2295 Object dragObject = child.getTag(); 2296 if (!(dragObject instanceof ItemInfo)) { 2297 String msg = "Drag started with a view that has no tag set. This " 2298 + "will cause a crash (issue 11627249) down the line. " 2299 + "View: " + child + " tag: " + child.getTag(); 2300 throw new IllegalStateException(msg); 2301 } 2302 beginDragShared(child, source, (ItemInfo) dragObject, 2303 new DragPreviewProvider(child), options); 2304 } 2305 2306 beginDragShared(View child, DragSource source, ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions)2307 public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject, 2308 DragPreviewProvider previewProvider, DragOptions dragOptions) { 2309 child.clearFocus(); 2310 child.setPressed(false); 2311 mOutlineProvider = previewProvider; 2312 2313 // The drag bitmap follows the touch point around on the screen 2314 final Bitmap b = previewProvider.createDragBitmap(mCanvas); 2315 int halfPadding = previewProvider.previewPadding / 2; 2316 2317 float scale = previewProvider.getScaleAndPosition(b, mTempXY); 2318 int dragLayerX = mTempXY[0]; 2319 int dragLayerY = mTempXY[1]; 2320 2321 DeviceProfile grid = mLauncher.getDeviceProfile(); 2322 Point dragVisualizeOffset = null; 2323 Rect dragRect = null; 2324 if (child instanceof BubbleTextView) { 2325 int iconSize = grid.iconSizePx; 2326 int top = child.getPaddingTop(); 2327 int left = (b.getWidth() - iconSize) / 2; 2328 int right = left + iconSize; 2329 int bottom = top + iconSize; 2330 dragLayerY += top; 2331 // Note: The drag region is used to calculate drag layer offsets, but the 2332 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. 2333 dragVisualizeOffset = new Point(- halfPadding, halfPadding); 2334 dragRect = new Rect(left, top, right, bottom); 2335 } else if (child instanceof FolderIcon) { 2336 int previewSize = grid.folderIconSizePx; 2337 dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop()); 2338 dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize); 2339 } 2340 2341 // Clear the pressed state if necessary 2342 if (child instanceof BubbleTextView) { 2343 BubbleTextView icon = (BubbleTextView) child; 2344 icon.clearPressedBackground(); 2345 } 2346 2347 if (child.getParent() instanceof ShortcutAndWidgetContainer) { 2348 mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); 2349 } 2350 2351 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, 2352 dragObject, dragVisualizeOffset, dragRect, scale, dragOptions); 2353 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); 2354 b.recycle(); 2355 return dv; 2356 } 2357 transitionStateShouldAllowDrop()2358 public boolean transitionStateShouldAllowDrop() { 2359 return ((!isSwitchingState() || mTransitionProgress > 0.5f) && 2360 (mState == State.NORMAL || mState == State.SPRING_LOADED)); 2361 } 2362 2363 /** 2364 * {@inheritDoc} 2365 */ acceptDrop(DragObject d)2366 public boolean acceptDrop(DragObject d) { 2367 // If it's an external drop (e.g. from All Apps), check if it should be accepted 2368 CellLayout dropTargetLayout = mDropToLayout; 2369 if (d.dragSource != this) { 2370 // Don't accept the drop if we're not over a screen at time of drop 2371 if (dropTargetLayout == null) { 2372 return false; 2373 } 2374 if (!transitionStateShouldAllowDrop()) return false; 2375 2376 mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); 2377 2378 // We want the point to be mapped to the dragTarget. 2379 if (mLauncher.isHotseatLayout(dropTargetLayout)) { 2380 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 2381 } else { 2382 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter); 2383 } 2384 2385 int spanX = 1; 2386 int spanY = 1; 2387 if (mDragInfo != null) { 2388 final CellLayout.CellInfo dragCellInfo = mDragInfo; 2389 spanX = dragCellInfo.spanX; 2390 spanY = dragCellInfo.spanY; 2391 } else { 2392 spanX = d.dragInfo.spanX; 2393 spanY = d.dragInfo.spanY; 2394 } 2395 2396 int minSpanX = spanX; 2397 int minSpanY = spanY; 2398 if (d.dragInfo instanceof PendingAddWidgetInfo) { 2399 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX; 2400 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY; 2401 } 2402 2403 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2404 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout, 2405 mTargetCell); 2406 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2407 mDragViewVisualCenter[1], mTargetCell); 2408 if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo, 2409 dropTargetLayout, mTargetCell, distance, true)) { 2410 return true; 2411 } 2412 2413 if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo, 2414 dropTargetLayout, mTargetCell, distance)) { 2415 return true; 2416 } 2417 2418 int[] resultSpan = new int[2]; 2419 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0], 2420 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 2421 null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP); 2422 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 2423 2424 // Don't accept the drop if there's no room for the item 2425 if (!foundCell) { 2426 // Don't show the message if we are dropping on the AllApps button and the hotseat 2427 // is full 2428 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 2429 if (mTargetCell != null && isHotseat && !FeatureFlags.NO_ALL_APPS_ICON) { 2430 Hotseat hotseat = mLauncher.getHotseat(); 2431 if (mLauncher.getDeviceProfile().inv.isAllAppsButtonRank( 2432 hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) { 2433 return false; 2434 } 2435 } 2436 2437 mLauncher.showOutOfSpaceMessage(isHotseat); 2438 return false; 2439 } 2440 } 2441 2442 long screenId = getIdForScreen(dropTargetLayout); 2443 if (screenId == EXTRA_EMPTY_SCREEN_ID) { 2444 commitExtraEmptyScreen(); 2445 } 2446 2447 return true; 2448 } 2449 willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float distance, boolean considerTimeout)2450 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, 2451 float distance, boolean considerTimeout) { 2452 if (distance > mMaxDistanceForFolderCreation) return false; 2453 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2454 return willCreateUserFolder(info, dropOverView, considerTimeout); 2455 } 2456 willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout)2457 boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) { 2458 if (dropOverView != null) { 2459 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 2460 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) { 2461 return false; 2462 } 2463 } 2464 2465 boolean hasntMoved = false; 2466 if (mDragInfo != null) { 2467 hasntMoved = dropOverView == mDragInfo.cell; 2468 } 2469 2470 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) { 2471 return false; 2472 } 2473 2474 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo); 2475 boolean willBecomeShortcut = 2476 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 2477 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT || 2478 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT); 2479 2480 return (aboveShortcut && willBecomeShortcut); 2481 } 2482 willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, float distance)2483 boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, 2484 float distance) { 2485 if (distance > mMaxDistanceForFolderCreation) return false; 2486 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2487 return willAddToExistingUserFolder(dragInfo, dropOverView); 2488 2489 } willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView)2490 boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) { 2491 if (dropOverView != null) { 2492 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 2493 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) { 2494 return false; 2495 } 2496 } 2497 2498 if (dropOverView instanceof FolderIcon) { 2499 FolderIcon fi = (FolderIcon) dropOverView; 2500 if (fi.acceptDrop(dragInfo)) { 2501 return true; 2502 } 2503 } 2504 return false; 2505 } 2506 createUserFolderIfNecessary(View newView, long container, CellLayout target, int[] targetCell, float distance, boolean external, DragView dragView, Runnable postAnimationRunnable)2507 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target, 2508 int[] targetCell, float distance, boolean external, DragView dragView, 2509 Runnable postAnimationRunnable) { 2510 if (distance > mMaxDistanceForFolderCreation) return false; 2511 View v = target.getChildAt(targetCell[0], targetCell[1]); 2512 2513 boolean hasntMoved = false; 2514 if (mDragInfo != null) { 2515 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); 2516 hasntMoved = (mDragInfo.cellX == targetCell[0] && 2517 mDragInfo.cellY == targetCell[1]) && (cellParent == target); 2518 } 2519 2520 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; 2521 mCreateUserFolderOnDrop = false; 2522 final long screenId = getIdForScreen(target); 2523 2524 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo); 2525 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo); 2526 2527 if (aboveShortcut && willBecomeShortcut) { 2528 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag(); 2529 ShortcutInfo destInfo = (ShortcutInfo) v.getTag(); 2530 // if the drag started here, we need to remove it from the workspace 2531 if (!external) { 2532 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2533 } 2534 2535 Rect folderLocation = new Rect(); 2536 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation); 2537 target.removeView(v); 2538 2539 FolderIcon fi = 2540 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]); 2541 destInfo.cellX = -1; 2542 destInfo.cellY = -1; 2543 sourceInfo.cellX = -1; 2544 sourceInfo.cellY = -1; 2545 2546 // If the dragView is null, we can't animate 2547 boolean animate = dragView != null; 2548 if (animate) { 2549 // In order to keep everything continuous, we hand off the currently rendered 2550 // folder background to the newly created icon. This preserves animation state. 2551 fi.setFolderBackground(mFolderCreateBg); 2552 mFolderCreateBg = new FolderIcon.PreviewBackground(); 2553 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, 2554 postAnimationRunnable); 2555 } else { 2556 fi.prepareCreate(v); 2557 fi.addItem(destInfo); 2558 fi.addItem(sourceInfo); 2559 } 2560 return true; 2561 } 2562 return false; 2563 } 2564 addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, float distance, DragObject d, boolean external)2565 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, 2566 float distance, DragObject d, boolean external) { 2567 if (distance > mMaxDistanceForFolderCreation) return false; 2568 2569 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2570 if (!mAddToExistingFolderOnDrop) return false; 2571 mAddToExistingFolderOnDrop = false; 2572 2573 if (dropOverView instanceof FolderIcon) { 2574 FolderIcon fi = (FolderIcon) dropOverView; 2575 if (fi.acceptDrop(d.dragInfo)) { 2576 fi.onDrop(d); 2577 2578 // if the drag started here, we need to remove it from the workspace 2579 if (!external) { 2580 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2581 } 2582 return true; 2583 } 2584 } 2585 return false; 2586 } 2587 2588 @Override prepareAccessibilityDrop()2589 public void prepareAccessibilityDrop() { } 2590 onDrop(final DragObject d)2591 public void onDrop(final DragObject d) { 2592 mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); 2593 CellLayout dropTargetLayout = mDropToLayout; 2594 2595 // We want the point to be mapped to the dragTarget. 2596 if (dropTargetLayout != null) { 2597 if (mLauncher.isHotseatLayout(dropTargetLayout)) { 2598 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 2599 } else { 2600 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter); 2601 } 2602 } 2603 2604 int snapScreen = -1; 2605 boolean resizeOnDrop = false; 2606 if (d.dragSource != this) { 2607 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], 2608 (int) mDragViewVisualCenter[1] }; 2609 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d); 2610 } else if (mDragInfo != null) { 2611 final View cell = mDragInfo.cell; 2612 2613 if (dropTargetLayout != null && !d.cancelled) { 2614 // Move internally 2615 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); 2616 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 2617 long container = hasMovedIntoHotseat ? 2618 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 2619 LauncherSettings.Favorites.CONTAINER_DESKTOP; 2620 long screenId = (mTargetCell[0] < 0) ? 2621 mDragInfo.screenId : getIdForScreen(dropTargetLayout); 2622 int spanX = mDragInfo != null ? mDragInfo.spanX : 1; 2623 int spanY = mDragInfo != null ? mDragInfo.spanY : 1; 2624 // First we find the cell nearest to point at which the item is 2625 // dropped, without any consideration to whether there is an item there. 2626 2627 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) 2628 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); 2629 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2630 mDragViewVisualCenter[1], mTargetCell); 2631 2632 // If the item being dropped is a shortcut and the nearest drop 2633 // cell also contains a shortcut, then create a folder with the two shortcuts. 2634 if (!mInScrollArea && createUserFolderIfNecessary(cell, container, 2635 dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) { 2636 return; 2637 } 2638 2639 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, 2640 distance, d, false)) { 2641 return; 2642 } 2643 2644 // Aside from the special case where we're dropping a shortcut onto a shortcut, 2645 // we need to find the nearest cell location that is vacant 2646 ItemInfo item = d.dragInfo; 2647 int minSpanX = item.spanX; 2648 int minSpanY = item.spanY; 2649 if (item.minSpanX > 0 && item.minSpanY > 0) { 2650 minSpanX = item.minSpanX; 2651 minSpanY = item.minSpanY; 2652 } 2653 2654 int[] resultSpan = new int[2]; 2655 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0], 2656 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, 2657 mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); 2658 2659 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 2660 2661 // if the widget resizes on drop 2662 if (foundCell && (cell instanceof AppWidgetHostView) && 2663 (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) { 2664 resizeOnDrop = true; 2665 item.spanX = resultSpan[0]; 2666 item.spanY = resultSpan[1]; 2667 AppWidgetHostView awhv = (AppWidgetHostView) cell; 2668 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], 2669 resultSpan[1]); 2670 } 2671 2672 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) { 2673 snapScreen = getPageIndexForScreenId(screenId); 2674 snapToPage(snapScreen); 2675 } 2676 2677 if (foundCell) { 2678 final ItemInfo info = (ItemInfo) cell.getTag(); 2679 if (hasMovedLayouts) { 2680 // Reparent the view 2681 CellLayout parentCell = getParentCellLayoutForView(cell); 2682 if (parentCell != null) { 2683 parentCell.removeView(cell); 2684 } else if (ProviderConfig.IS_DOGFOOD_BUILD) { 2685 throw new NullPointerException("mDragInfo.cell has null parent"); 2686 } 2687 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], 2688 info.spanX, info.spanY); 2689 } 2690 2691 // update the item's position after drop 2692 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 2693 lp.cellX = lp.tmpCellX = mTargetCell[0]; 2694 lp.cellY = lp.tmpCellY = mTargetCell[1]; 2695 lp.cellHSpan = item.spanX; 2696 lp.cellVSpan = item.spanY; 2697 lp.isLockedToGrid = true; 2698 2699 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && 2700 cell instanceof LauncherAppWidgetHostView) { 2701 final CellLayout cellLayout = dropTargetLayout; 2702 // We post this call so that the widget has a chance to be placed 2703 // in its final location 2704 2705 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; 2706 AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo(); 2707 if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE 2708 && !d.accessibleDrag) { 2709 mDelayedResizeRunnable = new Runnable() { 2710 public void run() { 2711 if (!isPageMoving() && !mIsSwitchingState) { 2712 DragLayer dragLayer = mLauncher.getDragLayer(); 2713 dragLayer.addResizeFrame(info, hostView, cellLayout); 2714 } 2715 } 2716 }; 2717 } 2718 } 2719 2720 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX, 2721 lp.cellY, item.spanX, item.spanY); 2722 } else { 2723 // If we can't find a drop location, we return the item to its original position 2724 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 2725 mTargetCell[0] = lp.cellX; 2726 mTargetCell[1] = lp.cellY; 2727 CellLayout layout = (CellLayout) cell.getParent().getParent(); 2728 layout.markCellsAsOccupiedForView(cell); 2729 } 2730 } 2731 2732 final CellLayout parent = (CellLayout) cell.getParent().getParent(); 2733 // Prepare it to be animated into its new position 2734 // This must be called after the view has been re-parented 2735 final Runnable onCompleteRunnable = new Runnable() { 2736 @Override 2737 public void run() { 2738 mAnimatingViewIntoPlace = false; 2739 updateChildrenLayersEnabled(false); 2740 } 2741 }; 2742 mAnimatingViewIntoPlace = true; 2743 if (d.dragView.hasDrawn()) { 2744 final ItemInfo info = (ItemInfo) cell.getTag(); 2745 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET 2746 || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 2747 if (isWidget) { 2748 int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : 2749 ANIMATE_INTO_POSITION_AND_DISAPPEAR; 2750 animateWidgetDrop(info, parent, d.dragView, 2751 onCompleteRunnable, animationType, cell, false); 2752 } else { 2753 int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; 2754 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, 2755 onCompleteRunnable, this); 2756 } 2757 } else { 2758 d.deferDragViewCleanupPostAnimation = false; 2759 cell.setVisibility(VISIBLE); 2760 } 2761 parent.onDropChild(cell); 2762 } 2763 if (d.stateAnnouncer != null) { 2764 d.stateAnnouncer.completeAction(R.string.item_moved); 2765 } 2766 } 2767 2768 /** 2769 * Computes the area relative to dragLayer which is used to display a page. 2770 */ 2771 public void getPageAreaRelativeToDragLayer(Rect outArea) { 2772 CellLayout child = (CellLayout) getChildAt(getNextPage()); 2773 if (child == null) { 2774 return; 2775 } 2776 ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets(); 2777 2778 // Use the absolute left instead of the child left, as we want the visible area 2779 // irrespective of the visible child. Since the view can only scroll horizontally, the 2780 // top position is not affected. 2781 mTempXY[0] = getViewportOffsetX() + getPaddingLeft() + boundingLayout.getLeft(); 2782 mTempXY[1] = child.getTop() + boundingLayout.getTop(); 2783 2784 float scale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY); 2785 outArea.set(mTempXY[0], mTempXY[1], 2786 (int) (mTempXY[0] + scale * boundingLayout.getMeasuredWidth()), 2787 (int) (mTempXY[1] + scale * boundingLayout.getMeasuredHeight())); 2788 } 2789 2790 @Override 2791 public void onDragEnter(DragObject d) { 2792 if (ENFORCE_DRAG_EVENT_ORDER) { 2793 enfoceDragParity("onDragEnter", 1, 1); 2794 } 2795 2796 mCreateUserFolderOnDrop = false; 2797 mAddToExistingFolderOnDrop = false; 2798 2799 mDropToLayout = null; 2800 setDropLayoutForDragObject(d); 2801 2802 if (!workspaceInModalState() && FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { 2803 mLauncher.getDragLayer().showPageHints(); 2804 } 2805 } 2806 2807 @Override 2808 public void onDragExit(DragObject d) { 2809 if (ENFORCE_DRAG_EVENT_ORDER) { 2810 enfoceDragParity("onDragExit", -1, 0); 2811 } 2812 2813 // Here we store the final page that will be dropped to, if the workspace in fact 2814 // receives the drop 2815 if (mInScrollArea) { 2816 if (isPageMoving()) { 2817 // If the user drops while the page is scrolling, we should use that page as the 2818 // destination instead of the page that is being hovered over. 2819 mDropToLayout = (CellLayout) getPageAt(getNextPage()); 2820 } else { 2821 mDropToLayout = mDragOverlappingLayout; 2822 } 2823 } else { 2824 mDropToLayout = mDragTargetLayout; 2825 } 2826 2827 if (mDragMode == DRAG_MODE_CREATE_FOLDER) { 2828 mCreateUserFolderOnDrop = true; 2829 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) { 2830 mAddToExistingFolderOnDrop = true; 2831 } 2832 2833 // Reset the scroll area and previous drag target 2834 onResetScrollArea(); 2835 setCurrentDropLayout(null); 2836 setCurrentDragOverlappingLayout(null); 2837 2838 mSpringLoadedDragController.cancel(); 2839 2840 mLauncher.getDragLayer().hidePageHints(); 2841 } 2842 2843 private void enfoceDragParity(String event, int update, int expectedValue) { 2844 enfoceDragParity(this, event, update, expectedValue); 2845 for (int i = 0; i < getChildCount(); i++) { 2846 enfoceDragParity(getChildAt(i), event, update, expectedValue); 2847 } 2848 } 2849 2850 private void enfoceDragParity(View v, String event, int update, int expectedValue) { 2851 Object tag = v.getTag(R.id.drag_event_parity); 2852 int value = tag == null ? 0 : (Integer) tag; 2853 value += update; 2854 v.setTag(R.id.drag_event_parity, value); 2855 2856 if (value != expectedValue) { 2857 Log.e(TAG, event + ": Drag contract violated: " + value); 2858 } 2859 } 2860 2861 void setCurrentDropLayout(CellLayout layout) { 2862 if (mDragTargetLayout != null) { 2863 mDragTargetLayout.revertTempState(); 2864 mDragTargetLayout.onDragExit(); 2865 } 2866 mDragTargetLayout = layout; 2867 if (mDragTargetLayout != null) { 2868 mDragTargetLayout.onDragEnter(); 2869 } 2870 cleanupReorder(true); 2871 cleanupFolderCreation(); 2872 setCurrentDropOverCell(-1, -1); 2873 } 2874 2875 void setCurrentDragOverlappingLayout(CellLayout layout) { 2876 if (mDragOverlappingLayout != null) { 2877 mDragOverlappingLayout.setIsDragOverlapping(false); 2878 } 2879 mDragOverlappingLayout = layout; 2880 if (mDragOverlappingLayout != null) { 2881 mDragOverlappingLayout.setIsDragOverlapping(true); 2882 } 2883 // Invalidating the scrim will also force this CellLayout 2884 // to be invalidated so that it is highlighted if necessary. 2885 mLauncher.getDragLayer().invalidateScrim(); 2886 } 2887 2888 public CellLayout getCurrentDragOverlappingLayout() { 2889 return mDragOverlappingLayout; 2890 } 2891 2892 void setCurrentDropOverCell(int x, int y) { 2893 if (x != mDragOverX || y != mDragOverY) { 2894 mDragOverX = x; 2895 mDragOverY = y; 2896 setDragMode(DRAG_MODE_NONE); 2897 } 2898 } 2899 2900 void setDragMode(int dragMode) { 2901 if (dragMode != mDragMode) { 2902 if (dragMode == DRAG_MODE_NONE) { 2903 cleanupAddToFolder(); 2904 // We don't want to cancel the re-order alarm every time the target cell changes 2905 // as this feels to slow / unresponsive. 2906 cleanupReorder(false); 2907 cleanupFolderCreation(); 2908 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) { 2909 cleanupReorder(true); 2910 cleanupFolderCreation(); 2911 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) { 2912 cleanupAddToFolder(); 2913 cleanupReorder(true); 2914 } else if (dragMode == DRAG_MODE_REORDER) { 2915 cleanupAddToFolder(); 2916 cleanupFolderCreation(); 2917 } 2918 mDragMode = dragMode; 2919 } 2920 } 2921 2922 private void cleanupFolderCreation() { 2923 if (mFolderCreateBg != null) { 2924 mFolderCreateBg.animateToRest(); 2925 } 2926 mFolderCreationAlarm.setOnAlarmListener(null); 2927 mFolderCreationAlarm.cancelAlarm(); 2928 } 2929 2930 private void cleanupAddToFolder() { 2931 if (mDragOverFolderIcon != null) { 2932 mDragOverFolderIcon.onDragExit(); 2933 mDragOverFolderIcon = null; 2934 } 2935 } 2936 2937 private void cleanupReorder(boolean cancelAlarm) { 2938 // Any pending reorders are canceled 2939 if (cancelAlarm) { 2940 mReorderAlarm.cancelAlarm(); 2941 } 2942 mLastReorderX = -1; 2943 mLastReorderY = -1; 2944 } 2945 2946 /* 2947 * 2948 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 2949 * coordinate space. The argument xy is modified with the return result. 2950 */ 2951 void mapPointFromSelfToChild(View v, float[] xy) { 2952 xy[0] = xy[0] - v.getLeft(); 2953 xy[1] = xy[1] - v.getTop(); 2954 } 2955 2956 boolean isPointInSelfOverHotseat(int x, int y) { 2957 mTempXY[0] = x; 2958 mTempXY[1] = y; 2959 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true); 2960 View hotseat = mLauncher.getHotseat(); 2961 return mTempXY[0] >= hotseat.getLeft() && 2962 mTempXY[0] <= hotseat.getRight() && 2963 mTempXY[1] >= hotseat.getTop() && 2964 mTempXY[1] <= hotseat.getBottom(); 2965 } 2966 2967 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { 2968 mTempXY[0] = (int) xy[0]; 2969 mTempXY[1] = (int) xy[1]; 2970 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true); 2971 mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempXY); 2972 2973 xy[0] = mTempXY[0]; 2974 xy[1] = mTempXY[1]; 2975 } 2976 2977 /* 2978 * 2979 * Convert the 2D coordinate xy from this CellLayout's coordinate space to 2980 * the parent View's coordinate space. The argument xy is modified with the return result. 2981 * 2982 */ 2983 void mapPointFromChildToSelf(View v, float[] xy) { 2984 xy[0] += v.getLeft(); 2985 xy[1] += v.getTop(); 2986 } 2987 2988 static private float squaredDistance(float[] point1, float[] point2) { 2989 float distanceX = point1[0] - point2[0]; 2990 float distanceY = point2[1] - point2[1]; 2991 return distanceX * distanceX + distanceY * distanceY; 2992 } 2993 2994 /* 2995 * 2996 * This method returns the CellLayout that is currently being dragged to. In order to drag 2997 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second 2998 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one 2999 * 3000 * Return null if no CellLayout is currently being dragged over 3001 * 3002 */ 3003 private CellLayout findMatchingPageForDragOver( 3004 DragView dragView, float originX, float originY, boolean exact) { 3005 // We loop through all the screens (ie CellLayouts) and see which ones overlap 3006 // with the item being dragged and then choose the one that's closest to the touch point 3007 final int screenCount = getChildCount(); 3008 CellLayout bestMatchingScreen = null; 3009 float smallestDistSoFar = Float.MAX_VALUE; 3010 3011 for (int i = 0; i < screenCount; i++) { 3012 // The custom content screen is not a valid drag over option 3013 if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) { 3014 continue; 3015 } 3016 3017 CellLayout cl = (CellLayout) getChildAt(i); 3018 3019 final float[] touchXy = {originX, originY}; 3020 mapPointFromSelfToChild(cl, touchXy); 3021 3022 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 3023 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 3024 return cl; 3025 } 3026 3027 if (!exact) { 3028 // Get the center of the cell layout in screen coordinates 3029 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; 3030 cellLayoutCenter[0] = cl.getWidth()/2; 3031 cellLayoutCenter[1] = cl.getHeight()/2; 3032 mapPointFromChildToSelf(cl, cellLayoutCenter); 3033 3034 touchXy[0] = originX; 3035 touchXy[1] = originY; 3036 3037 // Calculate the distance between the center of the CellLayout 3038 // and the touch point 3039 float dist = squaredDistance(touchXy, cellLayoutCenter); 3040 3041 if (dist < smallestDistSoFar) { 3042 smallestDistSoFar = dist; 3043 bestMatchingScreen = cl; 3044 } 3045 } 3046 } 3047 return bestMatchingScreen; 3048 } 3049 3050 private boolean isDragWidget(DragObject d) { 3051 return (d.dragInfo instanceof LauncherAppWidgetInfo || 3052 d.dragInfo instanceof PendingAddWidgetInfo); 3053 } 3054 3055 public void onDragOver(DragObject d) { 3056 // Skip drag over events while we are dragging over side pages 3057 if (mInScrollArea || !transitionStateShouldAllowDrop()) return; 3058 3059 ItemInfo item = d.dragInfo; 3060 if (item == null) { 3061 if (ProviderConfig.IS_DOGFOOD_BUILD) { 3062 throw new NullPointerException("DragObject has null info"); 3063 } 3064 return; 3065 } 3066 3067 // Ensure that we have proper spans for the item that we are dropping 3068 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); 3069 mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); 3070 3071 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 3072 if (setDropLayoutForDragObject(d)) { 3073 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED); 3074 if (isInSpringLoadedMode) { 3075 if (mLauncher.isHotseatLayout(mDragTargetLayout)) { 3076 mSpringLoadedDragController.cancel(); 3077 } else { 3078 mSpringLoadedDragController.setAlarm(mDragTargetLayout); 3079 } 3080 } 3081 } 3082 3083 // Handle the drag over 3084 if (mDragTargetLayout != null) { 3085 // We want the point to be mapped to the dragTarget. 3086 if (mLauncher.isHotseatLayout(mDragTargetLayout)) { 3087 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 3088 } else { 3089 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter); 3090 } 3091 3092 int minSpanX = item.spanX; 3093 int minSpanY = item.spanY; 3094 if (item.minSpanX > 0 && item.minSpanY > 0) { 3095 minSpanX = item.minSpanX; 3096 minSpanY = item.minSpanY; 3097 } 3098 3099 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 3100 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, 3101 mDragTargetLayout, mTargetCell); 3102 int reorderX = mTargetCell[0]; 3103 int reorderY = mTargetCell[1]; 3104 3105 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]); 3106 3107 float targetCellDistance = mDragTargetLayout.getDistanceFromCell( 3108 mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); 3109 3110 manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d); 3111 3112 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) 3113 mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, 3114 item.spanY, child, mTargetCell); 3115 3116 if (!nearestDropOccupied) { 3117 mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider, 3118 mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d); 3119 } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) 3120 && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX || 3121 mLastReorderY != reorderY)) { 3122 3123 int[] resultSpan = new int[2]; 3124 mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0], 3125 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY, 3126 child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT); 3127 3128 // Otherwise, if we aren't adding to or creating a folder and there's no pending 3129 // reorder, then we schedule a reorder 3130 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, 3131 minSpanX, minSpanY, item.spanX, item.spanY, d, child); 3132 mReorderAlarm.setOnAlarmListener(listener); 3133 mReorderAlarm.setAlarm(REORDER_TIMEOUT); 3134 } 3135 3136 if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER || 3137 !nearestDropOccupied) { 3138 if (mDragTargetLayout != null) { 3139 mDragTargetLayout.revertTempState(); 3140 } 3141 } 3142 } 3143 } 3144 3145 /** 3146 * Updates {@link #mDragTargetLayout} and {@link #mDragOverlappingLayout} 3147 * based on the DragObject's position. 3148 * 3149 * The layout will be: 3150 * - The Hotseat if the drag object is over it 3151 * - A side page if we are in spring-loaded mode and the drag object is over it 3152 * - The current page otherwise 3153 * 3154 * @return whether the layout is different from the current {@link #mDragTargetLayout}. 3155 */ 3156 private boolean setDropLayoutForDragObject(DragObject d) { 3157 CellLayout layout = null; 3158 // Test to see if we are over the hotseat first 3159 if (mLauncher.getHotseat() != null && !isDragWidget(d)) { 3160 if (isPointInSelfOverHotseat(d.x, d.y)) { 3161 layout = mLauncher.getHotseat().getLayout(); 3162 } 3163 } 3164 if (layout == null) { 3165 // Identify whether we have dragged over a side page, 3166 // otherwise just use the current page 3167 layout = workspaceInModalState() ? 3168 findMatchingPageForDragOver(d.dragView, d.x, d.y, false) 3169 : getCurrentDropLayout(); 3170 } 3171 if (layout != mDragTargetLayout) { 3172 setCurrentDropLayout(layout); 3173 setCurrentDragOverlappingLayout(layout); 3174 return true; 3175 } 3176 return false; 3177 } 3178 3179 private void manageFolderFeedback(CellLayout targetLayout, 3180 int[] targetCell, float distance, DragObject dragObject) { 3181 if (distance > mMaxDistanceForFolderCreation) return; 3182 3183 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]); 3184 ItemInfo info = dragObject.dragInfo; 3185 boolean userFolderPending = willCreateUserFolder(info, dragOverView, false); 3186 if (mDragMode == DRAG_MODE_NONE && userFolderPending && 3187 !mFolderCreationAlarm.alarmPending()) { 3188 3189 FolderCreationAlarmListener listener = new 3190 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]); 3191 3192 if (!dragObject.accessibleDrag) { 3193 mFolderCreationAlarm.setOnAlarmListener(listener); 3194 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); 3195 } else { 3196 listener.onAlarm(mFolderCreationAlarm); 3197 } 3198 3199 if (dragObject.stateAnnouncer != null) { 3200 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper 3201 .getDescriptionForDropOver(dragOverView, getContext())); 3202 } 3203 return; 3204 } 3205 3206 boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView); 3207 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) { 3208 mDragOverFolderIcon = ((FolderIcon) dragOverView); 3209 mDragOverFolderIcon.onDragEnter(info); 3210 if (targetLayout != null) { 3211 targetLayout.clearDragOutlines(); 3212 } 3213 setDragMode(DRAG_MODE_ADD_TO_FOLDER); 3214 3215 if (dragObject.stateAnnouncer != null) { 3216 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper 3217 .getDescriptionForDropOver(dragOverView, getContext())); 3218 } 3219 return; 3220 } 3221 3222 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) { 3223 setDragMode(DRAG_MODE_NONE); 3224 } 3225 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) { 3226 setDragMode(DRAG_MODE_NONE); 3227 } 3228 } 3229 3230 class FolderCreationAlarmListener implements OnAlarmListener { 3231 CellLayout layout; 3232 int cellX; 3233 int cellY; 3234 3235 FolderIcon.PreviewBackground bg = new FolderIcon.PreviewBackground(); 3236 3237 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { 3238 this.layout = layout; 3239 this.cellX = cellX; 3240 this.cellY = cellY; 3241 3242 DeviceProfile grid = mLauncher.getDeviceProfile(); 3243 BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY); 3244 3245 bg.setup(getResources().getDisplayMetrics(), grid, null, 3246 cell.getMeasuredWidth(), cell.getPaddingTop()); 3247 3248 // The full preview background should appear behind the icon 3249 bg.isClipping = false; 3250 } 3251 3252 public void onAlarm(Alarm alarm) { 3253 mFolderCreateBg = bg; 3254 mFolderCreateBg.animateToAccept(layout, cellX, cellY); 3255 layout.clearDragOutlines(); 3256 setDragMode(DRAG_MODE_CREATE_FOLDER); 3257 } 3258 } 3259 3260 class ReorderAlarmListener implements OnAlarmListener { 3261 float[] dragViewCenter; 3262 int minSpanX, minSpanY, spanX, spanY; 3263 DragObject dragObject; 3264 View child; 3265 3266 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, 3267 int spanY, DragObject dragObject, View child) { 3268 this.dragViewCenter = dragViewCenter; 3269 this.minSpanX = minSpanX; 3270 this.minSpanY = minSpanY; 3271 this.spanX = spanX; 3272 this.spanY = spanY; 3273 this.child = child; 3274 this.dragObject = dragObject; 3275 } 3276 3277 public void onAlarm(Alarm alarm) { 3278 int[] resultSpan = new int[2]; 3279 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 3280 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout, 3281 mTargetCell); 3282 mLastReorderX = mTargetCell[0]; 3283 mLastReorderY = mTargetCell[1]; 3284 3285 mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0], 3286 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 3287 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER); 3288 3289 if (mTargetCell[0] < 0 || mTargetCell[1] < 0) { 3290 mDragTargetLayout.revertTempState(); 3291 } else { 3292 setDragMode(DRAG_MODE_REORDER); 3293 } 3294 3295 boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; 3296 mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider, 3297 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject); 3298 } 3299 } 3300 3301 @Override 3302 public void getHitRectRelativeToDragLayer(Rect outRect) { 3303 // We want the workspace to have the whole area of the display (it will find the correct 3304 // cell layout to drop to in the existing drag/drop logic. 3305 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect); 3306 } 3307 3308 /** 3309 * Drop an item that didn't originate on one of the workspace screens. 3310 * It may have come from Launcher (e.g. from all apps or customize), or it may have 3311 * come from another app altogether. 3312 * 3313 * NOTE: This can also be called when we are outside of a drag event, when we want 3314 * to add an item to one of the workspace screens. 3315 */ 3316 private void onDropExternal(final int[] touchXY, final ItemInfo dragInfo, 3317 final CellLayout cellLayout, boolean insertAtFirst, DragObject d) { 3318 final Runnable exitSpringLoadedRunnable = new Runnable() { 3319 @Override 3320 public void run() { 3321 mLauncher.exitSpringLoadedDragModeDelayed(true, 3322 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 3323 } 3324 }; 3325 3326 ItemInfo info = dragInfo; 3327 int spanX = info.spanX; 3328 int spanY = info.spanY; 3329 if (mDragInfo != null) { 3330 spanX = mDragInfo.spanX; 3331 spanY = mDragInfo.spanY; 3332 } 3333 3334 final long container = mLauncher.isHotseatLayout(cellLayout) ? 3335 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 3336 LauncherSettings.Favorites.CONTAINER_DESKTOP; 3337 final long screenId = getIdForScreen(cellLayout); 3338 if (!mLauncher.isHotseatLayout(cellLayout) 3339 && screenId != getScreenIdForPageIndex(mCurrentPage) 3340 && mState != State.SPRING_LOADED) { 3341 snapToScreenId(screenId, null); 3342 } 3343 3344 if (info instanceof PendingAddItemInfo) { 3345 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo; 3346 3347 boolean findNearestVacantCell = true; 3348 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 3349 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, 3350 cellLayout, mTargetCell); 3351 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 3352 mDragViewVisualCenter[1], mTargetCell); 3353 if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true) 3354 || willAddToExistingUserFolder( 3355 d.dragInfo, cellLayout, mTargetCell, distance)) { 3356 findNearestVacantCell = false; 3357 } 3358 } 3359 3360 final ItemInfo item = d.dragInfo; 3361 boolean updateWidgetSize = false; 3362 if (findNearestVacantCell) { 3363 int minSpanX = item.spanX; 3364 int minSpanY = item.spanY; 3365 if (item.minSpanX > 0 && item.minSpanY > 0) { 3366 minSpanX = item.minSpanX; 3367 minSpanY = item.minSpanY; 3368 } 3369 int[] resultSpan = new int[2]; 3370 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0], 3371 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY, 3372 null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL); 3373 3374 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) { 3375 updateWidgetSize = true; 3376 } 3377 item.spanX = resultSpan[0]; 3378 item.spanY = resultSpan[1]; 3379 } 3380 3381 Runnable onAnimationCompleteRunnable = new Runnable() { 3382 @Override 3383 public void run() { 3384 // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when 3385 // adding an item that may not be dropped right away (due to a config activity) 3386 // we defer the removal until the activity returns. 3387 deferRemoveExtraEmptyScreen(); 3388 3389 // When dragging and dropping from customization tray, we deal with creating 3390 // widgets/shortcuts/folders in a slightly different way 3391 mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell, 3392 item.spanX, item.spanY); 3393 } 3394 }; 3395 boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET 3396 || pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 3397 3398 AppWidgetHostView finalView = isWidget ? 3399 ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; 3400 3401 if (finalView != null && updateWidgetSize) { 3402 AppWidgetResizeFrame.updateWidgetSizeRanges(finalView, mLauncher, item.spanX, 3403 item.spanY); 3404 } 3405 3406 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR; 3407 if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null && 3408 ((PendingAddWidgetInfo) pendingInfo).info.configure != null) { 3409 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN; 3410 } 3411 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, 3412 animationStyle, finalView, true); 3413 } else { 3414 // This is for other drag/drop cases, like dragging from All Apps 3415 View view = null; 3416 3417 switch (info.itemType) { 3418 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 3419 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 3420 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 3421 if (info.container == NO_ID && info instanceof AppInfo) { 3422 // Came from all apps -- make a copy 3423 info = ((AppInfo) info).makeShortcut(); 3424 d.dragInfo = info; 3425 } 3426 view = mLauncher.createShortcut(cellLayout, (ShortcutInfo) info); 3427 break; 3428 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 3429 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, 3430 (FolderInfo) info, mIconCache); 3431 break; 3432 default: 3433 throw new IllegalStateException("Unknown item type: " + info.itemType); 3434 } 3435 3436 // First we find the cell nearest to point at which the item is 3437 // dropped, without any consideration to whether there is an item there. 3438 if (touchXY != null) { 3439 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, 3440 cellLayout, mTargetCell); 3441 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 3442 mDragViewVisualCenter[1], mTargetCell); 3443 d.postAnimationRunnable = exitSpringLoadedRunnable; 3444 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, 3445 true, d.dragView, d.postAnimationRunnable)) { 3446 return; 3447 } 3448 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, 3449 true)) { 3450 return; 3451 } 3452 } 3453 3454 if (touchXY != null) { 3455 // when dragging and dropping, just find the closest free spot 3456 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0], 3457 (int) mDragViewVisualCenter[1], 1, 1, 1, 1, 3458 null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL); 3459 } else { 3460 cellLayout.findCellForSpan(mTargetCell, 1, 1); 3461 } 3462 // Add the item to DB before adding to screen ensures that the container and other 3463 // values of the info is properly updated. 3464 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, 3465 mTargetCell[0], mTargetCell[1]); 3466 3467 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX, 3468 info.spanY, insertAtFirst); 3469 cellLayout.onDropChild(view); 3470 cellLayout.getShortcutsAndWidgets().measureChild(view); 3471 3472 if (d.dragView != null) { 3473 // We wrap the animation call in the temporary set and reset of the current 3474 // cellLayout to its final transform -- this means we animate the drag view to 3475 // the correct final location. 3476 setFinalTransitionTransform(cellLayout); 3477 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, 3478 exitSpringLoadedRunnable, this); 3479 resetTransitionTransform(cellLayout); 3480 } 3481 } 3482 } 3483 3484 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { 3485 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false); 3486 int visibility = layout.getVisibility(); 3487 layout.setVisibility(VISIBLE); 3488 3489 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY); 3490 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY); 3491 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], 3492 Bitmap.Config.ARGB_8888); 3493 mCanvas.setBitmap(b); 3494 3495 layout.measure(width, height); 3496 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]); 3497 layout.draw(mCanvas); 3498 mCanvas.setBitmap(null); 3499 layout.setVisibility(visibility); 3500 return b; 3501 } 3502 3503 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY, 3504 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale) { 3505 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final 3506 // location and size on the home screen. 3507 int spanX = info.spanX; 3508 int spanY = info.spanY; 3509 3510 Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY); 3511 loc[0] = r.left; 3512 loc[1] = r.top; 3513 3514 setFinalTransitionTransform(layout); 3515 float cellLayoutScale = 3516 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true); 3517 resetTransitionTransform(layout); 3518 3519 float dragViewScaleX; 3520 float dragViewScaleY; 3521 if (scale) { 3522 dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); 3523 dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight(); 3524 } else { 3525 dragViewScaleX = 1f; 3526 dragViewScaleY = 1f; 3527 } 3528 3529 // The animation will scale the dragView about its center, so we need to center about 3530 // the final location. 3531 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2 3532 - Math.ceil(layout.getUnusedHorizontalSpace() / 2f); 3533 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2; 3534 3535 scaleXY[0] = dragViewScaleX * cellLayoutScale; 3536 scaleXY[1] = dragViewScaleY * cellLayoutScale; 3537 } 3538 3539 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView, 3540 final Runnable onCompleteRunnable, int animationType, final View finalView, 3541 boolean external) { 3542 Rect from = new Rect(); 3543 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from); 3544 3545 int[] finalPos = new int[2]; 3546 float scaleXY[] = new float[2]; 3547 boolean scalePreview = !(info instanceof PendingAddShortcutInfo); 3548 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell, 3549 scalePreview); 3550 3551 Resources res = mLauncher.getResources(); 3552 final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200; 3553 3554 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET || 3555 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; 3556 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) { 3557 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView); 3558 dragView.setCrossFadeBitmap(crossFadeBitmap); 3559 dragView.crossFade((int) (duration * 0.8f)); 3560 } else if (isWidget && external) { 3561 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]); 3562 } 3563 3564 DragLayer dragLayer = mLauncher.getDragLayer(); 3565 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) { 3566 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f, 3567 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration); 3568 } else { 3569 int endStyle; 3570 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) { 3571 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE; 3572 } else { 3573 endStyle = DragLayer.ANIMATION_END_DISAPPEAR; 3574 } 3575 3576 Runnable onComplete = new Runnable() { 3577 @Override 3578 public void run() { 3579 if (finalView != null) { 3580 finalView.setVisibility(VISIBLE); 3581 } 3582 if (onCompleteRunnable != null) { 3583 onCompleteRunnable.run(); 3584 } 3585 } 3586 }; 3587 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0], 3588 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle, 3589 duration, this); 3590 } 3591 } 3592 3593 public void setFinalTransitionTransform(CellLayout layout) { 3594 if (isSwitchingState()) { 3595 mCurrentScale = getScaleX(); 3596 setScaleX(mStateTransitionAnimation.getFinalScale()); 3597 setScaleY(mStateTransitionAnimation.getFinalScale()); 3598 } 3599 } 3600 public void resetTransitionTransform(CellLayout layout) { 3601 if (isSwitchingState()) { 3602 setScaleX(mCurrentScale); 3603 setScaleY(mCurrentScale); 3604 } 3605 } 3606 3607 public WorkspaceStateTransitionAnimation getStateTransitionAnimation() { 3608 return mStateTransitionAnimation; 3609 } 3610 3611 /** 3612 * Return the current {@link CellLayout}, correctly picking the destination 3613 * screen while a scroll is in progress. 3614 */ 3615 public CellLayout getCurrentDropLayout() { 3616 return (CellLayout) getChildAt(getNextPage()); 3617 } 3618 3619 /** 3620 * Return the current CellInfo describing our current drag; this method exists 3621 * so that Launcher can sync this object with the correct info when the activity is created/ 3622 * destroyed 3623 * 3624 */ 3625 public CellLayout.CellInfo getDragInfo() { 3626 return mDragInfo; 3627 } 3628 3629 public int getCurrentPageOffsetFromCustomContent() { 3630 return getNextPage() - numCustomPages(); 3631 } 3632 3633 /** 3634 * Calculate the nearest cell where the given object would be dropped. 3635 * 3636 * pixelX and pixelY should be in the coordinate system of layout 3637 */ 3638 @Thunk int[] findNearestArea(int pixelX, int pixelY, 3639 int spanX, int spanY, CellLayout layout, int[] recycle) { 3640 return layout.findNearestArea( 3641 pixelX, pixelY, spanX, spanY, recycle); 3642 } 3643 3644 void setup(DragController dragController) { 3645 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); 3646 mDragController = dragController; 3647 3648 // hardware layers on children are enabled on startup, but should be disabled until 3649 // needed 3650 updateChildrenLayersEnabled(false); 3651 } 3652 3653 /** 3654 * Called at the end of a drag which originated on the workspace. 3655 */ 3656 public void onDropCompleted(final View target, final DragObject d, 3657 final boolean isFlingToDelete, final boolean success) { 3658 if (mDeferDropAfterUninstall) { 3659 mDeferredAction = new Runnable() { 3660 public void run() { 3661 onDropCompleted(target, d, isFlingToDelete, success); 3662 mDeferredAction = null; 3663 } 3664 }; 3665 return; 3666 } 3667 3668 boolean beingCalledAfterUninstall = mDeferredAction != null; 3669 3670 if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) { 3671 if (target != this && mDragInfo != null) { 3672 removeWorkspaceItem(mDragInfo.cell); 3673 } 3674 } else if (mDragInfo != null) { 3675 final CellLayout cellLayout = mLauncher.getCellLayout( 3676 mDragInfo.container, mDragInfo.screenId); 3677 if (cellLayout != null) { 3678 cellLayout.onDropChild(mDragInfo.cell); 3679 } else if (ProviderConfig.IS_DOGFOOD_BUILD) { 3680 throw new RuntimeException("Invalid state: cellLayout == null in " 3681 + "Workspace#onDropCompleted. Please file a bug. "); 3682 }; 3683 } 3684 if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful)) 3685 && mDragInfo.cell != null) { 3686 mDragInfo.cell.setVisibility(VISIBLE); 3687 } 3688 mOutlineProvider = null; 3689 mDragInfo = null; 3690 3691 if (!isFlingToDelete) { 3692 // Fling to delete already exits spring loaded mode after the animation finishes. 3693 mLauncher.exitSpringLoadedDragModeDelayed(success, 3694 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, mDelayedResizeRunnable); 3695 mDelayedResizeRunnable = null; 3696 } 3697 } 3698 3699 /** 3700 * For opposite operation. See {@link #addInScreen}. 3701 */ 3702 public void removeWorkspaceItem(View v) { 3703 CellLayout parentCell = getParentCellLayoutForView(v); 3704 if (parentCell != null) { 3705 parentCell.removeView(v); 3706 } else if (ProviderConfig.IS_DOGFOOD_BUILD) { 3707 // When an app is uninstalled using the drop target, we wait until resume to remove 3708 // the icon. We also remove all the corresponding items from the workspace at 3709 // {@link Launcher#bindComponentsRemoved}. That call can come before or after 3710 // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is. 3711 Log.e(TAG, "mDragInfo.cell has null parent"); 3712 } 3713 if (v instanceof DropTarget) { 3714 mDragController.removeDropTarget((DropTarget) v); 3715 } 3716 } 3717 3718 /** 3719 * Removes all folder listeners 3720 */ 3721 public void removeFolderListeners() { 3722 mapOverItems(false, new ItemOperator() { 3723 @Override 3724 public boolean evaluate(ItemInfo info, View view) { 3725 if (view instanceof FolderIcon) { 3726 ((FolderIcon) view).removeListeners(); 3727 } 3728 return false; 3729 } 3730 }); 3731 } 3732 3733 @Override 3734 public void deferCompleteDropAfterUninstallActivity() { 3735 mDeferDropAfterUninstall = true; 3736 } 3737 3738 /// maybe move this into a smaller part 3739 @Override 3740 public void onDragObjectRemoved(boolean success) { 3741 mDeferDropAfterUninstall = false; 3742 mUninstallSuccessful = success; 3743 if (mDeferredAction != null) { 3744 mDeferredAction.run(); 3745 } 3746 } 3747 3748 @Override 3749 public float getIntrinsicIconScaleFactor() { 3750 return 1f; 3751 } 3752 3753 @Override 3754 public boolean supportsFlingToDelete() { 3755 return true; 3756 } 3757 3758 @Override 3759 public boolean supportsAppInfoDropTarget() { 3760 return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND; 3761 } 3762 3763 @Override 3764 public boolean supportsDeleteDropTarget() { 3765 return true; 3766 } 3767 3768 @Override 3769 public void onFlingToDelete(DragObject d, PointF vec) { 3770 // Do nothing 3771 } 3772 3773 @Override 3774 public void onFlingToDeleteCompleted() { 3775 // Do nothing 3776 } 3777 3778 public boolean isDropEnabled() { 3779 return true; 3780 } 3781 3782 @Override 3783 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 3784 // We don't dispatch restoreInstanceState to our children using this code path. 3785 // Some pages will be restored immediately as their items are bound immediately, and 3786 // others we will need to wait until after their items are bound. 3787 mSavedStates = container; 3788 } 3789 3790 public void restoreInstanceStateForChild(int child) { 3791 if (mSavedStates != null) { 3792 mRestoredPages.add(child); 3793 CellLayout cl = (CellLayout) getChildAt(child); 3794 if (cl != null) { 3795 cl.restoreInstanceState(mSavedStates); 3796 } 3797 } 3798 } 3799 3800 public void restoreInstanceStateForRemainingPages() { 3801 int count = getChildCount(); 3802 for (int i = 0; i < count; i++) { 3803 if (!mRestoredPages.contains(i)) { 3804 restoreInstanceStateForChild(i); 3805 } 3806 } 3807 mRestoredPages.clear(); 3808 mSavedStates = null; 3809 } 3810 3811 @Override 3812 public void scrollLeft() { 3813 if (!workspaceInModalState() && !mIsSwitchingState) { 3814 super.scrollLeft(); 3815 } 3816 Folder openFolder = getOpenFolder(); 3817 if (openFolder != null) { 3818 openFolder.completeDragExit(); 3819 } 3820 } 3821 3822 @Override 3823 public void scrollRight() { 3824 if (!workspaceInModalState() && !mIsSwitchingState) { 3825 super.scrollRight(); 3826 } 3827 Folder openFolder = getOpenFolder(); 3828 if (openFolder != null) { 3829 openFolder.completeDragExit(); 3830 } 3831 } 3832 3833 @Override 3834 public boolean onEnterScrollArea(int x, int y, int direction) { 3835 // Ignore the scroll area if we are dragging over the hot seat 3836 boolean isPortrait = !mLauncher.getDeviceProfile().isLandscape; 3837 if (mLauncher.getHotseat() != null && isPortrait) { 3838 Rect r = new Rect(); 3839 mLauncher.getHotseat().getHitRect(r); 3840 if (r.contains(x, y)) { 3841 return false; 3842 } 3843 } 3844 3845 boolean result = false; 3846 if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) { 3847 mInScrollArea = true; 3848 3849 final int page = getNextPage() + 3850 (direction == DragController.SCROLL_LEFT ? -1 : 1); 3851 // We always want to exit the current layout to ensure parity of enter / exit 3852 setCurrentDropLayout(null); 3853 3854 if (0 <= page && page < getChildCount()) { 3855 // Ensure that we are not dragging over to the custom content screen 3856 if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) { 3857 return false; 3858 } 3859 3860 CellLayout layout = (CellLayout) getChildAt(page); 3861 setCurrentDragOverlappingLayout(layout); 3862 3863 // Workspace is responsible for drawing the edge glow on adjacent pages, 3864 // so we need to redraw the workspace when this may have changed. 3865 invalidate(); 3866 result = true; 3867 } 3868 } 3869 return result; 3870 } 3871 3872 @Override 3873 public boolean onExitScrollArea() { 3874 boolean result = false; 3875 if (mInScrollArea) { 3876 invalidate(); 3877 CellLayout layout = getCurrentDropLayout(); 3878 setCurrentDropLayout(layout); 3879 setCurrentDragOverlappingLayout(layout); 3880 3881 result = true; 3882 mInScrollArea = false; 3883 } 3884 return result; 3885 } 3886 3887 private void onResetScrollArea() { 3888 setCurrentDragOverlappingLayout(null); 3889 mInScrollArea = false; 3890 } 3891 3892 /** 3893 * Returns a specific CellLayout 3894 */ 3895 CellLayout getParentCellLayoutForView(View v) { 3896 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts(); 3897 for (CellLayout layout : layouts) { 3898 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) { 3899 return layout; 3900 } 3901 } 3902 return null; 3903 } 3904 3905 /** 3906 * Returns a list of all the CellLayouts in the workspace. 3907 */ 3908 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() { 3909 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>(); 3910 int screenCount = getChildCount(); 3911 for (int screen = 0; screen < screenCount; screen++) { 3912 layouts.add(((CellLayout) getChildAt(screen))); 3913 } 3914 if (mLauncher.getHotseat() != null) { 3915 layouts.add(mLauncher.getHotseat().getLayout()); 3916 } 3917 return layouts; 3918 } 3919 3920 /** 3921 * We should only use this to search for specific children. Do not use this method to modify 3922 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from 3923 * the hotseat and workspace pages 3924 */ 3925 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() { 3926 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = new ArrayList<>(); 3927 int screenCount = getChildCount(); 3928 for (int screen = 0; screen < screenCount; screen++) { 3929 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets()); 3930 } 3931 if (mLauncher.getHotseat() != null) { 3932 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets()); 3933 } 3934 return childrenLayouts; 3935 } 3936 3937 public View getHomescreenIconByItemId(final long id) { 3938 return getFirstMatch(new ItemOperator() { 3939 3940 @Override 3941 public boolean evaluate(ItemInfo info, View v) { 3942 return info != null && info.id == id; 3943 } 3944 }); 3945 } 3946 3947 public View getViewForTag(final Object tag) { 3948 return getFirstMatch(new ItemOperator() { 3949 3950 @Override 3951 public boolean evaluate(ItemInfo info, View v) { 3952 return info == tag; 3953 } 3954 }); 3955 } 3956 3957 public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) { 3958 return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() { 3959 3960 @Override 3961 public boolean evaluate(ItemInfo info, View v) { 3962 return (info instanceof LauncherAppWidgetInfo) && 3963 ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId; 3964 } 3965 }); 3966 } 3967 3968 public View getFirstMatch(final ItemOperator operator) { 3969 final View[] value = new View[1]; 3970 mapOverItems(MAP_NO_RECURSE, new ItemOperator() { 3971 @Override 3972 public boolean evaluate(ItemInfo info, View v) { 3973 if (operator.evaluate(info, v)) { 3974 value[0] = v; 3975 return true; 3976 } 3977 return false; 3978 } 3979 }); 3980 return value[0]; 3981 } 3982 3983 void clearDropTargets() { 3984 mapOverItems(MAP_NO_RECURSE, new ItemOperator() { 3985 @Override 3986 public boolean evaluate(ItemInfo info, View v) { 3987 if (v instanceof DropTarget) { 3988 mDragController.removeDropTarget((DropTarget) v); 3989 } 3990 // not done, process all the shortcuts 3991 return false; 3992 } 3993 }); 3994 } 3995 3996 /** 3997 * Removes items that match the {@param matcher}. When applications are removed 3998 * as a part of an update, this is called to ensure that other widgets and application 3999 * shortcuts are not removed. 4000 */ 4001 public void removeItemsByMatcher(final ItemInfoMatcher matcher) { 4002 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts(); 4003 for (final CellLayout layoutParent: cellLayouts) { 4004 final ViewGroup layout = layoutParent.getShortcutsAndWidgets(); 4005 4006 final HashMap<ItemInfo, View> children = new HashMap<>(); 4007 for (int j = 0; j < layout.getChildCount(); j++) { 4008 final View view = layout.getChildAt(j); 4009 children.put((ItemInfo) view.getTag(), view); 4010 } 4011 4012 final ArrayList<View> childrenToRemove = new ArrayList<>(); 4013 final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<>(); 4014 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() { 4015 @Override 4016 public boolean filterItem(ItemInfo parent, ItemInfo info, 4017 ComponentName cn) { 4018 if (parent instanceof FolderInfo) { 4019 if (matcher.matches(info, cn)) { 4020 FolderInfo folder = (FolderInfo) parent; 4021 ArrayList<ShortcutInfo> appsToRemove; 4022 if (folderAppsToRemove.containsKey(folder)) { 4023 appsToRemove = folderAppsToRemove.get(folder); 4024 } else { 4025 appsToRemove = new ArrayList<ShortcutInfo>(); 4026 folderAppsToRemove.put(folder, appsToRemove); 4027 } 4028 appsToRemove.add((ShortcutInfo) info); 4029 return true; 4030 } 4031 } else { 4032 if (matcher.matches(info, cn)) { 4033 childrenToRemove.add(children.get(info)); 4034 return true; 4035 } 4036 } 4037 return false; 4038 } 4039 }; 4040 LauncherModel.filterItemInfos(children.keySet(), filter); 4041 4042 // Remove all the apps from their folders 4043 for (FolderInfo folder : folderAppsToRemove.keySet()) { 4044 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder); 4045 for (ShortcutInfo info : appsToRemove) { 4046 folder.remove(info, false); 4047 } 4048 } 4049 4050 // Remove all the other children 4051 for (View child : childrenToRemove) { 4052 // Note: We can not remove the view directly from CellLayoutChildren as this 4053 // does not re-mark the spaces as unoccupied. 4054 layoutParent.removeViewInLayout(child); 4055 if (child instanceof DropTarget) { 4056 mDragController.removeDropTarget((DropTarget) child); 4057 } 4058 } 4059 4060 if (childrenToRemove.size() > 0) { 4061 layout.requestLayout(); 4062 layout.invalidate(); 4063 } 4064 } 4065 4066 // Strip all the empty screens 4067 stripEmptyScreens(); 4068 } 4069 4070 public interface ItemOperator { 4071 /** 4072 * Process the next itemInfo, possibly with side-effect on the next item. 4073 * 4074 * @param info info for the shortcut 4075 * @param view view for the shortcut 4076 * @return true if done, false to continue the map 4077 */ 4078 public boolean evaluate(ItemInfo info, View view); 4079 } 4080 4081 /** 4082 * Map the operator over the shortcuts and widgets, return the first-non-null value. 4083 * 4084 * @param recurse true: iterate over folder children. false: op get the folders themselves. 4085 * @param op the operator to map over the shortcuts 4086 */ 4087 void mapOverItems(boolean recurse, ItemOperator op) { 4088 ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers(); 4089 final int containerCount = containers.size(); 4090 for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) { 4091 ShortcutAndWidgetContainer container = containers.get(containerIdx); 4092 // map over all the shortcuts on the workspace 4093 final int itemCount = container.getChildCount(); 4094 for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { 4095 View item = container.getChildAt(itemIdx); 4096 ItemInfo info = (ItemInfo) item.getTag(); 4097 if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) { 4098 FolderIcon folder = (FolderIcon) item; 4099 ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder(); 4100 // map over all the children in the folder 4101 final int childCount = folderChildren.size(); 4102 for (int childIdx = 0; childIdx < childCount; childIdx++) { 4103 View child = folderChildren.get(childIdx); 4104 info = (ItemInfo) child.getTag(); 4105 if (op.evaluate(info, child)) { 4106 return; 4107 } 4108 } 4109 } else { 4110 if (op.evaluate(info, item)) { 4111 return; 4112 } 4113 } 4114 } 4115 } 4116 } 4117 4118 void updateShortcuts(ArrayList<ShortcutInfo> shortcuts) { 4119 int total = shortcuts.size(); 4120 final HashSet<ShortcutInfo> updates = new HashSet<ShortcutInfo>(total); 4121 final HashSet<Long> folderIds = new HashSet<>(); 4122 4123 for (int i = 0; i < total; i++) { 4124 ShortcutInfo s = shortcuts.get(i); 4125 updates.add(s); 4126 folderIds.add(s.container); 4127 } 4128 4129 mapOverItems(MAP_RECURSE, new ItemOperator() { 4130 @Override 4131 public boolean evaluate(ItemInfo info, View v) { 4132 if (info instanceof ShortcutInfo && v instanceof BubbleTextView && 4133 updates.contains(info)) { 4134 ShortcutInfo si = (ShortcutInfo) info; 4135 BubbleTextView shortcut = (BubbleTextView) v; 4136 Drawable oldIcon = getTextViewIcon(shortcut); 4137 boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) 4138 && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); 4139 shortcut.applyFromShortcutInfo(si, mIconCache, 4140 si.isPromise() != oldPromiseState); 4141 } 4142 // process all the shortcuts 4143 return false; 4144 } 4145 }); 4146 4147 // Update folder icons 4148 mapOverItems(MAP_NO_RECURSE, new ItemOperator() { 4149 @Override 4150 public boolean evaluate(ItemInfo info, View v) { 4151 if (info instanceof FolderInfo && folderIds.contains(info.id)) { 4152 ((FolderInfo) info).itemsChanged(false); 4153 } 4154 // process all the shortcuts 4155 return false; 4156 } 4157 }); 4158 } 4159 4160 public void removeAbandonedPromise(String packageName, UserHandleCompat user) { 4161 HashSet<String> packages = new HashSet<>(1); 4162 packages.add(packageName); 4163 LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user); 4164 removeItemsByMatcher(ItemInfoMatcher.ofPackages(packages, user)); 4165 } 4166 4167 public void updateRestoreItems(final HashSet<ItemInfo> updates) { 4168 mapOverItems(MAP_RECURSE, new ItemOperator() { 4169 @Override 4170 public boolean evaluate(ItemInfo info, View v) { 4171 if (info instanceof ShortcutInfo && v instanceof BubbleTextView 4172 && updates.contains(info)) { 4173 ((BubbleTextView) v).applyState(false); 4174 } else if (v instanceof PendingAppWidgetHostView 4175 && info instanceof LauncherAppWidgetInfo 4176 && updates.contains(info)) { 4177 ((PendingAppWidgetHostView) v).applyState(); 4178 } 4179 // process all the shortcuts 4180 return false; 4181 } 4182 }); 4183 } 4184 4185 public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) { 4186 if (!changedInfo.isEmpty()) { 4187 DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo, 4188 mLauncher.getAppWidgetHost()); 4189 4190 LauncherAppWidgetInfo item = changedInfo.get(0); 4191 final AppWidgetProviderInfo widgetInfo; 4192 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { 4193 widgetInfo = AppWidgetManagerCompat 4194 .getInstance(mLauncher).findProvider(item.providerName, item.user); 4195 } else { 4196 widgetInfo = AppWidgetManagerCompat.getInstance(mLauncher) 4197 .getAppWidgetInfo(item.appWidgetId); 4198 } 4199 4200 if (widgetInfo != null) { 4201 // Re-inflate the widgets which have changed status 4202 widgetRefresh.run(); 4203 } else { 4204 // widgetRefresh will automatically run when the packages are updated. 4205 // For now just update the progress bars 4206 mapOverItems(MAP_NO_RECURSE, new ItemOperator() { 4207 @Override 4208 public boolean evaluate(ItemInfo info, View view) { 4209 if (view instanceof PendingAppWidgetHostView 4210 && changedInfo.contains(info)) { 4211 ((LauncherAppWidgetInfo) info).installProgress = 100; 4212 ((PendingAppWidgetHostView) view).applyState(); 4213 } 4214 // process all the shortcuts 4215 return false; 4216 } 4217 }); 4218 } 4219 } 4220 } 4221 4222 private void moveToScreen(int page, boolean animate) { 4223 if (!workspaceInModalState()) { 4224 if (animate) { 4225 snapToPage(page); 4226 } else { 4227 setCurrentPage(page); 4228 } 4229 } 4230 View child = getChildAt(page); 4231 if (child != null) { 4232 child.requestFocus(); 4233 } 4234 } 4235 4236 void moveToDefaultScreen(boolean animate) { 4237 moveToScreen(getDefaultPage(), animate); 4238 } 4239 4240 void moveToCustomContentScreen(boolean animate) { 4241 if (hasCustomContent()) { 4242 int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID); 4243 if (animate) { 4244 snapToPage(ccIndex); 4245 } else { 4246 setCurrentPage(ccIndex); 4247 } 4248 View child = getChildAt(ccIndex); 4249 if (child != null) { 4250 child.requestFocus(); 4251 } 4252 } 4253 exitWidgetResizeMode(); 4254 } 4255 4256 @Override 4257 protected String getPageIndicatorDescription() { 4258 return getResources().getString(R.string.all_apps_button_label); 4259 } 4260 4261 @Override 4262 protected String getCurrentPageDescription() { 4263 if (hasCustomContent() && getNextPage() == 0) { 4264 return mCustomContentDescription; 4265 } 4266 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 4267 return getPageDescription(page); 4268 } 4269 4270 private String getPageDescription(int page) { 4271 int delta = numCustomPages(); 4272 int nScreens = getChildCount() - delta; 4273 int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); 4274 if (extraScreenId >= 0 && nScreens > 1) { 4275 if (page == extraScreenId) { 4276 return getContext().getString(R.string.workspace_new_page); 4277 } 4278 nScreens--; 4279 } 4280 if (nScreens == 0) { 4281 // When the workspace is not loaded, we do not know how many screen will be bound. 4282 return getContext().getString(R.string.all_apps_home_button_label); 4283 } 4284 return getContext().getString(R.string.workspace_scroll_format, 4285 page + 1 - delta, nScreens); 4286 } 4287 4288 @Override 4289 public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) { 4290 target.gridX = info.cellX; 4291 target.gridY = info.cellY; 4292 target.pageIndex = getCurrentPage(); 4293 targetParent.containerType = LauncherLogProto.WORKSPACE; 4294 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 4295 target.rank = info.rank; 4296 targetParent.containerType = LauncherLogProto.HOTSEAT; 4297 } else if (info.container >= 0) { 4298 targetParent.containerType = LauncherLogProto.FOLDER; 4299 } 4300 } 4301 4302 /** 4303 * Used as a workaround to ensure that the AppWidgetService receives the 4304 * PACKAGE_ADDED broadcast before updating widgets. 4305 */ 4306 private class DeferredWidgetRefresh implements Runnable { 4307 private final ArrayList<LauncherAppWidgetInfo> mInfos; 4308 private final LauncherAppWidgetHost mHost; 4309 private final Handler mHandler; 4310 4311 private boolean mRefreshPending; 4312 4313 public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos, 4314 LauncherAppWidgetHost host) { 4315 mInfos = infos; 4316 mHost = host; 4317 mHandler = new Handler(); 4318 mRefreshPending = true; 4319 4320 mHost.addProviderChangeListener(this); 4321 // Force refresh after 10 seconds, if we don't get the provider changed event. 4322 // This could happen when the provider is no longer available in the app. 4323 mHandler.postDelayed(this, 10000); 4324 } 4325 4326 @Override 4327 public void run() { 4328 mHost.removeProviderChangeListener(this); 4329 mHandler.removeCallbacks(this); 4330 4331 if (!mRefreshPending) { 4332 return; 4333 } 4334 4335 mRefreshPending = false; 4336 4337 mapOverItems(MAP_NO_RECURSE, new ItemOperator() { 4338 @Override 4339 public boolean evaluate(ItemInfo info, View view) { 4340 if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) { 4341 PendingAppWidgetHostView hostView = (PendingAppWidgetHostView) view; 4342 mLauncher.removeItem(view, info, false /* deleteFromDb */); 4343 mLauncher.bindAppWidget((LauncherAppWidgetInfo) info); 4344 } 4345 // process all the shortcuts 4346 return false; 4347 } 4348 }); 4349 } 4350 } 4351 4352 public interface OnStateChangeListener { 4353 4354 /** 4355 * Called when the workspace state is changing. 4356 * @param toState final state 4357 * @param targetAnim animation which will be played during the transition or null. 4358 */ 4359 void prepareStateChange(State toState, AnimatorSet targetAnim); 4360 } 4361 4362 public static final boolean isQsbContainerPage(int pageNo) { 4363 return pageNo == 0; 4364 } 4365 } 4366