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