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