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