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