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