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