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