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