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