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