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