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