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