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