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