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