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