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