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