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