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