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