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