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