Workspace.java revision 360e63fd3e77247002b86da2a77bd8dfe8c8a807
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.launcher2; 18 19import android.animation.Animator; 20import android.animation.AnimatorSet; 21import android.animation.ObjectAnimator; 22import android.animation.TimeInterpolator; 23import android.animation.ValueAnimator; 24import android.animation.ValueAnimator.AnimatorUpdateListener; 25import android.app.WallpaperManager; 26import android.appwidget.AppWidgetHostView; 27import android.appwidget.AppWidgetManager; 28import android.appwidget.AppWidgetProviderInfo; 29import android.content.ComponentName; 30import android.content.Context; 31import android.content.Intent; 32import android.content.res.Resources; 33import android.content.res.TypedArray; 34import android.graphics.Bitmap; 35import android.graphics.Camera; 36import android.graphics.Canvas; 37import android.graphics.Matrix; 38import android.graphics.Paint; 39import android.graphics.Point; 40import android.graphics.PointF; 41import android.graphics.PorterDuff; 42import android.graphics.PorterDuffColorFilter; 43import android.graphics.Rect; 44import android.graphics.Region.Op; 45import android.graphics.drawable.Drawable; 46import android.os.IBinder; 47import android.os.Parcelable; 48import android.util.AttributeSet; 49import android.util.DisplayMetrics; 50import android.util.Log; 51import android.view.Display; 52import android.view.MotionEvent; 53import android.view.View; 54import android.view.ViewGroup; 55import android.view.animation.DecelerateInterpolator; 56import android.widget.ImageView; 57import android.widget.TextView; 58 59import com.android.launcher.R; 60import com.android.launcher2.FolderIcon.FolderRingAnimator; 61import com.android.launcher2.LauncherSettings.Favorites; 62 63import java.util.ArrayList; 64import java.util.HashSet; 65 66/** 67 * The workspace is a wide area with a wallpaper and a finite number of pages. 68 * Each page contains a number of icons, folders or widgets the user can 69 * interact with. A workspace is meant to be used with a fixed width only. 70 */ 71public class Workspace extends SmoothPagedView 72 implements DropTarget, DragSource, DragScroller, View.OnTouchListener, 73 DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener { 74 private static final String TAG = "Launcher.Workspace"; 75 76 // Y rotation to apply to the workspace screens 77 private static final float WORKSPACE_ROTATION = 12.5f; 78 private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f; 79 private static float CAMERA_DISTANCE = 6500; 80 81 private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0; 82 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375; 83 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100; 84 85 private static final int BACKGROUND_FADE_OUT_DURATION = 350; 86 private static final int ADJACENT_SCREEN_DROP_DURATION = 300; 87 private static final int FLING_THRESHOLD_VELOCITY = 500; 88 89 private float mMaxDistanceForFolderCreation; 90 91 // These animators are used to fade the children's outlines 92 private ObjectAnimator mChildrenOutlineFadeInAnimation; 93 private ObjectAnimator mChildrenOutlineFadeOutAnimation; 94 private float mChildrenOutlineAlpha = 0; 95 96 // These properties refer to the background protection gradient used for AllApps and Customize 97 private ValueAnimator mBackgroundFadeInAnimation; 98 private ValueAnimator mBackgroundFadeOutAnimation; 99 private Drawable mBackground; 100 boolean mDrawBackground = true; 101 private float mBackgroundAlpha = 0; 102 private float mOverScrollMaxBackgroundAlpha = 0.0f; 103 private int mOverScrollPageIndex = -1; 104 105 private float mWallpaperScrollRatio = 1.0f; 106 107 private final WallpaperManager mWallpaperManager; 108 private IBinder mWindowToken; 109 private static final float WALLPAPER_SCREENS_SPAN = 2f; 110 111 private int mDefaultPage; 112 113 /** 114 * CellInfo for the cell that is currently being dragged 115 */ 116 private CellLayout.CellInfo mDragInfo; 117 118 /** 119 * Target drop area calculated during last acceptDrop call. 120 */ 121 private int[] mTargetCell = new int[2]; 122 private int mDragOverX = -1; 123 private int mDragOverY = -1; 124 125 /** 126 * The CellLayout that is currently being dragged over 127 */ 128 private CellLayout mDragTargetLayout = null; 129 /** 130 * The CellLayout that we will show as glowing 131 */ 132 private CellLayout mDragOverlappingLayout = null; 133 134 /** 135 * The CellLayout which will be dropped to 136 */ 137 private CellLayout mDropToLayout = null; 138 139 private Launcher mLauncher; 140 private IconCache mIconCache; 141 private DragController mDragController; 142 143 // These are temporary variables to prevent having to allocate a new object just to 144 // return an (x, y) value from helper functions. Do NOT use them to maintain other state. 145 private int[] mTempCell = new int[2]; 146 private int[] mTempEstimate = new int[2]; 147 private float[] mDragViewVisualCenter = new float[2]; 148 private float[] mTempDragCoordinates = new float[2]; 149 private float[] mTempCellLayoutCenterCoordinates = new float[2]; 150 private float[] mTempDragBottomRightCoordinates = new float[2]; 151 private Matrix mTempInverseMatrix = new Matrix(); 152 153 private SpringLoadedDragController mSpringLoadedDragController; 154 private float mSpringLoadedShrinkFactor; 155 156 private static final int DEFAULT_CELL_COUNT_X = 4; 157 private static final int DEFAULT_CELL_COUNT_Y = 4; 158 159 // State variable that indicates whether the pages are small (ie when you're 160 // in all apps or customize mode) 161 162 enum State { NORMAL, SPRING_LOADED, SMALL }; 163 private State mState = State.NORMAL; 164 private boolean mIsSwitchingState = false; 165 166 boolean mAnimatingViewIntoPlace = false; 167 boolean mIsDragOccuring = false; 168 boolean mChildrenLayersEnabled = true; 169 170 /** Is the user is dragging an item near the edge of a page? */ 171 private boolean mInScrollArea = false; 172 173 private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper(); 174 private Bitmap mDragOutline = null; 175 private final Rect mTempRect = new Rect(); 176 private final int[] mTempXY = new int[2]; 177 private float mOverscrollFade = 0; 178 private boolean mOverscrollTransformsSet; 179 public static final int DRAG_BITMAP_PADDING = 2; 180 181 // Camera and Matrix used to determine the final position of a neighboring CellLayout 182 private final Matrix mMatrix = new Matrix(); 183 private final Camera mCamera = new Camera(); 184 private final float mTempFloat2[] = new float[2]; 185 186 enum WallpaperVerticalOffset { TOP, MIDDLE, BOTTOM }; 187 int mWallpaperWidth; 188 int mWallpaperHeight; 189 WallpaperOffsetInterpolator mWallpaperOffset; 190 boolean mUpdateWallpaperOffsetImmediately = false; 191 private Runnable mDelayedResizeRunnable; 192 private Runnable mDelayedSnapToPageRunnable; 193 private Point mDisplaySize = new Point(); 194 private boolean mIsStaticWallpaper; 195 private int mWallpaperTravelWidth; 196 197 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts 198 private static final int FOLDER_CREATION_TIMEOUT = 0; 199 private static final int REORDER_TIMEOUT = 250; 200 private final Alarm mFolderCreationAlarm = new Alarm(); 201 private final Alarm mReorderAlarm = new Alarm(); 202 private FolderRingAnimator mDragFolderRingAnimator = null; 203 private FolderIcon mDragOverFolderIcon = null; 204 private boolean mCreateUserFolderOnDrop = false; 205 private boolean mAddToExistingFolderOnDrop = false; 206 private DropTarget.DragEnforcer mDragEnforcer; 207 208 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget) 209 private float mXDown; 210 private float mYDown; 211 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6; 212 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3; 213 final static float TOUCH_SLOP_DAMPING_FACTOR = 4; 214 215 // Relating to the animation of items being dropped externally 216 public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0; 217 public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1; 218 public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2; 219 public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3; 220 public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4; 221 222 // Related to dragging, folder creation and reordering 223 private static final int DRAG_MODE_NONE = 0; 224 private static final int DRAG_MODE_CREATE_FOLDER = 1; 225 private static final int DRAG_MODE_ADD_TO_FOLDER = 2; 226 private static final int DRAG_MODE_REORDER = 3; 227 private int mDragMode = DRAG_MODE_NONE; 228 private int mLastReorderX = -1; 229 private int mLastReorderY = -1; 230 231 // These variables are used for storing the initial and final values during workspace animations 232 private int mSavedScrollX; 233 private float mSavedRotationY; 234 private float mSavedTranslationX; 235 private float mCurrentScaleX; 236 private float mCurrentScaleY; 237 private float mCurrentRotationY; 238 private float mCurrentTranslationX; 239 private float mCurrentTranslationY; 240 private float[] mOldTranslationXs; 241 private float[] mOldTranslationYs; 242 private float[] mOldScaleXs; 243 private float[] mOldScaleYs; 244 private float[] mOldBackgroundAlphas; 245 private float[] mOldBackgroundAlphaMultipliers; 246 private float[] mOldAlphas; 247 private float[] mOldRotationYs; 248 private float[] mNewTranslationXs; 249 private float[] mNewTranslationYs; 250 private float[] mNewScaleXs; 251 private float[] mNewScaleYs; 252 private float[] mNewBackgroundAlphas; 253 private float[] mNewBackgroundAlphaMultipliers; 254 private float[] mNewAlphas; 255 private float[] mNewRotationYs; 256 private float mTransitionProgress; 257 258 /** 259 * Used to inflate the Workspace from XML. 260 * 261 * @param context The application's context. 262 * @param attrs The attributes set containing the Workspace's customization values. 263 */ 264 public Workspace(Context context, AttributeSet attrs) { 265 this(context, attrs, 0); 266 } 267 268 /** 269 * Used to inflate the Workspace from XML. 270 * 271 * @param context The application's context. 272 * @param attrs The attributes set containing the Workspace's customization values. 273 * @param defStyle Unused. 274 */ 275 public Workspace(Context context, AttributeSet attrs, int defStyle) { 276 super(context, attrs, defStyle); 277 mContentIsRefreshable = false; 278 279 mDragEnforcer = new DropTarget.DragEnforcer(context); 280 // With workspace, data is available straight from the get-go 281 setDataIsReady(); 282 283 final Resources res = getResources(); 284 mFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens); 285 mWallpaperManager = WallpaperManager.getInstance(context); 286 287 int cellCountX = DEFAULT_CELL_COUNT_X; 288 int cellCountY = DEFAULT_CELL_COUNT_Y; 289 290 TypedArray a = context.obtainStyledAttributes(attrs, 291 R.styleable.Workspace, defStyle, 0); 292 293 if (LauncherApplication.isScreenLarge()) { 294 // Determine number of rows/columns dynamically 295 // TODO: This code currently fails on tablets with an aspect ratio < 1.3. 296 // Around that ratio we should make cells the same size in portrait and 297 // landscape 298 TypedArray actionBarSizeTypedArray = 299 context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize }); 300 DisplayMetrics displayMetrics = res.getDisplayMetrics(); 301 final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f); 302 final float systemBarHeight = res.getDimension(R.dimen.status_bar_height); 303 final float smallestScreenDim = res.getConfiguration().smallestScreenWidthDp * 304 displayMetrics.density; 305 306 cellCountX = 1; 307 while (CellLayout.widthInPortrait(res, cellCountX + 1) <= smallestScreenDim) { 308 cellCountX++; 309 } 310 311 cellCountY = 1; 312 while (actionBarHeight + CellLayout.heightInLandscape(res, cellCountY + 1) 313 <= smallestScreenDim - systemBarHeight) { 314 cellCountY++; 315 } 316 } 317 318 mSpringLoadedShrinkFactor = 319 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; 320 321 // if the value is manually specified, use that instead 322 cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX); 323 cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY); 324 mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1); 325 a.recycle(); 326 327 setOnHierarchyChangeListener(this); 328 329 LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY); 330 setHapticFeedbackEnabled(false); 331 332 mLauncher = (Launcher) context; 333 initWorkspace(); 334 335 // Disable multitouch across the workspace/all apps/customize tray 336 setMotionEventSplittingEnabled(true); 337 } 338 339 // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each 340 // dimension if unsuccessful 341 public int[] estimateItemSize(int hSpan, int vSpan, 342 ItemInfo itemInfo, boolean springLoaded) { 343 int[] size = new int[2]; 344 if (getChildCount() > 0) { 345 CellLayout cl = (CellLayout) mLauncher.getWorkspace().getChildAt(0); 346 Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan); 347 size[0] = r.width(); 348 size[1] = r.height(); 349 if (springLoaded) { 350 size[0] *= mSpringLoadedShrinkFactor; 351 size[1] *= mSpringLoadedShrinkFactor; 352 } 353 return size; 354 } else { 355 size[0] = Integer.MAX_VALUE; 356 size[1] = Integer.MAX_VALUE; 357 return size; 358 } 359 } 360 public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo, 361 int hCell, int vCell, int hSpan, int vSpan) { 362 Rect r = new Rect(); 363 cl.cellToRect(hCell, vCell, hSpan, vSpan, r); 364 return r; 365 } 366 367 public void buildPageHardwareLayers() { 368 if (getWindowToken() != null) { 369 final int childCount = getChildCount(); 370 for (int i = 0; i < childCount; i++) { 371 CellLayout cl = (CellLayout) getChildAt(i); 372 cl.getShortcutsAndWidgets().buildLayer(); 373 } 374 } 375 } 376 377 public void onDragStart(DragSource source, Object info, int dragAction) { 378 mIsDragOccuring = true; 379 updateChildrenLayersEnabled(); 380 mLauncher.lockScreenOrientationOnLargeUI(); 381 } 382 383 public void onDragEnd() { 384 mIsDragOccuring = false; 385 updateChildrenLayersEnabled(); 386 mLauncher.unlockScreenOrientationOnLargeUI(); 387 } 388 389 /** 390 * Initializes various states for this workspace. 391 */ 392 protected void initWorkspace() { 393 Context context = getContext(); 394 mCurrentPage = mDefaultPage; 395 Launcher.setScreen(mCurrentPage); 396 LauncherApplication app = (LauncherApplication)context.getApplicationContext(); 397 mIconCache = app.getIconCache(); 398 setWillNotDraw(false); 399 setChildrenDrawnWithCacheEnabled(true); 400 401 final Resources res = getResources(); 402 try { 403 mBackground = res.getDrawable(R.drawable.apps_customize_bg); 404 } catch (Resources.NotFoundException e) { 405 // In this case, we will skip drawing background protection 406 } 407 408 mWallpaperOffset = new WallpaperOffsetInterpolator(); 409 Display display = mLauncher.getWindowManager().getDefaultDisplay(); 410 display.getSize(mDisplaySize); 411 mWallpaperTravelWidth = (int) (mDisplaySize.x * 412 wallpaperTravelToScreenWidthRatio(mDisplaySize.x, mDisplaySize.y)); 413 414 mMaxDistanceForFolderCreation = (0.6f * res.getDimensionPixelSize(R.dimen.app_icon_size)); 415 mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); 416 } 417 418 @Override 419 protected int getScrollMode() { 420 return SmoothPagedView.X_LARGE_MODE; 421 } 422 423 @Override 424 public void onChildViewAdded(View parent, View child) { 425 if (!(child instanceof CellLayout)) { 426 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 427 } 428 CellLayout cl = ((CellLayout) child); 429 cl.setOnInterceptTouchListener(this); 430 cl.setClickable(true); 431 cl.enableHardwareLayers(); 432 } 433 434 @Override 435 public void onChildViewRemoved(View parent, View child) { 436 } 437 438 /** 439 * @return The open folder on the current screen, or null if there is none 440 */ 441 Folder getOpenFolder() { 442 DragLayer dragLayer = mLauncher.getDragLayer(); 443 int count = dragLayer.getChildCount(); 444 for (int i = 0; i < count; i++) { 445 View child = dragLayer.getChildAt(i); 446 if (child instanceof Folder) { 447 Folder folder = (Folder) child; 448 if (folder.getInfo().opened) 449 return folder; 450 } 451 } 452 return null; 453 } 454 455 boolean isTouchActive() { 456 return mTouchState != TOUCH_STATE_REST; 457 } 458 459 /** 460 * Adds the specified child in the specified screen. The position and dimension of 461 * the child are defined by x, y, spanX and spanY. 462 * 463 * @param child The child to add in one of the workspace's screens. 464 * @param screen The screen in which to add the child. 465 * @param x The X position of the child in the screen's grid. 466 * @param y The Y position of the child in the screen's grid. 467 * @param spanX The number of cells spanned horizontally by the child. 468 * @param spanY The number of cells spanned vertically by the child. 469 */ 470 void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY) { 471 addInScreen(child, container, screen, x, y, spanX, spanY, false); 472 } 473 474 /** 475 * Adds the specified child in the specified screen. The position and dimension of 476 * the child are defined by x, y, spanX and spanY. 477 * 478 * @param child The child to add in one of the workspace's screens. 479 * @param screen The screen in which to add the child. 480 * @param x The X position of the child in the screen's grid. 481 * @param y The Y position of the child in the screen's grid. 482 * @param spanX The number of cells spanned horizontally by the child. 483 * @param spanY The number of cells spanned vertically by the child. 484 * @param insert When true, the child is inserted at the beginning of the children list. 485 */ 486 void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY, 487 boolean insert) { 488 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 489 if (screen < 0 || screen >= getChildCount()) { 490 Log.e(TAG, "The screen must be >= 0 and < " + getChildCount() 491 + " (was " + screen + "); skipping child"); 492 return; 493 } 494 } 495 496 final CellLayout layout; 497 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 498 layout = mLauncher.getHotseat().getLayout(); 499 child.setOnKeyListener(null); 500 501 // Hide folder title in the hotseat 502 if (child instanceof FolderIcon) { 503 ((FolderIcon) child).setTextVisible(false); 504 } 505 506 if (screen < 0) { 507 screen = mLauncher.getHotseat().getOrderInHotseat(x, y); 508 } else { 509 // Note: We do this to ensure that the hotseat is always laid out in the orientation 510 // of the hotseat in order regardless of which orientation they were added 511 x = mLauncher.getHotseat().getCellXFromOrder(screen); 512 y = mLauncher.getHotseat().getCellYFromOrder(screen); 513 } 514 } else { 515 // Show folder title if not in the hotseat 516 if (child instanceof FolderIcon) { 517 ((FolderIcon) child).setTextVisible(true); 518 } 519 520 layout = (CellLayout) getChildAt(screen); 521 child.setOnKeyListener(new IconKeyEventListener()); 522 } 523 524 LayoutParams genericLp = child.getLayoutParams(); 525 CellLayout.LayoutParams lp; 526 if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) { 527 lp = new CellLayout.LayoutParams(x, y, spanX, spanY); 528 } else { 529 lp = (CellLayout.LayoutParams) genericLp; 530 lp.cellX = x; 531 lp.cellY = y; 532 lp.cellHSpan = spanX; 533 lp.cellVSpan = spanY; 534 } 535 536 if (spanX < 0 && spanY < 0) { 537 lp.isLockedToGrid = false; 538 } 539 540 // Get the canonical child id to uniquely represent this view in this screen 541 int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY); 542 boolean markCellsAsOccupied = !(child instanceof Folder); 543 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) { 544 // TODO: This branch occurs when the workspace is adding views 545 // outside of the defined grid 546 // maybe we should be deleting these items from the LauncherModel? 547 Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout"); 548 } 549 550 if (!(child instanceof Folder)) { 551 child.setHapticFeedbackEnabled(false); 552 child.setOnLongClickListener(mLongClickListener); 553 } 554 if (child instanceof DropTarget) { 555 mDragController.addDropTarget((DropTarget) child); 556 } 557 } 558 559 /** 560 * Check if the point (x, y) hits a given page. 561 */ 562 private boolean hitsPage(int index, float x, float y) { 563 final View page = getChildAt(index); 564 if (page != null) { 565 float[] localXY = { x, y }; 566 mapPointFromSelfToChild(page, localXY); 567 return (localXY[0] >= 0 && localXY[0] < page.getWidth() 568 && localXY[1] >= 0 && localXY[1] < page.getHeight()); 569 } 570 return false; 571 } 572 573 @Override 574 protected boolean hitsPreviousPage(float x, float y) { 575 // mNextPage is set to INVALID_PAGE whenever we are stationary. 576 // Calculating "next page" this way ensures that you scroll to whatever page you tap on 577 final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; 578 579 // Only allow tap to next page on large devices, where there's significant margin outside 580 // the active workspace 581 return LauncherApplication.isScreenLarge() && hitsPage(current - 1, x, y); 582 } 583 584 @Override 585 protected boolean hitsNextPage(float x, float y) { 586 // mNextPage is set to INVALID_PAGE whenever we are stationary. 587 // Calculating "next page" this way ensures that you scroll to whatever page you tap on 588 final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; 589 590 // Only allow tap to next page on large devices, where there's significant margin outside 591 // the active workspace 592 return LauncherApplication.isScreenLarge() && hitsPage(current + 1, x, y); 593 } 594 595 /** 596 * Called directly from a CellLayout (not by the framework), after we've been added as a 597 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout 598 * that it should intercept touch events, which is not something that is normally supported. 599 */ 600 @Override 601 public boolean onTouch(View v, MotionEvent event) { 602 return (isSmall() || !isFinishedSwitchingState()); 603 } 604 605 public boolean isSwitchingState() { 606 return mIsSwitchingState; 607 } 608 609 /** This differs from isSwitchingState in that we take into account how far the transition 610 * has completed. */ 611 public boolean isFinishedSwitchingState() { 612 return !mIsSwitchingState || (mTransitionProgress > 0.5f); 613 } 614 615 protected void onWindowVisibilityChanged (int visibility) { 616 mLauncher.onWindowVisibilityChanged(visibility); 617 } 618 619 @Override 620 public boolean dispatchUnhandledMove(View focused, int direction) { 621 if (isSmall() || !isFinishedSwitchingState()) { 622 // when the home screens are shrunken, shouldn't allow side-scrolling 623 return false; 624 } 625 return super.dispatchUnhandledMove(focused, direction); 626 } 627 628 @Override 629 public boolean onInterceptTouchEvent(MotionEvent ev) { 630 switch (ev.getAction() & MotionEvent.ACTION_MASK) { 631 case MotionEvent.ACTION_DOWN: 632 mXDown = ev.getX(); 633 mYDown = ev.getY(); 634 break; 635 case MotionEvent.ACTION_POINTER_UP: 636 case MotionEvent.ACTION_UP: 637 if (mTouchState == TOUCH_STATE_REST) { 638 final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); 639 if (!currentPage.lastDownOnOccupiedCell()) { 640 onWallpaperTap(ev); 641 } 642 } 643 } 644 return super.onInterceptTouchEvent(ev); 645 } 646 647 @Override 648 protected void determineScrollingStart(MotionEvent ev) { 649 if (isSmall()) return; 650 if (!isFinishedSwitchingState()) return; 651 652 float deltaX = Math.abs(ev.getX() - mXDown); 653 float deltaY = Math.abs(ev.getY() - mYDown); 654 655 if (Float.compare(deltaX, 0f) == 0) return; 656 657 float slope = deltaY / deltaX; 658 float theta = (float) Math.atan(slope); 659 660 if (deltaX > mTouchSlop || deltaY > mTouchSlop) { 661 cancelCurrentPageLongPress(); 662 } 663 664 if (theta > MAX_SWIPE_ANGLE) { 665 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace 666 return; 667 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) { 668 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to 669 // increase the touch slop to make it harder to begin scrolling the workspace. This 670 // results in vertically scrolling widgets to more easily. The higher the angle, the 671 // more we increase touch slop. 672 theta -= START_DAMPING_TOUCH_SLOP_ANGLE; 673 float extraRatio = (float) 674 Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE))); 675 super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio); 676 } else { 677 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special 678 super.determineScrollingStart(ev); 679 } 680 } 681 682 @Override 683 protected boolean isScrollingIndicatorEnabled() { 684 return super.isScrollingIndicatorEnabled() && (mState != State.SPRING_LOADED); 685 } 686 687 protected void onPageBeginMoving() { 688 super.onPageBeginMoving(); 689 690 if (isHardwareAccelerated()) { 691 updateChildrenLayersEnabled(); 692 } else { 693 if (mNextPage != INVALID_PAGE) { 694 // we're snapping to a particular screen 695 enableChildrenCache(mCurrentPage, mNextPage); 696 } else { 697 // this is when user is actively dragging a particular screen, they might 698 // swipe it either left or right (but we won't advance by more than one screen) 699 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1); 700 } 701 } 702 703 // Only show page outlines as we pan if we are on large screen 704 if (LauncherApplication.isScreenLarge()) { 705 showOutlines(); 706 mIsStaticWallpaper = mWallpaperManager.getWallpaperInfo() == null; 707 } 708 709 // If we are not fading in adjacent screens, we still need to restore the alpha in case the 710 // user scrolls while we are transitioning (should not affect dispatchDraw optimizations) 711 if (!mFadeInAdjacentScreens) { 712 for (int i = 0; i < getChildCount(); ++i) { 713 ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f); 714 } 715 } 716 717 // Show the scroll indicator as you pan the page 718 showScrollingIndicator(false); 719 } 720 721 protected void onPageEndMoving() { 722 super.onPageEndMoving(); 723 724 if (isHardwareAccelerated()) { 725 updateChildrenLayersEnabled(); 726 } else { 727 clearChildrenCache(); 728 } 729 730 731 if (mDragController.isDragging()) { 732 if (isSmall()) { 733 // If we are in springloaded mode, then force an event to check if the current touch 734 // is under a new page (to scroll to) 735 mDragController.forceMoveEvent(); 736 } 737 } else { 738 // If we are not mid-dragging, hide the page outlines if we are on a large screen 739 if (LauncherApplication.isScreenLarge()) { 740 hideOutlines(); 741 } 742 743 // Hide the scroll indicator as you pan the page 744 if (!mDragController.isDragging()) { 745 hideScrollingIndicator(false); 746 } 747 } 748 mOverScrollMaxBackgroundAlpha = 0.0f; 749 mOverScrollPageIndex = -1; 750 751 if (mDelayedResizeRunnable != null) { 752 mDelayedResizeRunnable.run(); 753 mDelayedResizeRunnable = null; 754 } 755 756 if (mDelayedSnapToPageRunnable != null) { 757 mDelayedSnapToPageRunnable.run(); 758 mDelayedSnapToPageRunnable = null; 759 } 760 } 761 762 @Override 763 protected void notifyPageSwitchListener() { 764 super.notifyPageSwitchListener(); 765 Launcher.setScreen(mCurrentPage); 766 }; 767 768 // As a ratio of screen height, the total distance we want the parallax effect to span 769 // horizontally 770 private float wallpaperTravelToScreenWidthRatio(int width, int height) { 771 float aspectRatio = width / (float) height; 772 773 // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width 774 // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width 775 // We will use these two data points to extrapolate how much the wallpaper parallax effect 776 // to span (ie travel) at any aspect ratio: 777 778 final float ASPECT_RATIO_LANDSCAPE = 16/10f; 779 final float ASPECT_RATIO_PORTRAIT = 10/16f; 780 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; 781 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; 782 783 // To find out the desired width at different aspect ratios, we use the following two 784 // formulas, where the coefficient on x is the aspect ratio (width/height): 785 // (16/10)x + y = 1.5 786 // (10/16)x + y = 1.2 787 // We solve for x and y and end up with a final formula: 788 final float x = 789 (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / 790 (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); 791 final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; 792 return x * aspectRatio + y; 793 } 794 795 // The range of scroll values for Workspace 796 private int getScrollRange() { 797 return getChildOffset(getChildCount() - 1) - getChildOffset(0); 798 } 799 800 protected void setWallpaperDimension() { 801 DisplayMetrics displayMetrics = new DisplayMetrics(); 802 mLauncher.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics); 803 final int maxDim = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels); 804 final int minDim = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels); 805 806 // We need to ensure that there is enough extra space in the wallpaper for the intended 807 // parallax effects 808 if (LauncherApplication.isScreenLarge()) { 809 mWallpaperWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim)); 810 mWallpaperHeight = maxDim; 811 } else { 812 mWallpaperWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim); 813 mWallpaperHeight = maxDim; 814 } 815 new Thread("setWallpaperDimension") { 816 public void run() { 817 mWallpaperManager.suggestDesiredDimensions(mWallpaperWidth, mWallpaperHeight); 818 } 819 }.start(); 820 } 821 822 private float wallpaperOffsetForCurrentScroll() { 823 // Set wallpaper offset steps (1 / (number of screens - 1)) 824 mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f); 825 826 // For the purposes of computing the scrollRange and overScrollOffset, we assume 827 // that mLayoutScale is 1. This means that when we're in spring-loaded mode, 828 // there's no discrepancy between the wallpaper offset for a given page. 829 float layoutScale = mLayoutScale; 830 mLayoutScale = 1f; 831 int scrollRange = getScrollRange(); 832 833 // Again, we adjust the wallpaper offset to be consistent between values of mLayoutScale 834 float adjustedScrollX = Math.max(0, Math.min(getScrollX(), mMaxScrollX)); 835 adjustedScrollX *= mWallpaperScrollRatio; 836 mLayoutScale = layoutScale; 837 838 float scrollProgress = 839 adjustedScrollX / (float) scrollRange; 840 841 if (LauncherApplication.isScreenLarge() && mIsStaticWallpaper) { 842 // The wallpaper travel width is how far, from left to right, the wallpaper will move 843 // at this orientation. On tablets in portrait mode we don't move all the way to the 844 // edges of the wallpaper, or otherwise the parallax effect would be too strong. 845 int wallpaperTravelWidth = Math.min(mWallpaperTravelWidth, mWallpaperWidth); 846 847 float offsetInDips = wallpaperTravelWidth * scrollProgress + 848 (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it 849 float offset = offsetInDips / (float) mWallpaperWidth; 850 return offset; 851 } else { 852 return scrollProgress; 853 } 854 } 855 856 private void syncWallpaperOffsetWithScroll() { 857 final boolean enableWallpaperEffects = isHardwareAccelerated(); 858 if (enableWallpaperEffects) { 859 mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll()); 860 } 861 } 862 863 public void updateWallpaperOffsetImmediately() { 864 mUpdateWallpaperOffsetImmediately = true; 865 } 866 867 private void updateWallpaperOffsets() { 868 boolean updateNow = false; 869 boolean keepUpdating = true; 870 if (mUpdateWallpaperOffsetImmediately) { 871 updateNow = true; 872 keepUpdating = false; 873 mWallpaperOffset.jumpToFinal(); 874 mUpdateWallpaperOffsetImmediately = false; 875 } else { 876 updateNow = keepUpdating = mWallpaperOffset.computeScrollOffset(); 877 } 878 if (updateNow) { 879 if (mWindowToken != null) { 880 mWallpaperManager.setWallpaperOffsets(mWindowToken, 881 mWallpaperOffset.getCurrX(), mWallpaperOffset.getCurrY()); 882 } 883 } 884 if (keepUpdating) { 885 invalidate(); 886 } 887 } 888 889 @Override 890 protected void updateCurrentPageScroll() { 891 super.updateCurrentPageScroll(); 892 computeWallpaperScrollRatio(mCurrentPage); 893 } 894 895 @Override 896 protected void snapToPage(int whichPage) { 897 super.snapToPage(whichPage); 898 computeWallpaperScrollRatio(whichPage); 899 } 900 901 @Override 902 protected void snapToPage(int whichPage, int duration) { 903 super.snapToPage(whichPage, duration); 904 computeWallpaperScrollRatio(whichPage); 905 } 906 907 protected void snapToPage(int whichPage, Runnable r) { 908 if (mDelayedSnapToPageRunnable != null) { 909 mDelayedSnapToPageRunnable.run(); 910 } 911 mDelayedSnapToPageRunnable = r; 912 snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION); 913 } 914 915 private void computeWallpaperScrollRatio(int page) { 916 // Here, we determine what the desired scroll would be with and without a layout scale, 917 // and compute a ratio between the two. This allows us to adjust the wallpaper offset 918 // as though there is no layout scale. 919 float layoutScale = mLayoutScale; 920 int scaled = getChildOffset(page) - getRelativeChildOffset(page); 921 mLayoutScale = 1.0f; 922 float unscaled = getChildOffset(page) - getRelativeChildOffset(page); 923 mLayoutScale = layoutScale; 924 if (scaled > 0) { 925 mWallpaperScrollRatio = (1.0f * unscaled) / scaled; 926 } else { 927 mWallpaperScrollRatio = 1f; 928 } 929 } 930 931 class WallpaperOffsetInterpolator { 932 float mFinalHorizontalWallpaperOffset = 0.0f; 933 float mFinalVerticalWallpaperOffset = 0.5f; 934 float mHorizontalWallpaperOffset = 0.0f; 935 float mVerticalWallpaperOffset = 0.5f; 936 long mLastWallpaperOffsetUpdateTime; 937 boolean mIsMovingFast; 938 boolean mOverrideHorizontalCatchupConstant; 939 float mHorizontalCatchupConstant = 0.35f; 940 float mVerticalCatchupConstant = 0.35f; 941 942 public WallpaperOffsetInterpolator() { 943 } 944 945 public void setOverrideHorizontalCatchupConstant(boolean override) { 946 mOverrideHorizontalCatchupConstant = override; 947 } 948 949 public void setHorizontalCatchupConstant(float f) { 950 mHorizontalCatchupConstant = f; 951 } 952 953 public void setVerticalCatchupConstant(float f) { 954 mVerticalCatchupConstant = f; 955 } 956 957 public boolean computeScrollOffset() { 958 if (Float.compare(mHorizontalWallpaperOffset, mFinalHorizontalWallpaperOffset) == 0 && 959 Float.compare(mVerticalWallpaperOffset, mFinalVerticalWallpaperOffset) == 0) { 960 mIsMovingFast = false; 961 return false; 962 } 963 boolean isLandscape = mDisplaySize.x > mDisplaySize.y; 964 965 long currentTime = System.currentTimeMillis(); 966 long timeSinceLastUpdate = currentTime - mLastWallpaperOffsetUpdateTime; 967 timeSinceLastUpdate = Math.min((long) (1000/30f), timeSinceLastUpdate); 968 timeSinceLastUpdate = Math.max(1L, timeSinceLastUpdate); 969 970 float xdiff = Math.abs(mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset); 971 if (!mIsMovingFast && xdiff > 0.07) { 972 mIsMovingFast = true; 973 } 974 975 float fractionToCatchUpIn1MsHorizontal; 976 if (mOverrideHorizontalCatchupConstant) { 977 fractionToCatchUpIn1MsHorizontal = mHorizontalCatchupConstant; 978 } else if (mIsMovingFast) { 979 fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.5f : 0.75f; 980 } else { 981 // slow 982 fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.27f : 0.5f; 983 } 984 float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant; 985 986 fractionToCatchUpIn1MsHorizontal /= 33f; 987 fractionToCatchUpIn1MsVertical /= 33f; 988 989 final float UPDATE_THRESHOLD = 0.00001f; 990 float hOffsetDelta = mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset; 991 float vOffsetDelta = mFinalVerticalWallpaperOffset - mVerticalWallpaperOffset; 992 boolean jumpToFinalValue = Math.abs(hOffsetDelta) < UPDATE_THRESHOLD && 993 Math.abs(vOffsetDelta) < UPDATE_THRESHOLD; 994 995 // Don't have any lag between workspace and wallpaper on non-large devices 996 if (!LauncherApplication.isScreenLarge() || jumpToFinalValue) { 997 mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset; 998 mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset; 999 } else { 1000 float percentToCatchUpVertical = 1001 Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsVertical); 1002 float percentToCatchUpHorizontal = 1003 Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsHorizontal); 1004 mHorizontalWallpaperOffset += percentToCatchUpHorizontal * hOffsetDelta; 1005 mVerticalWallpaperOffset += percentToCatchUpVertical * vOffsetDelta; 1006 } 1007 1008 mLastWallpaperOffsetUpdateTime = System.currentTimeMillis(); 1009 return true; 1010 } 1011 1012 public float getCurrX() { 1013 return mHorizontalWallpaperOffset; 1014 } 1015 1016 public float getFinalX() { 1017 return mFinalHorizontalWallpaperOffset; 1018 } 1019 1020 public float getCurrY() { 1021 return mVerticalWallpaperOffset; 1022 } 1023 1024 public float getFinalY() { 1025 return mFinalVerticalWallpaperOffset; 1026 } 1027 1028 public void setFinalX(float x) { 1029 mFinalHorizontalWallpaperOffset = Math.max(0f, Math.min(x, 1.0f)); 1030 } 1031 1032 public void setFinalY(float y) { 1033 mFinalVerticalWallpaperOffset = Math.max(0f, Math.min(y, 1.0f)); 1034 } 1035 1036 public void jumpToFinal() { 1037 mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset; 1038 mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset; 1039 } 1040 } 1041 1042 @Override 1043 public void computeScroll() { 1044 super.computeScroll(); 1045 syncWallpaperOffsetWithScroll(); 1046 } 1047 1048 void showOutlines() { 1049 if (!isSmall() && !mIsSwitchingState) { 1050 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); 1051 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); 1052 mChildrenOutlineFadeInAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 1.0f); 1053 mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION); 1054 mChildrenOutlineFadeInAnimation.start(); 1055 } 1056 } 1057 1058 void hideOutlines() { 1059 if (!isSmall() && !mIsSwitchingState) { 1060 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); 1061 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); 1062 mChildrenOutlineFadeOutAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 0.0f); 1063 mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION); 1064 mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY); 1065 mChildrenOutlineFadeOutAnimation.start(); 1066 } 1067 } 1068 1069 public void showOutlinesTemporarily() { 1070 if (!mIsPageMoving && !isTouchActive()) { 1071 snapToPage(mCurrentPage); 1072 } 1073 } 1074 1075 public void setChildrenOutlineAlpha(float alpha) { 1076 mChildrenOutlineAlpha = alpha; 1077 for (int i = 0; i < getChildCount(); i++) { 1078 CellLayout cl = (CellLayout) getChildAt(i); 1079 cl.setBackgroundAlpha(alpha); 1080 } 1081 } 1082 1083 public float getChildrenOutlineAlpha() { 1084 return mChildrenOutlineAlpha; 1085 } 1086 1087 void disableBackground() { 1088 mDrawBackground = false; 1089 } 1090 void enableBackground() { 1091 mDrawBackground = true; 1092 } 1093 1094 private void animateBackgroundGradient(float finalAlpha, boolean animated) { 1095 if (mBackground == null) return; 1096 if (mBackgroundFadeInAnimation != null) { 1097 mBackgroundFadeInAnimation.cancel(); 1098 mBackgroundFadeInAnimation = null; 1099 } 1100 if (mBackgroundFadeOutAnimation != null) { 1101 mBackgroundFadeOutAnimation.cancel(); 1102 mBackgroundFadeOutAnimation = null; 1103 } 1104 float startAlpha = getBackgroundAlpha(); 1105 if (finalAlpha != startAlpha) { 1106 if (animated) { 1107 mBackgroundFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha); 1108 mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() { 1109 public void onAnimationUpdate(ValueAnimator animation) { 1110 setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue()); 1111 } 1112 }); 1113 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f)); 1114 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION); 1115 mBackgroundFadeOutAnimation.start(); 1116 } else { 1117 setBackgroundAlpha(finalAlpha); 1118 } 1119 } 1120 } 1121 1122 public void setBackgroundAlpha(float alpha) { 1123 if (alpha != mBackgroundAlpha) { 1124 mBackgroundAlpha = alpha; 1125 invalidate(); 1126 } 1127 } 1128 1129 public float getBackgroundAlpha() { 1130 return mBackgroundAlpha; 1131 } 1132 1133 /** 1134 * Due to 3D transformations, if two CellLayouts are theoretically touching each other, 1135 * on the xy plane, when one is rotated along the y-axis, the gap between them is perceived 1136 * as being larger. This method computes what offset the rotated view should be translated 1137 * in order to minimize this perceived gap. 1138 * @param degrees Angle of the view 1139 * @param width Width of the view 1140 * @param height Height of the view 1141 * @return Offset to be used in a View.setTranslationX() call 1142 */ 1143 private float getOffsetXForRotation(float degrees, int width, int height) { 1144 mMatrix.reset(); 1145 mCamera.save(); 1146 mCamera.rotateY(Math.abs(degrees)); 1147 mCamera.getMatrix(mMatrix); 1148 mCamera.restore(); 1149 1150 mMatrix.preTranslate(-width * 0.5f, -height * 0.5f); 1151 mMatrix.postTranslate(width * 0.5f, height * 0.5f); 1152 mTempFloat2[0] = width; 1153 mTempFloat2[1] = height; 1154 mMatrix.mapPoints(mTempFloat2); 1155 return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f); 1156 } 1157 1158 float backgroundAlphaInterpolator(float r) { 1159 float pivotA = 0.1f; 1160 float pivotB = 0.4f; 1161 if (r < pivotA) { 1162 return 0; 1163 } else if (r > pivotB) { 1164 return 1.0f; 1165 } else { 1166 return (r - pivotA)/(pivotB - pivotA); 1167 } 1168 } 1169 1170 float overScrollBackgroundAlphaInterpolator(float r) { 1171 float threshold = 0.08f; 1172 1173 if (r > mOverScrollMaxBackgroundAlpha) { 1174 mOverScrollMaxBackgroundAlpha = r; 1175 } else if (r < mOverScrollMaxBackgroundAlpha) { 1176 r = mOverScrollMaxBackgroundAlpha; 1177 } 1178 1179 return Math.min(r / threshold, 1.0f); 1180 } 1181 1182 private void screenScrolledLargeUI(int screenCenter) { 1183 if (isSwitchingState()) return; 1184 boolean isInOverscroll = false; 1185 for (int i = 0; i < getChildCount(); i++) { 1186 CellLayout cl = (CellLayout) getChildAt(i); 1187 if (cl != null) { 1188 float scrollProgress = getScrollProgress(screenCenter, cl, i); 1189 float rotation = WORKSPACE_ROTATION * scrollProgress; 1190 float translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight()); 1191 1192 // If the current page (i) is being over scrolled, we use a different 1193 // set of rules for setting the background alpha multiplier. 1194 if (!isSmall()) { 1195 if ((mOverScrollX < 0 && i == 0) || (mOverScrollX > mMaxScrollX && 1196 i == getChildCount() -1)) { 1197 isInOverscroll = true; 1198 rotation *= -1; 1199 cl.setBackgroundAlphaMultiplier( 1200 overScrollBackgroundAlphaInterpolator(Math.abs(scrollProgress))); 1201 mOverScrollPageIndex = i; 1202 cl.setOverScrollAmount(Math.abs(scrollProgress), i == 0); 1203 if (!mOverscrollTransformsSet) { 1204 mOverscrollTransformsSet = true; 1205 cl.setPivotX(cl.getMeasuredWidth() * (i == 0 ? 0.75f : 0.25f)); 1206 cl.setPivotY(cl.getMeasuredHeight() * 0.5f); 1207 cl.setOverscrollTransformsDirty(true); 1208 } 1209 } else if (mOverScrollPageIndex != i) { 1210 cl.setBackgroundAlphaMultiplier( 1211 backgroundAlphaInterpolator(Math.abs(scrollProgress))); 1212 } 1213 } 1214 cl.setTranslationX(translationX); 1215 cl.setRotationY(rotation); 1216 if (mFadeInAdjacentScreens && !isSmall()) { 1217 float alpha = 1 - Math.abs(scrollProgress); 1218 cl.setShortcutAndWidgetAlpha(alpha); 1219 } 1220 } 1221 } 1222 if (mOverscrollTransformsSet && !isInOverscroll) { 1223 mOverscrollTransformsSet = false; 1224 ((CellLayout) getChildAt(0)).resetOverscrollTransforms(); 1225 ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms(); 1226 } 1227 invalidate(); 1228 } 1229 1230 private void screenScrolledStandardUI(int screenCenter) { 1231 if (mOverScrollX < 0 || mOverScrollX > mMaxScrollX) { 1232 int index = mOverScrollX < 0 ? 0 : getChildCount() - 1; 1233 CellLayout cl = (CellLayout) getChildAt(index); 1234 float scrollProgress = getScrollProgress(screenCenter, cl, index); 1235 cl.setOverScrollAmount(Math.abs(scrollProgress), index == 0); 1236 float rotation = - WORKSPACE_OVERSCROLL_ROTATION * scrollProgress; 1237 cl.setRotationY(rotation); 1238 setFadeForOverScroll(Math.abs(scrollProgress)); 1239 if (!mOverscrollTransformsSet) { 1240 mOverscrollTransformsSet = true; 1241 cl.setCameraDistance(mDensity * CAMERA_DISTANCE); 1242 cl.setPivotX(cl.getMeasuredWidth() * (index == 0 ? 0.75f : 0.25f)); 1243 cl.setPivotY(cl.getMeasuredHeight() * 0.5f); 1244 cl.setOverscrollTransformsDirty(true); 1245 } 1246 } else { 1247 if (mOverscrollFade != 0) { 1248 setFadeForOverScroll(0); 1249 } 1250 if (mOverscrollTransformsSet) { 1251 mOverscrollTransformsSet = false; 1252 ((CellLayout) getChildAt(0)).resetOverscrollTransforms(); 1253 ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms(); 1254 } 1255 } 1256 } 1257 1258 @Override 1259 protected void screenScrolled(int screenCenter) { 1260 if (LauncherApplication.isScreenLarge()) { 1261 // We don't call super.screenScrolled() here because we handle the adjacent pages alpha 1262 // ourselves (for efficiency), and there are no scrolling indicators to update. 1263 screenScrolledLargeUI(screenCenter); 1264 } else { 1265 super.screenScrolled(screenCenter); 1266 screenScrolledStandardUI(screenCenter); 1267 } 1268 } 1269 1270 @Override 1271 protected void overScroll(float amount) { 1272 if (LauncherApplication.isScreenLarge()) { 1273 dampedOverScroll(amount); 1274 } else { 1275 acceleratedOverScroll(amount); 1276 } 1277 } 1278 1279 protected void onAttachedToWindow() { 1280 super.onAttachedToWindow(); 1281 mWindowToken = getWindowToken(); 1282 computeScroll(); 1283 mDragController.setWindowToken(mWindowToken); 1284 } 1285 1286 protected void onDetachedFromWindow() { 1287 mWindowToken = null; 1288 } 1289 1290 @Override 1291 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1292 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { 1293 mUpdateWallpaperOffsetImmediately = true; 1294 } 1295 super.onLayout(changed, left, top, right, bottom); 1296 } 1297 1298 @Override 1299 protected void onDraw(Canvas canvas) { 1300 updateWallpaperOffsets(); 1301 1302 // Draw the background gradient if necessary 1303 if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) { 1304 int alpha = (int) (mBackgroundAlpha * 255); 1305 mBackground.setAlpha(alpha); 1306 mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(), 1307 getMeasuredHeight()); 1308 mBackground.draw(canvas); 1309 } 1310 1311 super.onDraw(canvas); 1312 } 1313 1314 boolean isDrawingBackgroundGradient() { 1315 return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground); 1316 } 1317 1318 @Override 1319 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 1320 if (!mLauncher.isAllAppsVisible()) { 1321 final Folder openFolder = getOpenFolder(); 1322 if (openFolder != null) { 1323 return openFolder.requestFocus(direction, previouslyFocusedRect); 1324 } else { 1325 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 1326 } 1327 } 1328 return false; 1329 } 1330 1331 @Override 1332 public int getDescendantFocusability() { 1333 if (isSmall()) { 1334 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 1335 } 1336 return super.getDescendantFocusability(); 1337 } 1338 1339 @Override 1340 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1341 if (!mLauncher.isAllAppsVisible()) { 1342 final Folder openFolder = getOpenFolder(); 1343 if (openFolder != null) { 1344 openFolder.addFocusables(views, direction); 1345 } else { 1346 super.addFocusables(views, direction, focusableMode); 1347 } 1348 } 1349 } 1350 1351 public boolean isSmall() { 1352 return mState == State.SMALL || mState == State.SPRING_LOADED; 1353 } 1354 1355 void enableChildrenCache(int fromPage, int toPage) { 1356 if (fromPage > toPage) { 1357 final int temp = fromPage; 1358 fromPage = toPage; 1359 toPage = temp; 1360 } 1361 1362 final int screenCount = getChildCount(); 1363 1364 fromPage = Math.max(fromPage, 0); 1365 toPage = Math.min(toPage, screenCount - 1); 1366 1367 for (int i = fromPage; i <= toPage; i++) { 1368 final CellLayout layout = (CellLayout) getChildAt(i); 1369 layout.setChildrenDrawnWithCacheEnabled(true); 1370 layout.setChildrenDrawingCacheEnabled(true); 1371 } 1372 } 1373 1374 void clearChildrenCache() { 1375 final int screenCount = getChildCount(); 1376 for (int i = 0; i < screenCount; i++) { 1377 final CellLayout layout = (CellLayout) getChildAt(i); 1378 layout.setChildrenDrawnWithCacheEnabled(false); 1379 // In software mode, we don't want the items to continue to be drawn into bitmaps 1380 if (!isHardwareAccelerated()) { 1381 layout.setChildrenDrawingCacheEnabled(false); 1382 } 1383 } 1384 } 1385 1386 private void updateChildrenLayersEnabled() { 1387 boolean small = mState == State.SMALL || mIsSwitchingState; 1388 boolean enableChildrenLayers = small || mAnimatingViewIntoPlace || isPageMoving(); 1389 1390 if (enableChildrenLayers != mChildrenLayersEnabled) { 1391 mChildrenLayersEnabled = enableChildrenLayers; 1392 for (int i = 0; i < getPageCount(); i++) { 1393 ((ViewGroup)getChildAt(i)).setChildrenLayersEnabled(mChildrenLayersEnabled); 1394 } 1395 } 1396 } 1397 1398 protected void onWallpaperTap(MotionEvent ev) { 1399 final int[] position = mTempCell; 1400 getLocationOnScreen(position); 1401 1402 int pointerIndex = ev.getActionIndex(); 1403 position[0] += (int) ev.getX(pointerIndex); 1404 position[1] += (int) ev.getY(pointerIndex); 1405 1406 mWallpaperManager.sendWallpaperCommand(getWindowToken(), 1407 ev.getAction() == MotionEvent.ACTION_UP 1408 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP, 1409 position[0], position[1], 0, null); 1410 } 1411 1412 /* 1413 * This interpolator emulates the rate at which the perceived scale of an object changes 1414 * as its distance from a camera increases. When this interpolator is applied to a scale 1415 * animation on a view, it evokes the sense that the object is shrinking due to moving away 1416 * from the camera. 1417 */ 1418 static class ZInterpolator implements TimeInterpolator { 1419 private float focalLength; 1420 1421 public ZInterpolator(float foc) { 1422 focalLength = foc; 1423 } 1424 1425 public float getInterpolation(float input) { 1426 return (1.0f - focalLength / (focalLength + input)) / 1427 (1.0f - focalLength / (focalLength + 1.0f)); 1428 } 1429 } 1430 1431 /* 1432 * The exact reverse of ZInterpolator. 1433 */ 1434 static class InverseZInterpolator implements TimeInterpolator { 1435 private ZInterpolator zInterpolator; 1436 public InverseZInterpolator(float foc) { 1437 zInterpolator = new ZInterpolator(foc); 1438 } 1439 public float getInterpolation(float input) { 1440 return 1 - zInterpolator.getInterpolation(1 - input); 1441 } 1442 } 1443 1444 /* 1445 * ZInterpolator compounded with an ease-out. 1446 */ 1447 static class ZoomOutInterpolator implements TimeInterpolator { 1448 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f); 1449 private final ZInterpolator zInterpolator = new ZInterpolator(0.13f); 1450 1451 public float getInterpolation(float input) { 1452 return decelerate.getInterpolation(zInterpolator.getInterpolation(input)); 1453 } 1454 } 1455 1456 /* 1457 * InvereZInterpolator compounded with an ease-out. 1458 */ 1459 static class ZoomInInterpolator implements TimeInterpolator { 1460 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f); 1461 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f); 1462 1463 public float getInterpolation(float input) { 1464 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input)); 1465 } 1466 } 1467 1468 private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator(); 1469 1470 /* 1471 * 1472 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we 1473 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace 1474 * 1475 * These methods mark the appropriate pages as accepting drops (which alters their visual 1476 * appearance). 1477 * 1478 */ 1479 public void onDragStartedWithItem(View v) { 1480 final Canvas canvas = new Canvas(); 1481 1482 // The outline is used to visualize where the item will land if dropped 1483 mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING); 1484 } 1485 1486 public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, Paint alphaClipPaint) { 1487 final Canvas canvas = new Canvas(); 1488 1489 int[] size = estimateItemSize(info.spanX, info.spanY, info, false); 1490 1491 // The outline is used to visualize where the item will land if dropped 1492 mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0], 1493 size[1], alphaClipPaint); 1494 } 1495 1496 public void exitWidgetResizeMode() { 1497 DragLayer dragLayer = mLauncher.getDragLayer(); 1498 dragLayer.clearAllResizeFrames(); 1499 } 1500 1501 private void initAnimationArrays() { 1502 final int childCount = getChildCount(); 1503 if (mOldTranslationXs != null) return; 1504 mOldTranslationXs = new float[childCount]; 1505 mOldTranslationYs = new float[childCount]; 1506 mOldScaleXs = new float[childCount]; 1507 mOldScaleYs = new float[childCount]; 1508 mOldBackgroundAlphas = new float[childCount]; 1509 mOldBackgroundAlphaMultipliers = new float[childCount]; 1510 mOldAlphas = new float[childCount]; 1511 mOldRotationYs = new float[childCount]; 1512 mNewTranslationXs = new float[childCount]; 1513 mNewTranslationYs = new float[childCount]; 1514 mNewScaleXs = new float[childCount]; 1515 mNewScaleYs = new float[childCount]; 1516 mNewBackgroundAlphas = new float[childCount]; 1517 mNewBackgroundAlphaMultipliers = new float[childCount]; 1518 mNewAlphas = new float[childCount]; 1519 mNewRotationYs = new float[childCount]; 1520 } 1521 1522 Animator getChangeStateAnimation(final State state, boolean animated) { 1523 return getChangeStateAnimation(state, animated, 0); 1524 } 1525 1526 Animator getChangeStateAnimation(final State state, boolean animated, int delay) { 1527 if (mState == state) { 1528 return null; 1529 } 1530 1531 // Initialize animation arrays for the first time if necessary 1532 initAnimationArrays(); 1533 1534 AnimatorSet anim = animated ? new AnimatorSet() : null; 1535 1536 // Stop any scrolling, move to the current page right away 1537 setCurrentPage(getNextPage()); 1538 1539 final State oldState = mState; 1540 final boolean oldStateIsNormal = (oldState == State.NORMAL); 1541 final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED); 1542 final boolean oldStateIsSmall = (oldState == State.SMALL); 1543 mState = state; 1544 final boolean stateIsNormal = (state == State.NORMAL); 1545 final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED); 1546 final boolean stateIsSmall = (state == State.SMALL); 1547 float finalScaleFactor = 1.0f; 1548 float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f; 1549 float translationX = 0; 1550 float translationY = 0; 1551 boolean zoomIn = true; 1552 1553 if (state != State.NORMAL) { 1554 finalScaleFactor = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0); 1555 if (oldStateIsNormal && stateIsSmall) { 1556 zoomIn = false; 1557 setLayoutScale(finalScaleFactor); 1558 updateChildrenLayersEnabled(); 1559 } else { 1560 finalBackgroundAlpha = 1.0f; 1561 setLayoutScale(finalScaleFactor); 1562 } 1563 } else { 1564 setLayoutScale(1.0f); 1565 } 1566 1567 final int duration = zoomIn ? 1568 getResources().getInteger(R.integer.config_workspaceUnshrinkTime) : 1569 getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime); 1570 for (int i = 0; i < getChildCount(); i++) { 1571 final CellLayout cl = (CellLayout) getChildAt(i); 1572 float rotation = 0f; 1573 float initialAlpha = cl.getAlpha(); 1574 float finalAlphaMultiplierValue = 1f; 1575 float finalAlpha = (!mFadeInAdjacentScreens || stateIsSpringLoaded || 1576 (i == mCurrentPage)) ? 1f : 0f; 1577 1578 // Determine the pages alpha during the state transition 1579 if ((oldStateIsSmall && stateIsNormal) || 1580 (oldStateIsNormal && stateIsSmall)) { 1581 // To/from workspace - only show the current page unless the transition is not 1582 // animated and the animation end callback below doesn't run; 1583 // or, if we're in spring-loaded mode 1584 if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) { 1585 finalAlpha = 1f; 1586 finalAlphaMultiplierValue = 0f; 1587 } else { 1588 initialAlpha = 0f; 1589 finalAlpha = 0f; 1590 } 1591 } 1592 1593 // Update the rotation of the screen (don't apply rotation on Phone UI) 1594 if (LauncherApplication.isScreenLarge()) { 1595 if (i < mCurrentPage) { 1596 rotation = WORKSPACE_ROTATION; 1597 } else if (i > mCurrentPage) { 1598 rotation = -WORKSPACE_ROTATION; 1599 } 1600 } 1601 1602 // If the screen is not xlarge, then don't rotate the CellLayouts 1603 // NOTE: If we don't update the side pages alpha, then we should not hide the side 1604 // pages. see unshrink(). 1605 if (LauncherApplication.isScreenLarge()) { 1606 translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight()); 1607 } 1608 1609 mOldAlphas[i] = initialAlpha; 1610 mNewAlphas[i] = finalAlpha; 1611 if (animated) { 1612 mOldTranslationXs[i] = cl.getTranslationX(); 1613 mOldTranslationYs[i] = cl.getTranslationY(); 1614 mOldScaleXs[i] = cl.getScaleX(); 1615 mOldScaleYs[i] = cl.getScaleY(); 1616 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha(); 1617 mOldBackgroundAlphaMultipliers[i] = cl.getBackgroundAlphaMultiplier(); 1618 mOldRotationYs[i] = cl.getRotationY(); 1619 1620 mNewTranslationXs[i] = translationX; 1621 mNewTranslationYs[i] = translationY; 1622 mNewScaleXs[i] = finalScaleFactor; 1623 mNewScaleYs[i] = finalScaleFactor; 1624 mNewBackgroundAlphas[i] = finalBackgroundAlpha; 1625 mNewBackgroundAlphaMultipliers[i] = finalAlphaMultiplierValue; 1626 mNewRotationYs[i] = rotation; 1627 } else { 1628 cl.setTranslationX(translationX); 1629 cl.setTranslationY(translationY); 1630 cl.setScaleX(finalScaleFactor); 1631 cl.setScaleY(finalScaleFactor); 1632 cl.setBackgroundAlpha(finalBackgroundAlpha); 1633 cl.setBackgroundAlphaMultiplier(finalAlphaMultiplierValue); 1634 cl.setShortcutAndWidgetAlpha(finalAlpha); 1635 cl.setRotationY(rotation); 1636 } 1637 } 1638 1639 if (animated) { 1640 for (int index = 0; index < getChildCount(); index++) { 1641 final int i = index; 1642 final CellLayout cl = (CellLayout) getChildAt(i); 1643 if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) { 1644 cl.setTranslationX(mNewTranslationXs[i]); 1645 cl.setTranslationY(mNewTranslationYs[i]); 1646 cl.setScaleX(mNewScaleXs[i]); 1647 cl.setScaleY(mNewScaleYs[i]); 1648 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]); 1649 cl.setBackgroundAlphaMultiplier(mNewBackgroundAlphaMultipliers[i]); 1650 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]); 1651 cl.setRotationY(mNewRotationYs[i]); 1652 } else { 1653 LauncherViewPropertyAnimator a = new LauncherViewPropertyAnimator(cl); 1654 a.translationX(mNewTranslationXs[i]) 1655 .translationY(mNewTranslationYs[i]) 1656 .scaleX(mNewScaleXs[i]) 1657 .scaleY(mNewScaleYs[i]) 1658 .setDuration(duration) 1659 .setInterpolator(mZoomInInterpolator); 1660 anim.play(a); 1661 1662 if (mOldAlphas[i] != mNewAlphas[i]) { 1663 LauncherViewPropertyAnimator alphaAnim = 1664 new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets()); 1665 alphaAnim.alpha(mNewAlphas[i]) 1666 .setDuration(duration) 1667 .setInterpolator(mZoomInInterpolator); 1668 anim.play(alphaAnim); 1669 } 1670 if (mOldRotationYs[i] != 0 || mNewRotationYs[i] != 0) { 1671 ValueAnimator rotate = ValueAnimator.ofFloat(0f, 1f).setDuration(duration); 1672 rotate.setInterpolator(new DecelerateInterpolator(2.0f)); 1673 rotate.addUpdateListener(new LauncherAnimatorUpdateListener() { 1674 public void onAnimationUpdate(float a, float b) { 1675 cl.setRotationY(a * mOldRotationYs[i] + b * mNewRotationYs[i]); 1676 } 1677 }); 1678 anim.play(rotate); 1679 } 1680 if (mOldBackgroundAlphas[i] != 0 || 1681 mNewBackgroundAlphas[i] != 0 || 1682 mOldBackgroundAlphaMultipliers[i] != 0 || 1683 mNewBackgroundAlphaMultipliers[i] != 0) { 1684 ValueAnimator bgAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration); 1685 bgAnim.setInterpolator(mZoomInInterpolator); 1686 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { 1687 public void onAnimationUpdate(float a, float b) { 1688 cl.setBackgroundAlpha( 1689 a * mOldBackgroundAlphas[i] + 1690 b * mNewBackgroundAlphas[i]); 1691 cl.setBackgroundAlphaMultiplier( 1692 a * mOldBackgroundAlphaMultipliers[i] + 1693 b * mNewBackgroundAlphaMultipliers[i]); 1694 } 1695 }); 1696 anim.play(bgAnim); 1697 } 1698 } 1699 } 1700 buildPageHardwareLayers(); 1701 anim.setStartDelay(delay); 1702 } 1703 1704 if (stateIsSpringLoaded) { 1705 // Right now we're covered by Apps Customize 1706 // Show the background gradient immediately, so the gradient will 1707 // be showing once AppsCustomize disappears 1708 animateBackgroundGradient(getResources().getInteger( 1709 R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false); 1710 } else { 1711 // Fade the background gradient away 1712 animateBackgroundGradient(0f, true); 1713 } 1714 return anim; 1715 } 1716 1717 @Override 1718 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { 1719 mIsSwitchingState = true; 1720 cancelScrollingIndicatorAnimations(); 1721 } 1722 1723 @Override 1724 public void onLauncherTransitionStep(Launcher l, float t) { 1725 mTransitionProgress = t; 1726 } 1727 1728 @Override 1729 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { 1730 mIsSwitchingState = false; 1731 mWallpaperOffset.setOverrideHorizontalCatchupConstant(false); 1732 updateChildrenLayersEnabled(); 1733 // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure 1734 // ensure that only the current page is visible during (and subsequently, after) the 1735 // transition animation. If fade adjacent pages is disabled, then re-enable the page 1736 // visibility after the transition animation. 1737 if (!mFadeInAdjacentScreens) { 1738 for (int i = 0; i < getChildCount(); i++) { 1739 final CellLayout cl = (CellLayout) getChildAt(i); 1740 cl.setShortcutAndWidgetAlpha(1f); 1741 } 1742 } 1743 } 1744 1745 @Override 1746 public View getContent() { 1747 return this; 1748 } 1749 1750 /** 1751 * Draw the View v into the given Canvas. 1752 * 1753 * @param v the view to draw 1754 * @param destCanvas the canvas to draw on 1755 * @param padding the horizontal and vertical padding to use when drawing 1756 */ 1757 private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) { 1758 final Rect clipRect = mTempRect; 1759 v.getDrawingRect(clipRect); 1760 1761 boolean textVisible = false; 1762 1763 destCanvas.save(); 1764 if (v instanceof TextView && pruneToDrawable) { 1765 Drawable d = ((TextView) v).getCompoundDrawables()[1]; 1766 clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding); 1767 destCanvas.translate(padding / 2, padding / 2); 1768 d.draw(destCanvas); 1769 } else { 1770 if (v instanceof FolderIcon) { 1771 // For FolderIcons the text can bleed into the icon area, and so we need to 1772 // hide the text completely (which can't be achieved by clipping). 1773 if (((FolderIcon) v).getTextVisible()) { 1774 ((FolderIcon) v).setTextVisible(false); 1775 textVisible = true; 1776 } 1777 } else if (v instanceof BubbleTextView) { 1778 final BubbleTextView tv = (BubbleTextView) v; 1779 clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + 1780 tv.getLayout().getLineTop(0); 1781 } else if (v instanceof TextView) { 1782 final TextView tv = (TextView) v; 1783 clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() + 1784 tv.getLayout().getLineTop(0); 1785 } 1786 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2); 1787 destCanvas.clipRect(clipRect, Op.REPLACE); 1788 v.draw(destCanvas); 1789 1790 // Restore text visibility of FolderIcon if necessary 1791 if (textVisible) { 1792 ((FolderIcon) v).setTextVisible(true); 1793 } 1794 } 1795 destCanvas.restore(); 1796 } 1797 1798 /** 1799 * Returns a new bitmap to show when the given View is being dragged around. 1800 * Responsibility for the bitmap is transferred to the caller. 1801 */ 1802 public Bitmap createDragBitmap(View v, Canvas canvas, int padding) { 1803 Bitmap b; 1804 1805 if (v instanceof TextView) { 1806 Drawable d = ((TextView) v).getCompoundDrawables()[1]; 1807 b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding, 1808 d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888); 1809 } else { 1810 b = Bitmap.createBitmap( 1811 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 1812 } 1813 1814 canvas.setBitmap(b); 1815 drawDragView(v, canvas, padding, true); 1816 canvas.setBitmap(null); 1817 1818 return b; 1819 } 1820 1821 /** 1822 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 1823 * Responsibility for the bitmap is transferred to the caller. 1824 */ 1825 private Bitmap createDragOutline(View v, Canvas canvas, int padding) { 1826 final int outlineColor = getResources().getColor(android.R.color.holo_blue_light); 1827 final Bitmap b = Bitmap.createBitmap( 1828 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 1829 1830 canvas.setBitmap(b); 1831 drawDragView(v, canvas, padding, true); 1832 mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); 1833 canvas.setBitmap(null); 1834 return b; 1835 } 1836 1837 /** 1838 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 1839 * Responsibility for the bitmap is transferred to the caller. 1840 */ 1841 private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h, 1842 Paint alphaClipPaint) { 1843 final int outlineColor = getResources().getColor(android.R.color.holo_blue_light); 1844 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 1845 canvas.setBitmap(b); 1846 1847 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight()); 1848 float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(), 1849 (h - padding) / (float) orig.getHeight()); 1850 int scaledWidth = (int) (scaleFactor * orig.getWidth()); 1851 int scaledHeight = (int) (scaleFactor * orig.getHeight()); 1852 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight); 1853 1854 // center the image 1855 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2); 1856 1857 canvas.drawBitmap(orig, src, dst, null); 1858 mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor, 1859 alphaClipPaint); 1860 canvas.setBitmap(null); 1861 1862 return b; 1863 } 1864 1865 void startDrag(CellLayout.CellInfo cellInfo) { 1866 View child = cellInfo.cell; 1867 1868 // Make sure the drag was started by a long press as opposed to a long click. 1869 if (!child.isInTouchMode()) { 1870 return; 1871 } 1872 1873 mDragInfo = cellInfo; 1874 child.setVisibility(INVISIBLE); 1875 CellLayout layout = (CellLayout) child.getParent().getParent(); 1876 layout.prepareChildForDrag(child); 1877 1878 child.clearFocus(); 1879 child.setPressed(false); 1880 1881 final Canvas canvas = new Canvas(); 1882 1883 // The outline is used to visualize where the item will land if dropped 1884 mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING); 1885 beginDragShared(child, this); 1886 } 1887 1888 public void beginDragShared(View child, DragSource source) { 1889 Resources r = getResources(); 1890 1891 // The drag bitmap follows the touch point around on the screen 1892 final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING); 1893 1894 final int bmpWidth = b.getWidth(); 1895 final int bmpHeight = b.getHeight(); 1896 1897 mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY); 1898 int dragLayerX = 1899 Math.round(mTempXY[0] - (bmpWidth - child.getScaleX() * child.getWidth()) / 2); 1900 int dragLayerY = 1901 Math.round(mTempXY[1] - (bmpHeight - child.getScaleY() * bmpHeight) / 2 1902 - DRAG_BITMAP_PADDING / 2); 1903 1904 Point dragVisualizeOffset = null; 1905 Rect dragRect = null; 1906 if (child instanceof BubbleTextView || child instanceof PagedViewIcon) { 1907 int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size); 1908 int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top); 1909 int top = child.getPaddingTop(); 1910 int left = (bmpWidth - iconSize) / 2; 1911 int right = left + iconSize; 1912 int bottom = top + iconSize; 1913 dragLayerY += top; 1914 // Note: The drag region is used to calculate drag layer offsets, but the 1915 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. 1916 dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, 1917 iconPaddingTop - DRAG_BITMAP_PADDING / 2); 1918 dragRect = new Rect(left, top, right, bottom); 1919 } else if (child instanceof FolderIcon) { 1920 int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size); 1921 dragRect = new Rect(0, 0, child.getWidth(), previewSize); 1922 } 1923 1924 // Clear the pressed state if necessary 1925 if (child instanceof BubbleTextView) { 1926 BubbleTextView icon = (BubbleTextView) child; 1927 icon.clearPressedOrFocusedBackground(); 1928 } 1929 1930 mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), 1931 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, child.getScaleX()); 1932 b.recycle(); 1933 1934 // Show the scrolling indicator when you pick up an item 1935 showScrollingIndicator(false); 1936 } 1937 1938 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen, 1939 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) { 1940 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info); 1941 1942 final int[] cellXY = new int[2]; 1943 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); 1944 addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst); 1945 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, cellXY[0], 1946 cellXY[1]); 1947 } 1948 1949 public boolean transitionStateShouldAllowDrop() { 1950 return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL); 1951 } 1952 1953 /** 1954 * {@inheritDoc} 1955 */ 1956 public boolean acceptDrop(DragObject d) { 1957 // If it's an external drop (e.g. from All Apps), check if it should be accepted 1958 CellLayout dropTargetLayout = mDropToLayout; 1959 if (d.dragSource != this) { 1960 // Don't accept the drop if we're not over a screen at time of drop 1961 if (dropTargetLayout == null) { 1962 return false; 1963 } 1964 if (!transitionStateShouldAllowDrop()) return false; 1965 1966 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, 1967 d.dragView, mDragViewVisualCenter); 1968 1969 // We want the point to be mapped to the dragTarget. 1970 if (mLauncher.isHotseatLayout(dropTargetLayout)) { 1971 mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter); 1972 } else { 1973 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); 1974 } 1975 1976 int spanX = 1; 1977 int spanY = 1; 1978 if (mDragInfo != null) { 1979 final CellLayout.CellInfo dragCellInfo = mDragInfo; 1980 spanX = dragCellInfo.spanX; 1981 spanY = dragCellInfo.spanY; 1982 } else { 1983 final ItemInfo dragInfo = (ItemInfo) d.dragInfo; 1984 spanX = dragInfo.spanX; 1985 spanY = dragInfo.spanY; 1986 } 1987 1988 int minSpanX = spanX; 1989 int minSpanY = spanY; 1990 if (d.dragInfo instanceof PendingAddWidgetInfo) { 1991 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX; 1992 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY; 1993 } 1994 1995 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 1996 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout, 1997 mTargetCell); 1998 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 1999 mDragViewVisualCenter[1], mTargetCell); 2000 if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, 2001 mTargetCell, distance, true)) { 2002 return true; 2003 } 2004 if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, 2005 mTargetCell, distance)) { 2006 return true; 2007 } 2008 2009 int[] resultSpan = new int[2]; 2010 mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], 2011 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 2012 null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP); 2013 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 2014 2015 // Don't accept the drop if there's no room for the item 2016 if (!foundCell) { 2017 // Don't show the message if we are dropping on the AllApps button and the hotseat 2018 // is full 2019 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 2020 if (mTargetCell != null && isHotseat) { 2021 Hotseat hotseat = mLauncher.getHotseat(); 2022 if (hotseat.isAllAppsButtonRank( 2023 hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) { 2024 return false; 2025 } 2026 } 2027 2028 mLauncher.showOutOfSpaceMessage(isHotseat); 2029 return false; 2030 } 2031 } 2032 return true; 2033 } 2034 2035 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float 2036 distance, boolean considerTimeout) { 2037 if (distance > mMaxDistanceForFolderCreation) return false; 2038 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2039 2040 if (dropOverView != null) { 2041 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 2042 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { 2043 return false; 2044 } 2045 } 2046 2047 boolean hasntMoved = false; 2048 if (mDragInfo != null) { 2049 hasntMoved = dropOverView == mDragInfo.cell; 2050 } 2051 2052 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) { 2053 return false; 2054 } 2055 2056 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo); 2057 boolean willBecomeShortcut = 2058 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 2059 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); 2060 2061 return (aboveShortcut && willBecomeShortcut); 2062 } 2063 2064 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell, 2065 float distance) { 2066 if (distance > mMaxDistanceForFolderCreation) return false; 2067 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2068 2069 if (dropOverView != null) { 2070 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 2071 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { 2072 return false; 2073 } 2074 } 2075 2076 if (dropOverView instanceof FolderIcon) { 2077 FolderIcon fi = (FolderIcon) dropOverView; 2078 if (fi.acceptDrop(dragInfo)) { 2079 return true; 2080 } 2081 } 2082 return false; 2083 } 2084 2085 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target, 2086 int[] targetCell, float distance, boolean external, DragView dragView, 2087 Runnable postAnimationRunnable) { 2088 if (distance > mMaxDistanceForFolderCreation) return false; 2089 View v = target.getChildAt(targetCell[0], targetCell[1]); 2090 2091 boolean hasntMoved = false; 2092 if (mDragInfo != null) { 2093 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); 2094 hasntMoved = (mDragInfo.cellX == targetCell[0] && 2095 mDragInfo.cellY == targetCell[1]) && (cellParent == target); 2096 } 2097 2098 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; 2099 mCreateUserFolderOnDrop = false; 2100 final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target); 2101 2102 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo); 2103 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo); 2104 2105 if (aboveShortcut && willBecomeShortcut) { 2106 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag(); 2107 ShortcutInfo destInfo = (ShortcutInfo) v.getTag(); 2108 // if the drag started here, we need to remove it from the workspace 2109 if (!external) { 2110 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2111 } 2112 2113 Rect folderLocation = new Rect(); 2114 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation); 2115 target.removeView(v); 2116 2117 FolderIcon fi = 2118 mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]); 2119 destInfo.cellX = -1; 2120 destInfo.cellY = -1; 2121 sourceInfo.cellX = -1; 2122 sourceInfo.cellY = -1; 2123 2124 // If the dragView is null, we can't animate 2125 boolean animate = dragView != null; 2126 if (animate) { 2127 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, 2128 postAnimationRunnable); 2129 } else { 2130 fi.addItem(destInfo); 2131 fi.addItem(sourceInfo); 2132 } 2133 return true; 2134 } 2135 return false; 2136 } 2137 2138 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, 2139 float distance, DragObject d, boolean external) { 2140 if (distance > mMaxDistanceForFolderCreation) return false; 2141 2142 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2143 if (!mAddToExistingFolderOnDrop) return false; 2144 mAddToExistingFolderOnDrop = false; 2145 2146 if (dropOverView instanceof FolderIcon) { 2147 FolderIcon fi = (FolderIcon) dropOverView; 2148 if (fi.acceptDrop(d.dragInfo)) { 2149 fi.onDrop(d); 2150 2151 // if the drag started here, we need to remove it from the workspace 2152 if (!external) { 2153 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2154 } 2155 return true; 2156 } 2157 } 2158 return false; 2159 } 2160 2161 public void onDrop(final DragObject d) { 2162 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, 2163 mDragViewVisualCenter); 2164 2165 CellLayout dropTargetLayout = mDropToLayout; 2166 2167 // We want the point to be mapped to the dragTarget. 2168 if (dropTargetLayout != null) { 2169 if (mLauncher.isHotseatLayout(dropTargetLayout)) { 2170 mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter); 2171 } else { 2172 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); 2173 } 2174 } 2175 2176 int snapScreen = -1; 2177 boolean resizeOnDrop = false; 2178 if (d.dragSource != this) { 2179 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], 2180 (int) mDragViewVisualCenter[1] }; 2181 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d); 2182 } else if (mDragInfo != null) { 2183 final View cell = mDragInfo.cell; 2184 2185 Runnable resizeRunnable = null; 2186 if (dropTargetLayout != null) { 2187 // Move internally 2188 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); 2189 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 2190 long container = hasMovedIntoHotseat ? 2191 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 2192 LauncherSettings.Favorites.CONTAINER_DESKTOP; 2193 int screen = (mTargetCell[0] < 0) ? 2194 mDragInfo.screen : indexOfChild(dropTargetLayout); 2195 int spanX = mDragInfo != null ? mDragInfo.spanX : 1; 2196 int spanY = mDragInfo != null ? mDragInfo.spanY : 1; 2197 // First we find the cell nearest to point at which the item is 2198 // dropped, without any consideration to whether there is an item there. 2199 2200 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) 2201 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); 2202 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2203 mDragViewVisualCenter[1], mTargetCell); 2204 2205 // If the item being dropped is a shortcut and the nearest drop 2206 // cell also contains a shortcut, then create a folder with the two shortcuts. 2207 if (!mInScrollArea && createUserFolderIfNecessary(cell, container, 2208 dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) { 2209 return; 2210 } 2211 2212 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, 2213 distance, d, false)) { 2214 return; 2215 } 2216 2217 // Aside from the special case where we're dropping a shortcut onto a shortcut, 2218 // we need to find the nearest cell location that is vacant 2219 ItemInfo item = (ItemInfo) d.dragInfo; 2220 int minSpanX = item.spanX; 2221 int minSpanY = item.spanY; 2222 if (item.minSpanX > 0 && item.minSpanY > 0) { 2223 minSpanX = item.minSpanX; 2224 minSpanY = item.minSpanY; 2225 } 2226 2227 int[] resultSpan = new int[2]; 2228 mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], 2229 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, 2230 mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); 2231 2232 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 2233 if (foundCell && (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) { 2234 resizeOnDrop = true; 2235 item.spanX = resultSpan[0]; 2236 item.spanY = resultSpan[1]; 2237 } 2238 2239 if (mCurrentPage != screen && !hasMovedIntoHotseat) { 2240 snapScreen = screen; 2241 snapToPage(screen); 2242 } 2243 2244 if (foundCell) { 2245 final ItemInfo info = (ItemInfo) cell.getTag(); 2246 if (hasMovedLayouts) { 2247 // Reparent the view 2248 getParentCellLayoutForView(cell).removeView(cell); 2249 addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1], 2250 info.spanX, info.spanY); 2251 } 2252 2253 // update the item's position after drop 2254 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 2255 lp.cellX = lp.tmpCellX = mTargetCell[0]; 2256 lp.cellY = lp.tmpCellY = mTargetCell[1]; 2257 lp.cellHSpan = item.spanX; 2258 lp.cellVSpan = item.spanY; 2259 lp.isLockedToGrid = true; 2260 cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen, 2261 mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); 2262 2263 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && 2264 cell instanceof LauncherAppWidgetHostView) { 2265 final CellLayout cellLayout = dropTargetLayout; 2266 // We post this call so that the widget has a chance to be placed 2267 // in its final location 2268 2269 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; 2270 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo(); 2271 if (pinfo != null && 2272 pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) { 2273 final Runnable addResizeFrame = new Runnable() { 2274 public void run() { 2275 DragLayer dragLayer = mLauncher.getDragLayer(); 2276 dragLayer.addResizeFrame(info, hostView, cellLayout); 2277 } 2278 }; 2279 resizeRunnable = (new Runnable() { 2280 public void run() { 2281 if (!isPageMoving()) { 2282 addResizeFrame.run(); 2283 } else { 2284 mDelayedResizeRunnable = addResizeFrame; 2285 } 2286 } 2287 }); 2288 } 2289 } 2290 2291 LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX, 2292 lp.cellY); 2293 } else { 2294 // If we can't find a drop location, we return the item to its original position 2295 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 2296 mTargetCell[0] = lp.cellX; 2297 mTargetCell[1] = lp.cellY; 2298 } 2299 } 2300 2301 final CellLayout parent = (CellLayout) cell.getParent().getParent(); 2302 final Runnable finalResizeRunnable = resizeRunnable; 2303 // Prepare it to be animated into its new position 2304 // This must be called after the view has been re-parented 2305 final Runnable onCompleteRunnable = new Runnable() { 2306 @Override 2307 public void run() { 2308 mAnimatingViewIntoPlace = false; 2309 updateChildrenLayersEnabled(); 2310 if (finalResizeRunnable != null) { 2311 finalResizeRunnable.run(); 2312 } 2313 } 2314 }; 2315 mAnimatingViewIntoPlace = true; 2316 if (d.dragView.hasDrawn()) { 2317 final ItemInfo info = (ItemInfo) cell.getTag(); 2318 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) { 2319 int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : 2320 ANIMATE_INTO_POSITION_AND_DISAPPEAR; 2321 animateWidgetDrop(info, parent, d.dragView, 2322 onCompleteRunnable, animationType, cell, false); 2323 } else { 2324 int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; 2325 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, 2326 onCompleteRunnable, this); 2327 } 2328 } else { 2329 d.deferDragViewCleanupPostAnimation = false; 2330 cell.setVisibility(VISIBLE); 2331 } 2332 parent.onDropChild(cell); 2333 } 2334 } 2335 2336 public void setFinalScrollForPageChange(int screen) { 2337 if (screen >= 0) { 2338 mSavedScrollX = getScrollX(); 2339 CellLayout cl = (CellLayout) getChildAt(screen); 2340 mSavedTranslationX = cl.getTranslationX(); 2341 mSavedRotationY = cl.getRotationY(); 2342 final int newX = getChildOffset(screen) - getRelativeChildOffset(screen); 2343 setScrollX(newX); 2344 cl.setTranslationX(0f); 2345 cl.setRotationY(0f); 2346 } 2347 } 2348 2349 public void resetFinalScrollForPageChange(int screen) { 2350 if (screen >= 0) { 2351 CellLayout cl = (CellLayout) getChildAt(screen); 2352 setScrollX(mSavedScrollX); 2353 cl.setTranslationX(mSavedTranslationX); 2354 cl.setRotationY(mSavedRotationY); 2355 } 2356 } 2357 2358 public void getViewLocationRelativeToSelf(View v, int[] location) { 2359 getLocationInWindow(location); 2360 int x = location[0]; 2361 int y = location[1]; 2362 2363 v.getLocationInWindow(location); 2364 int vX = location[0]; 2365 int vY = location[1]; 2366 2367 location[0] = vX - x; 2368 location[1] = vY - y; 2369 } 2370 2371 public void onDragEnter(DragObject d) { 2372 mDragEnforcer.onDragEnter(); 2373 mCreateUserFolderOnDrop = false; 2374 mAddToExistingFolderOnDrop = false; 2375 2376 mDropToLayout = null; 2377 CellLayout layout = getCurrentDropLayout(); 2378 setCurrentDropLayout(layout); 2379 setCurrentDragOverlappingLayout(layout); 2380 2381 // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we 2382 // don't need to show the outlines 2383 if (LauncherApplication.isScreenLarge()) { 2384 showOutlines(); 2385 } 2386 } 2387 2388 public void onDragExit(DragObject d) { 2389 mDragEnforcer.onDragExit(); 2390 2391 // Here we store the final page that will be dropped to, if the workspace in fact 2392 // receives the drop 2393 if (mInScrollArea) { 2394 mDropToLayout = mDragOverlappingLayout; 2395 } else { 2396 mDropToLayout = mDragTargetLayout; 2397 } 2398 2399 if (mDragMode == DRAG_MODE_CREATE_FOLDER) { 2400 mCreateUserFolderOnDrop = true; 2401 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) { 2402 mAddToExistingFolderOnDrop = true; 2403 } 2404 2405 // Reset the scroll area and previous drag target 2406 onResetScrollArea(); 2407 setCurrentDropLayout(null); 2408 setCurrentDragOverlappingLayout(null); 2409 2410 mSpringLoadedDragController.cancel(); 2411 2412 if (!mIsPageMoving) { 2413 hideOutlines(); 2414 } 2415 } 2416 2417 void setCurrentDropLayout(CellLayout layout) { 2418 if (mDragTargetLayout != null) { 2419 mDragTargetLayout.revertTempState(); 2420 mDragTargetLayout.onDragExit(); 2421 } 2422 mDragTargetLayout = layout; 2423 if (mDragTargetLayout != null) { 2424 mDragTargetLayout.onDragEnter(); 2425 } 2426 cleanupReorder(true); 2427 cleanupFolderCreation(); 2428 setCurrentDropOverCell(-1, -1); 2429 } 2430 2431 void setCurrentDragOverlappingLayout(CellLayout layout) { 2432 if (mDragOverlappingLayout != null) { 2433 mDragOverlappingLayout.setIsDragOverlapping(false); 2434 } 2435 mDragOverlappingLayout = layout; 2436 if (mDragOverlappingLayout != null) { 2437 mDragOverlappingLayout.setIsDragOverlapping(true); 2438 } 2439 invalidate(); 2440 } 2441 2442 void setCurrentDropOverCell(int x, int y) { 2443 if (x != mDragOverX || y != mDragOverY) { 2444 mDragOverX = x; 2445 mDragOverY = y; 2446 setDragMode(DRAG_MODE_NONE); 2447 } 2448 } 2449 2450 void setDragMode(int dragMode) { 2451 if (dragMode != mDragMode) { 2452 if (dragMode == DRAG_MODE_NONE) { 2453 cleanupAddToFolder(); 2454 // We don't want to cancel the re-order alarm every time the target cell changes 2455 // as this feels to slow / unresponsive. 2456 cleanupReorder(false); 2457 cleanupFolderCreation(); 2458 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) { 2459 cleanupReorder(true); 2460 cleanupFolderCreation(); 2461 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) { 2462 cleanupAddToFolder(); 2463 cleanupReorder(true); 2464 } else if (dragMode == DRAG_MODE_REORDER) { 2465 cleanupAddToFolder(); 2466 cleanupFolderCreation(); 2467 } 2468 mDragMode = dragMode; 2469 } 2470 } 2471 2472 private void cleanupFolderCreation() { 2473 if (mDragFolderRingAnimator != null) { 2474 mDragFolderRingAnimator.animateToNaturalState(); 2475 } 2476 mFolderCreationAlarm.cancelAlarm(); 2477 } 2478 2479 private void cleanupAddToFolder() { 2480 if (mDragOverFolderIcon != null) { 2481 mDragOverFolderIcon.onDragExit(null); 2482 mDragOverFolderIcon = null; 2483 } 2484 } 2485 2486 private void cleanupReorder(boolean cancelAlarm) { 2487 // Any pending reorders are canceled 2488 if (cancelAlarm) { 2489 mReorderAlarm.cancelAlarm(); 2490 } 2491 mLastReorderX = -1; 2492 mLastReorderY = -1; 2493 } 2494 2495 public DropTarget getDropTargetDelegate(DragObject d) { 2496 return null; 2497 } 2498 2499 /* 2500 * 2501 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 2502 * coordinate space. The argument xy is modified with the return result. 2503 * 2504 */ 2505 void mapPointFromSelfToChild(View v, float[] xy) { 2506 mapPointFromSelfToChild(v, xy, null); 2507 } 2508 2509 /* 2510 * 2511 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 2512 * coordinate space. The argument xy is modified with the return result. 2513 * 2514 * if cachedInverseMatrix is not null, this method will just use that matrix instead of 2515 * computing it itself; we use this to avoid redundant matrix inversions in 2516 * findMatchingPageForDragOver 2517 * 2518 */ 2519 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { 2520 if (cachedInverseMatrix == null) { 2521 v.getMatrix().invert(mTempInverseMatrix); 2522 cachedInverseMatrix = mTempInverseMatrix; 2523 } 2524 int scrollX = getScrollX(); 2525 if (mNextPage != INVALID_PAGE) { 2526 scrollX = mScroller.getFinalX(); 2527 } 2528 xy[0] = xy[0] + scrollX - v.getLeft(); 2529 xy[1] = xy[1] + getScrollY() - v.getTop(); 2530 cachedInverseMatrix.mapPoints(xy); 2531 } 2532 2533 /* 2534 * Maps a point from the Workspace's coordinate system to another sibling view's. (Workspace 2535 * covers the full screen) 2536 */ 2537 void mapPointFromSelfToSibling(View v, float[] xy) { 2538 xy[0] = xy[0] - v.getLeft(); 2539 xy[1] = xy[1] - v.getTop(); 2540 } 2541 2542 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { 2543 xy[0] = xy[0] - hotseat.getLeft() - hotseat.getLayout().getLeft(); 2544 xy[1] = xy[1] - hotseat.getTop() - hotseat.getLayout().getTop(); 2545 } 2546 2547 /* 2548 * 2549 * Convert the 2D coordinate xy from this CellLayout's coordinate space to 2550 * the parent View's coordinate space. The argument xy is modified with the return result. 2551 * 2552 */ 2553 void mapPointFromChildToSelf(View v, float[] xy) { 2554 v.getMatrix().mapPoints(xy); 2555 int scrollX = getScrollX(); 2556 if (mNextPage != INVALID_PAGE) { 2557 scrollX = mScroller.getFinalX(); 2558 } 2559 xy[0] -= (scrollX - v.getLeft()); 2560 xy[1] -= (getScrollY() - v.getTop()); 2561 } 2562 2563 static private float squaredDistance(float[] point1, float[] point2) { 2564 float distanceX = point1[0] - point2[0]; 2565 float distanceY = point2[1] - point2[1]; 2566 return distanceX * distanceX + distanceY * distanceY; 2567 } 2568 2569 /* 2570 * 2571 * Returns true if the passed CellLayout cl overlaps with dragView 2572 * 2573 */ 2574 boolean overlaps(CellLayout cl, DragView dragView, 2575 int dragViewX, int dragViewY, Matrix cachedInverseMatrix) { 2576 // Transform the coordinates of the item being dragged to the CellLayout's coordinates 2577 final float[] draggedItemTopLeft = mTempDragCoordinates; 2578 draggedItemTopLeft[0] = dragViewX; 2579 draggedItemTopLeft[1] = dragViewY; 2580 final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; 2581 draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth(); 2582 draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight(); 2583 2584 // Transform the dragged item's top left coordinates 2585 // to the CellLayout's local coordinates 2586 mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix); 2587 float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]); 2588 float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]); 2589 2590 if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) { 2591 // Transform the dragged item's bottom right coordinates 2592 // to the CellLayout's local coordinates 2593 mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix); 2594 float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]); 2595 float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]); 2596 2597 if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) { 2598 float overlap = (overlapRegionRight - overlapRegionLeft) * 2599 (overlapRegionBottom - overlapRegionTop); 2600 if (overlap > 0) { 2601 return true; 2602 } 2603 } 2604 } 2605 return false; 2606 } 2607 2608 /* 2609 * 2610 * This method returns the CellLayout that is currently being dragged to. In order to drag 2611 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second 2612 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one 2613 * 2614 * Return null if no CellLayout is currently being dragged over 2615 * 2616 */ 2617 private CellLayout findMatchingPageForDragOver( 2618 DragView dragView, float originX, float originY, boolean exact) { 2619 // We loop through all the screens (ie CellLayouts) and see which ones overlap 2620 // with the item being dragged and then choose the one that's closest to the touch point 2621 final int screenCount = getChildCount(); 2622 CellLayout bestMatchingScreen = null; 2623 float smallestDistSoFar = Float.MAX_VALUE; 2624 2625 for (int i = 0; i < screenCount; i++) { 2626 CellLayout cl = (CellLayout) getChildAt(i); 2627 2628 final float[] touchXy = {originX, originY}; 2629 // Transform the touch coordinates to the CellLayout's local coordinates 2630 // If the touch point is within the bounds of the cell layout, we can return immediately 2631 cl.getMatrix().invert(mTempInverseMatrix); 2632 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); 2633 2634 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 2635 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 2636 return cl; 2637 } 2638 2639 if (!exact) { 2640 // Get the center of the cell layout in screen coordinates 2641 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; 2642 cellLayoutCenter[0] = cl.getWidth()/2; 2643 cellLayoutCenter[1] = cl.getHeight()/2; 2644 mapPointFromChildToSelf(cl, cellLayoutCenter); 2645 2646 touchXy[0] = originX; 2647 touchXy[1] = originY; 2648 2649 // Calculate the distance between the center of the CellLayout 2650 // and the touch point 2651 float dist = squaredDistance(touchXy, cellLayoutCenter); 2652 2653 if (dist < smallestDistSoFar) { 2654 smallestDistSoFar = dist; 2655 bestMatchingScreen = cl; 2656 } 2657 } 2658 } 2659 return bestMatchingScreen; 2660 } 2661 2662 // This is used to compute the visual center of the dragView. This point is then 2663 // used to visualize drop locations and determine where to drop an item. The idea is that 2664 // the visual center represents the user's interpretation of where the item is, and hence 2665 // is the appropriate point to use when determining drop location. 2666 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, 2667 DragView dragView, float[] recycle) { 2668 float res[]; 2669 if (recycle == null) { 2670 res = new float[2]; 2671 } else { 2672 res = recycle; 2673 } 2674 2675 // First off, the drag view has been shifted in a way that is not represented in the 2676 // x and y values or the x/yOffsets. Here we account for that shift. 2677 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX); 2678 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); 2679 2680 // These represent the visual top and left of drag view if a dragRect was provided. 2681 // If a dragRect was not provided, then they correspond to the actual view left and 2682 // top, as the dragRect is in that case taken to be the entire dragView. 2683 // R.dimen.dragViewOffsetY. 2684 int left = x - xOffset; 2685 int top = y - yOffset; 2686 2687 // In order to find the visual center, we shift by half the dragRect 2688 res[0] = left + dragView.getDragRegion().width() / 2; 2689 res[1] = top + dragView.getDragRegion().height() / 2; 2690 2691 return res; 2692 } 2693 2694 private boolean isDragWidget(DragObject d) { 2695 return (d.dragInfo instanceof LauncherAppWidgetInfo || 2696 d.dragInfo instanceof PendingAddWidgetInfo); 2697 } 2698 private boolean isExternalDragWidget(DragObject d) { 2699 return d.dragSource != this && isDragWidget(d); 2700 } 2701 2702 public void onDragOver(DragObject d) { 2703 // Skip drag over events while we are dragging over side pages 2704 if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return; 2705 2706 Rect r = new Rect(); 2707 CellLayout layout = null; 2708 ItemInfo item = (ItemInfo) d.dragInfo; 2709 2710 // Ensure that we have proper spans for the item that we are dropping 2711 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); 2712 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, 2713 d.dragView, mDragViewVisualCenter); 2714 2715 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 2716 // Identify whether we have dragged over a side page 2717 if (isSmall()) { 2718 if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) { 2719 mLauncher.getHotseat().getHitRect(r); 2720 if (r.contains(d.x, d.y)) { 2721 layout = mLauncher.getHotseat().getLayout(); 2722 } 2723 } 2724 if (layout == null) { 2725 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false); 2726 } 2727 if (layout != mDragTargetLayout) { 2728 2729 setCurrentDropLayout(layout); 2730 setCurrentDragOverlappingLayout(layout); 2731 2732 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED); 2733 if (isInSpringLoadedMode) { 2734 if (mLauncher.isHotseatLayout(layout)) { 2735 mSpringLoadedDragController.cancel(); 2736 } else { 2737 mSpringLoadedDragController.setAlarm(mDragTargetLayout); 2738 } 2739 } 2740 } 2741 } else { 2742 // Test to see if we are over the hotseat otherwise just use the current page 2743 if (mLauncher.getHotseat() != null && !isDragWidget(d)) { 2744 mLauncher.getHotseat().getHitRect(r); 2745 if (r.contains(d.x, d.y)) { 2746 layout = mLauncher.getHotseat().getLayout(); 2747 } 2748 } 2749 if (layout == null) { 2750 layout = getCurrentDropLayout(); 2751 } 2752 if (layout != mDragTargetLayout) { 2753 setCurrentDropLayout(layout); 2754 setCurrentDragOverlappingLayout(layout); 2755 } 2756 } 2757 2758 // Handle the drag over 2759 if (mDragTargetLayout != null) { 2760 // We want the point to be mapped to the dragTarget. 2761 if (mLauncher.isHotseatLayout(mDragTargetLayout)) { 2762 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 2763 } else { 2764 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null); 2765 } 2766 2767 ItemInfo info = (ItemInfo) d.dragInfo; 2768 2769 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2770 (int) mDragViewVisualCenter[1], 1, 1, mDragTargetLayout, mTargetCell); 2771 2772 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]); 2773 2774 float targetCellDistance = mDragTargetLayout.getDistanceFromCell( 2775 mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); 2776 2777 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], 2778 mTargetCell[1]); 2779 2780 manageFolderFeedback(info, mDragTargetLayout, mTargetCell, 2781 targetCellDistance, dragOverView); 2782 2783 int minSpanX = item.spanX; 2784 int minSpanY = item.spanY; 2785 if (item.minSpanX > 0 && item.minSpanY > 0) { 2786 minSpanX = item.minSpanX; 2787 minSpanY = item.minSpanY; 2788 } 2789 2790 int[] reorderPosition = new int[2]; 2791 reorderPosition = findNearestArea((int) mDragViewVisualCenter[0], 2792 (int) mDragViewVisualCenter[1], item.spanX, item.spanY, mDragTargetLayout, 2793 reorderPosition); 2794 2795 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) 2796 mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, 2797 item.spanY, child, mTargetCell); 2798 2799 if (!nearestDropOccupied) { 2800 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, 2801 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], 2802 mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, 2803 d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion()); 2804 } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) 2805 && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderPosition[0] || 2806 mLastReorderY != reorderPosition[1])) { 2807 // Otherwise, if we aren't adding to or creating a folder and there's no pending 2808 // reorder, then we schedule a reorder 2809 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, 2810 minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child); 2811 mReorderAlarm.setOnAlarmListener(listener); 2812 mReorderAlarm.setAlarm(REORDER_TIMEOUT); 2813 } 2814 2815 if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER || 2816 !nearestDropOccupied) { 2817 if (mDragTargetLayout != null) { 2818 mDragTargetLayout.revertTempState(); 2819 } 2820 } 2821 } 2822 } 2823 2824 private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout, 2825 int[] targetCell, float distance, View dragOverView) { 2826 boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance, 2827 false); 2828 2829 if (mDragMode == DRAG_MODE_NONE && userFolderPending && 2830 !mFolderCreationAlarm.alarmPending()) { 2831 mFolderCreationAlarm.setOnAlarmListener(new 2832 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1])); 2833 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); 2834 return; 2835 } 2836 2837 boolean willAddToFolder = 2838 willAddToExistingUserFolder(info, targetLayout, targetCell, distance); 2839 2840 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) { 2841 mDragOverFolderIcon = ((FolderIcon) dragOverView); 2842 mAddToExistingFolderOnDrop = true; 2843 mDragOverFolderIcon.onDragEnter(info); 2844 if (targetLayout != null) { 2845 targetLayout.clearDragOutlines(); 2846 } 2847 setDragMode(DRAG_MODE_ADD_TO_FOLDER); 2848 return; 2849 } 2850 2851 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) { 2852 setDragMode(DRAG_MODE_NONE); 2853 } 2854 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) { 2855 setDragMode(DRAG_MODE_NONE); 2856 } 2857 2858 return; 2859 } 2860 2861 class FolderCreationAlarmListener implements OnAlarmListener { 2862 CellLayout layout; 2863 int cellX; 2864 int cellY; 2865 2866 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { 2867 this.layout = layout; 2868 this.cellX = cellX; 2869 this.cellY = cellY; 2870 } 2871 2872 public void onAlarm(Alarm alarm) { 2873 if (mDragFolderRingAnimator == null) { 2874 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null); 2875 } 2876 mDragFolderRingAnimator.setCell(cellX, cellY); 2877 mDragFolderRingAnimator.setCellLayout(layout); 2878 mDragFolderRingAnimator.animateToAcceptState(); 2879 layout.showFolderAccept(mDragFolderRingAnimator); 2880 layout.clearDragOutlines(); 2881 setDragMode(DRAG_MODE_CREATE_FOLDER); 2882 } 2883 } 2884 2885 class ReorderAlarmListener implements OnAlarmListener { 2886 float[] dragViewCenter; 2887 int minSpanX, minSpanY, spanX, spanY; 2888 DragView dragView; 2889 View child; 2890 2891 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, 2892 int spanY, DragView dragView, View child) { 2893 this.dragViewCenter = dragViewCenter; 2894 this.minSpanX = minSpanX; 2895 this.minSpanY = minSpanY; 2896 this.spanX = spanX; 2897 this.spanY = spanY; 2898 this.child = child; 2899 this.dragView = dragView; 2900 } 2901 2902 public void onAlarm(Alarm alarm) { 2903 int[] resultSpan = new int[2]; 2904 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2905 (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell); 2906 mLastReorderX = mTargetCell[0]; 2907 mLastReorderY = mTargetCell[1]; 2908 2909 mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0], 2910 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 2911 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER); 2912 2913 if (mTargetCell[0] < 0 || mTargetCell[1] < 0) { 2914 mDragTargetLayout.revertTempState(); 2915 } else { 2916 setDragMode(DRAG_MODE_REORDER); 2917 } 2918 2919 boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; 2920 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, 2921 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], 2922 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, 2923 dragView.getDragVisualizeOffset(), dragView.getDragRegion()); 2924 } 2925 } 2926 2927 @Override 2928 public void getHitRect(Rect outRect) { 2929 // We want the workspace to have the whole area of the display (it will find the correct 2930 // cell layout to drop to in the existing drag/drop logic. 2931 outRect.set(0, 0, mDisplaySize.x, mDisplaySize.y); 2932 } 2933 2934 /** 2935 * Add the item specified by dragInfo to the given layout. 2936 * @return true if successful 2937 */ 2938 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) { 2939 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) { 2940 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false); 2941 return true; 2942 } 2943 mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout)); 2944 return false; 2945 } 2946 2947 private void onDropExternal(int[] touchXY, Object dragInfo, 2948 CellLayout cellLayout, boolean insertAtFirst) { 2949 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null); 2950 } 2951 2952 /** 2953 * Drop an item that didn't originate on one of the workspace screens. 2954 * It may have come from Launcher (e.g. from all apps or customize), or it may have 2955 * come from another app altogether. 2956 * 2957 * NOTE: This can also be called when we are outside of a drag event, when we want 2958 * to add an item to one of the workspace screens. 2959 */ 2960 private void onDropExternal(final int[] touchXY, final Object dragInfo, 2961 final CellLayout cellLayout, boolean insertAtFirst, DragObject d) { 2962 final Runnable exitSpringLoadedRunnable = new Runnable() { 2963 @Override 2964 public void run() { 2965 mLauncher.exitSpringLoadedDragModeDelayed(true, false, null); 2966 } 2967 }; 2968 2969 ItemInfo info = (ItemInfo) dragInfo; 2970 int spanX = info.spanX; 2971 int spanY = info.spanY; 2972 if (mDragInfo != null) { 2973 spanX = mDragInfo.spanX; 2974 spanY = mDragInfo.spanY; 2975 } 2976 2977 final long container = mLauncher.isHotseatLayout(cellLayout) ? 2978 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 2979 LauncherSettings.Favorites.CONTAINER_DESKTOP; 2980 final int screen = indexOfChild(cellLayout); 2981 if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage 2982 && mState != State.SPRING_LOADED) { 2983 snapToPage(screen); 2984 } 2985 2986 if (info instanceof PendingAddItemInfo) { 2987 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo; 2988 2989 boolean findNearestVacantCell = true; 2990 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 2991 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, 2992 cellLayout, mTargetCell); 2993 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2994 mDragViewVisualCenter[1], mTargetCell); 2995 if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell, 2996 distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo, 2997 cellLayout, mTargetCell, distance)) { 2998 findNearestVacantCell = false; 2999 } 3000 } 3001 3002 final ItemInfo item = (ItemInfo) d.dragInfo; 3003 if (findNearestVacantCell) { 3004 int minSpanX = item.spanX; 3005 int minSpanY = item.spanY; 3006 if (item.minSpanX > 0 && item.minSpanY > 0) { 3007 minSpanX = item.minSpanX; 3008 minSpanY = item.minSpanY; 3009 } 3010 int[] resultSpan = new int[2]; 3011 mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], 3012 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY, 3013 null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL); 3014 item.spanX = resultSpan[0]; 3015 item.spanY = resultSpan[1]; 3016 } 3017 3018 Runnable onAnimationCompleteRunnable = new Runnable() { 3019 @Override 3020 public void run() { 3021 // When dragging and dropping from customization tray, we deal with creating 3022 // widgets/shortcuts/folders in a slightly different way 3023 switch (pendingInfo.itemType) { 3024 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 3025 int span[] = new int[2]; 3026 span[0] = item.spanX; 3027 span[1] = item.spanY; 3028 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo, 3029 container, screen, mTargetCell, span, null); 3030 break; 3031 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 3032 mLauncher.processShortcutFromDrop(pendingInfo.componentName, 3033 container, screen, mTargetCell, null); 3034 break; 3035 default: 3036 throw new IllegalStateException("Unknown item type: " + 3037 pendingInfo.itemType); 3038 } 3039 } 3040 }; 3041 View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET 3042 ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; 3043 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR; 3044 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && 3045 ((PendingAddWidgetInfo) pendingInfo).info.configure != null) { 3046 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN; 3047 } 3048 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, 3049 animationStyle, finalView, true); 3050 } else { 3051 // This is for other drag/drop cases, like dragging from All Apps 3052 View view = null; 3053 3054 switch (info.itemType) { 3055 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 3056 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 3057 if (info.container == NO_ID && info instanceof ApplicationInfo) { 3058 // Came from all apps -- make a copy 3059 info = new ShortcutInfo((ApplicationInfo) info); 3060 } 3061 view = mLauncher.createShortcut(R.layout.application, cellLayout, 3062 (ShortcutInfo) info); 3063 break; 3064 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 3065 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, 3066 (FolderInfo) info, mIconCache); 3067 break; 3068 default: 3069 throw new IllegalStateException("Unknown item type: " + info.itemType); 3070 } 3071 3072 // First we find the cell nearest to point at which the item is 3073 // dropped, without any consideration to whether there is an item there. 3074 if (touchXY != null) { 3075 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, 3076 cellLayout, mTargetCell); 3077 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 3078 mDragViewVisualCenter[1], mTargetCell); 3079 d.postAnimationRunnable = exitSpringLoadedRunnable; 3080 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, 3081 true, d.dragView, d.postAnimationRunnable)) { 3082 return; 3083 } 3084 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, 3085 true)) { 3086 return; 3087 } 3088 } 3089 3090 if (touchXY != null) { 3091 // when dragging and dropping, just find the closest free spot 3092 mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], 3093 (int) mDragViewVisualCenter[1], 1, 1, 1, 1, 3094 null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL); 3095 } else { 3096 cellLayout.findCellForSpan(mTargetCell, 1, 1); 3097 } 3098 addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX, 3099 info.spanY, insertAtFirst); 3100 cellLayout.onDropChild(view); 3101 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 3102 cellLayout.getShortcutsAndWidgets().measureChild(view); 3103 3104 3105 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, 3106 lp.cellX, lp.cellY); 3107 3108 if (d.dragView != null) { 3109 // We wrap the animation call in the temporary set and reset of the current 3110 // cellLayout to its final transform -- this means we animate the drag view to 3111 // the correct final location. 3112 setFinalTransitionTransform(cellLayout); 3113 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, 3114 exitSpringLoadedRunnable); 3115 resetTransitionTransform(cellLayout); 3116 } 3117 } 3118 } 3119 3120 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { 3121 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX, 3122 widgetInfo.spanY, widgetInfo, false); 3123 int visibility = layout.getVisibility(); 3124 layout.setVisibility(VISIBLE); 3125 3126 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY); 3127 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY); 3128 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], 3129 Bitmap.Config.ARGB_8888); 3130 Canvas c = new Canvas(b); 3131 3132 layout.measure(width, height); 3133 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]); 3134 layout.draw(c); 3135 c.setBitmap(null); 3136 layout.setVisibility(visibility); 3137 return b; 3138 } 3139 3140 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY, 3141 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, View finalView, 3142 boolean external) { 3143 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final 3144 // location and size on the home screen. 3145 int spanX = info.spanX; 3146 int spanY = info.spanY; 3147 3148 Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY); 3149 loc[0] = r.left; 3150 loc[1] = r.top; 3151 3152 setFinalTransitionTransform(layout); 3153 float cellLayoutScale = 3154 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc); 3155 resetTransitionTransform(layout); 3156 float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); 3157 float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight(); 3158 3159 // The animation will scale the dragView about its center, so we need to center about 3160 // the final location. 3161 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2; 3162 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2; 3163 3164 scaleXY[0] = dragViewScaleX * cellLayoutScale; 3165 scaleXY[1] = dragViewScaleY * cellLayoutScale; 3166 } 3167 3168 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView, 3169 final Runnable onCompleteRunnable, int animationType, final View finalView, 3170 boolean external) { 3171 Rect from = new Rect(); 3172 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from); 3173 3174 int[] finalPos = new int[2]; 3175 float scaleXY[] = new float[2]; 3176 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell, 3177 finalView, external); 3178 3179 Resources res = mLauncher.getResources(); 3180 int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200; 3181 3182 // In the case where we've prebound the widget, we remove it from the DragLayer 3183 if (finalView instanceof AppWidgetHostView && external) { 3184 mLauncher.getDragLayer().removeView(finalView); 3185 } 3186 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) { 3187 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView); 3188 dragView.setCrossFadeBitmap(crossFadeBitmap); 3189 dragView.crossFade((int) (duration * 0.8f)); 3190 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) { 3191 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]); 3192 } 3193 3194 DragLayer dragLayer = mLauncher.getDragLayer(); 3195 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) { 3196 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f, 3197 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration); 3198 } else { 3199 int endStyle; 3200 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) { 3201 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE; 3202 } else { 3203 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;; 3204 } 3205 3206 Runnable onComplete = new Runnable() { 3207 @Override 3208 public void run() { 3209 if (finalView != null) { 3210 finalView.setVisibility(VISIBLE); 3211 } 3212 if (onCompleteRunnable != null) { 3213 onCompleteRunnable.run(); 3214 } 3215 } 3216 }; 3217 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0], 3218 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle, 3219 duration, this); 3220 } 3221 } 3222 3223 public void setFinalTransitionTransform(CellLayout layout) { 3224 if (isSwitchingState()) { 3225 int index = indexOfChild(layout); 3226 mCurrentScaleX = layout.getScaleX(); 3227 mCurrentScaleY = layout.getScaleY(); 3228 mCurrentTranslationX = layout.getTranslationX(); 3229 mCurrentTranslationY = layout.getTranslationY(); 3230 mCurrentRotationY = layout.getRotationY(); 3231 layout.setScaleX(mNewScaleXs[index]); 3232 layout.setScaleY(mNewScaleYs[index]); 3233 layout.setTranslationX(mNewTranslationXs[index]); 3234 layout.setTranslationY(mNewTranslationYs[index]); 3235 layout.setRotationY(mNewRotationYs[index]); 3236 } 3237 } 3238 public void resetTransitionTransform(CellLayout layout) { 3239 if (isSwitchingState()) { 3240 mCurrentScaleX = layout.getScaleX(); 3241 mCurrentScaleY = layout.getScaleY(); 3242 mCurrentTranslationX = layout.getTranslationX(); 3243 mCurrentTranslationY = layout.getTranslationY(); 3244 mCurrentRotationY = layout.getRotationY(); 3245 layout.setScaleX(mCurrentScaleX); 3246 layout.setScaleY(mCurrentScaleY); 3247 layout.setTranslationX(mCurrentTranslationX); 3248 layout.setTranslationY(mCurrentTranslationY); 3249 layout.setRotationY(mCurrentRotationY); 3250 } 3251 } 3252 3253 /** 3254 * Return the current {@link CellLayout}, correctly picking the destination 3255 * screen while a scroll is in progress. 3256 */ 3257 public CellLayout getCurrentDropLayout() { 3258 return (CellLayout) getChildAt(getNextPage()); 3259 } 3260 3261 /** 3262 * Return the current CellInfo describing our current drag; this method exists 3263 * so that Launcher can sync this object with the correct info when the activity is created/ 3264 * destroyed 3265 * 3266 */ 3267 public CellLayout.CellInfo getDragInfo() { 3268 return mDragInfo; 3269 } 3270 3271 /** 3272 * Calculate the nearest cell where the given object would be dropped. 3273 * 3274 * pixelX and pixelY should be in the coordinate system of layout 3275 */ 3276 private int[] findNearestArea(int pixelX, int pixelY, 3277 int spanX, int spanY, CellLayout layout, int[] recycle) { 3278 return layout.findNearestArea( 3279 pixelX, pixelY, spanX, spanY, recycle); 3280 } 3281 3282 void setup(DragController dragController) { 3283 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); 3284 mDragController = dragController; 3285 3286 // hardware layers on children are enabled on startup, but should be disabled until 3287 // needed 3288 updateChildrenLayersEnabled(); 3289 setWallpaperDimension(); 3290 } 3291 3292 /** 3293 * Called at the end of a drag which originated on the workspace. 3294 */ 3295 public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, 3296 boolean success) { 3297 if (success) { 3298 if (target != this) { 3299 if (mDragInfo != null) { 3300 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 3301 if (mDragInfo.cell instanceof DropTarget) { 3302 mDragController.removeDropTarget((DropTarget) mDragInfo.cell); 3303 } 3304 } 3305 } 3306 } else if (mDragInfo != null) { 3307 CellLayout cellLayout; 3308 if (mLauncher.isHotseatLayout(target)) { 3309 cellLayout = mLauncher.getHotseat().getLayout(); 3310 } else { 3311 cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 3312 } 3313 cellLayout.onDropChild(mDragInfo.cell); 3314 } 3315 if (d.cancelled && mDragInfo.cell != null) { 3316 mDragInfo.cell.setVisibility(VISIBLE); 3317 } 3318 mDragOutline = null; 3319 mDragInfo = null; 3320 3321 // Hide the scrolling indicator after you pick up an item 3322 hideScrollingIndicator(false); 3323 } 3324 3325 void updateItemLocationsInDatabase(CellLayout cl) { 3326 int count = cl.getShortcutsAndWidgets().getChildCount(); 3327 3328 int screen = indexOfChild(cl); 3329 int container = Favorites.CONTAINER_DESKTOP; 3330 3331 if (mLauncher.isHotseatLayout(cl)) { 3332 screen = -1; 3333 container = Favorites.CONTAINER_HOTSEAT; 3334 } 3335 3336 for (int i = 0; i < count; i++) { 3337 View v = cl.getShortcutsAndWidgets().getChildAt(i); 3338 ItemInfo info = (ItemInfo) v.getTag(); 3339 // Null check required as the AllApps button doesn't have an item info 3340 if (info != null) { 3341 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screen, info.cellX, 3342 info.cellY, info.spanX, info.spanY); 3343 } 3344 } 3345 } 3346 3347 @Override 3348 public boolean supportsFlingToDelete() { 3349 return true; 3350 } 3351 3352 @Override 3353 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { 3354 // Do nothing 3355 } 3356 3357 @Override 3358 public void onFlingToDeleteCompleted() { 3359 // Do nothing 3360 } 3361 3362 public boolean isDropEnabled() { 3363 return true; 3364 } 3365 3366 @Override 3367 protected void onRestoreInstanceState(Parcelable state) { 3368 super.onRestoreInstanceState(state); 3369 Launcher.setScreen(mCurrentPage); 3370 } 3371 3372 @Override 3373 public void scrollLeft() { 3374 if (!isSmall() && !mIsSwitchingState) { 3375 super.scrollLeft(); 3376 } 3377 Folder openFolder = getOpenFolder(); 3378 if (openFolder != null) { 3379 openFolder.completeDragExit(); 3380 } 3381 } 3382 3383 @Override 3384 public void scrollRight() { 3385 if (!isSmall() && !mIsSwitchingState) { 3386 super.scrollRight(); 3387 } 3388 Folder openFolder = getOpenFolder(); 3389 if (openFolder != null) { 3390 openFolder.completeDragExit(); 3391 } 3392 } 3393 3394 @Override 3395 public boolean onEnterScrollArea(int x, int y, int direction) { 3396 // Ignore the scroll area if we are dragging over the hot seat 3397 boolean isPortrait = !LauncherApplication.isScreenLandscape(getContext()); 3398 if (mLauncher.getHotseat() != null && isPortrait) { 3399 Rect r = new Rect(); 3400 mLauncher.getHotseat().getHitRect(r); 3401 if (r.contains(x, y)) { 3402 return false; 3403 } 3404 } 3405 3406 boolean result = false; 3407 if (!isSmall() && !mIsSwitchingState) { 3408 mInScrollArea = true; 3409 3410 final int page = getNextPage() + 3411 (direction == DragController.SCROLL_LEFT ? -1 : 1); 3412 3413 // We always want to exit the current layout to ensure parity of enter / exit 3414 setCurrentDropLayout(null); 3415 3416 if (0 <= page && page < getChildCount()) { 3417 CellLayout layout = (CellLayout) getChildAt(page); 3418 setCurrentDragOverlappingLayout(layout); 3419 3420 // Workspace is responsible for drawing the edge glow on adjacent pages, 3421 // so we need to redraw the workspace when this may have changed. 3422 invalidate(); 3423 result = true; 3424 } 3425 } 3426 return result; 3427 } 3428 3429 @Override 3430 public boolean onExitScrollArea() { 3431 boolean result = false; 3432 if (mInScrollArea) { 3433 invalidate(); 3434 CellLayout layout = getCurrentDropLayout(); 3435 setCurrentDropLayout(layout); 3436 setCurrentDragOverlappingLayout(layout); 3437 3438 result = true; 3439 mInScrollArea = false; 3440 } 3441 return result; 3442 } 3443 3444 private void onResetScrollArea() { 3445 setCurrentDragOverlappingLayout(null); 3446 mInScrollArea = false; 3447 } 3448 3449 /** 3450 * Returns a specific CellLayout 3451 */ 3452 CellLayout getParentCellLayoutForView(View v) { 3453 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts(); 3454 for (CellLayout layout : layouts) { 3455 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) { 3456 return layout; 3457 } 3458 } 3459 return null; 3460 } 3461 3462 /** 3463 * Returns a list of all the CellLayouts in the workspace. 3464 */ 3465 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() { 3466 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>(); 3467 int screenCount = getChildCount(); 3468 for (int screen = 0; screen < screenCount; screen++) { 3469 layouts.add(((CellLayout) getChildAt(screen))); 3470 } 3471 if (mLauncher.getHotseat() != null) { 3472 layouts.add(mLauncher.getHotseat().getLayout()); 3473 } 3474 return layouts; 3475 } 3476 3477 /** 3478 * We should only use this to search for specific children. Do not use this method to modify 3479 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from 3480 * the hotseat and workspace pages 3481 */ 3482 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() { 3483 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 3484 new ArrayList<ShortcutAndWidgetContainer>(); 3485 int screenCount = getChildCount(); 3486 for (int screen = 0; screen < screenCount; screen++) { 3487 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets()); 3488 } 3489 if (mLauncher.getHotseat() != null) { 3490 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets()); 3491 } 3492 return childrenLayouts; 3493 } 3494 3495 public Folder getFolderForTag(Object tag) { 3496 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 3497 getAllShortcutAndWidgetContainers(); 3498 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 3499 int count = layout.getChildCount(); 3500 for (int i = 0; i < count; i++) { 3501 View child = layout.getChildAt(i); 3502 if (child instanceof Folder) { 3503 Folder f = (Folder) child; 3504 if (f.getInfo() == tag && f.getInfo().opened) { 3505 return f; 3506 } 3507 } 3508 } 3509 } 3510 return null; 3511 } 3512 3513 public View getViewForTag(Object tag) { 3514 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 3515 getAllShortcutAndWidgetContainers(); 3516 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 3517 int count = layout.getChildCount(); 3518 for (int i = 0; i < count; i++) { 3519 View child = layout.getChildAt(i); 3520 if (child.getTag() == tag) { 3521 return child; 3522 } 3523 } 3524 } 3525 return null; 3526 } 3527 3528 void clearDropTargets() { 3529 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 3530 getAllShortcutAndWidgetContainers(); 3531 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 3532 int childCount = layout.getChildCount(); 3533 for (int j = 0; j < childCount; j++) { 3534 View v = layout.getChildAt(j); 3535 if (v instanceof DropTarget) { 3536 mDragController.removeDropTarget((DropTarget) v); 3537 } 3538 } 3539 } 3540 } 3541 3542 void removeItems(final ArrayList<ApplicationInfo> apps) { 3543 final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); 3544 3545 final HashSet<String> packageNames = new HashSet<String>(); 3546 final int appCount = apps.size(); 3547 for (int i = 0; i < appCount; i++) { 3548 packageNames.add(apps.get(i).componentName.getPackageName()); 3549 } 3550 3551 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts(); 3552 for (final CellLayout layoutParent: cellLayouts) { 3553 final ViewGroup layout = layoutParent.getShortcutsAndWidgets(); 3554 3555 // Avoid ANRs by treating each screen separately 3556 post(new Runnable() { 3557 public void run() { 3558 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 3559 childrenToRemove.clear(); 3560 3561 int childCount = layout.getChildCount(); 3562 for (int j = 0; j < childCount; j++) { 3563 final View view = layout.getChildAt(j); 3564 Object tag = view.getTag(); 3565 3566 if (tag instanceof ShortcutInfo) { 3567 final ShortcutInfo info = (ShortcutInfo) tag; 3568 final Intent intent = info.intent; 3569 final ComponentName name = intent.getComponent(); 3570 3571 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 3572 for (String packageName: packageNames) { 3573 if (packageName.equals(name.getPackageName())) { 3574 LauncherModel.deleteItemFromDatabase(mLauncher, info); 3575 childrenToRemove.add(view); 3576 } 3577 } 3578 } 3579 } else if (tag instanceof FolderInfo) { 3580 final FolderInfo info = (FolderInfo) tag; 3581 final ArrayList<ShortcutInfo> contents = info.contents; 3582 final int contentsCount = contents.size(); 3583 final ArrayList<ShortcutInfo> appsToRemoveFromFolder = 3584 new ArrayList<ShortcutInfo>(); 3585 3586 for (int k = 0; k < contentsCount; k++) { 3587 final ShortcutInfo appInfo = contents.get(k); 3588 final Intent intent = appInfo.intent; 3589 final ComponentName name = intent.getComponent(); 3590 3591 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 3592 for (String packageName: packageNames) { 3593 if (packageName.equals(name.getPackageName())) { 3594 appsToRemoveFromFolder.add(appInfo); 3595 } 3596 } 3597 } 3598 } 3599 for (ShortcutInfo item: appsToRemoveFromFolder) { 3600 info.remove(item); 3601 LauncherModel.deleteItemFromDatabase(mLauncher, item); 3602 } 3603 } else if (tag instanceof LauncherAppWidgetInfo) { 3604 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; 3605 final AppWidgetProviderInfo provider = 3606 widgets.getAppWidgetInfo(info.appWidgetId); 3607 if (provider != null) { 3608 for (String packageName: packageNames) { 3609 if (packageName.equals(provider.provider.getPackageName())) { 3610 LauncherModel.deleteItemFromDatabase(mLauncher, info); 3611 childrenToRemove.add(view); 3612 } 3613 } 3614 } 3615 } 3616 } 3617 3618 childCount = childrenToRemove.size(); 3619 for (int j = 0; j < childCount; j++) { 3620 View child = childrenToRemove.get(j); 3621 // Note: We can not remove the view directly from CellLayoutChildren as this 3622 // does not re-mark the spaces as unoccupied. 3623 layoutParent.removeViewInLayout(child); 3624 if (child instanceof DropTarget) { 3625 mDragController.removeDropTarget((DropTarget)child); 3626 } 3627 } 3628 3629 if (childCount > 0) { 3630 layout.requestLayout(); 3631 layout.invalidate(); 3632 } 3633 } 3634 }); 3635 } 3636 } 3637 3638 void updateShortcuts(ArrayList<ApplicationInfo> apps) { 3639 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers(); 3640 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 3641 int childCount = layout.getChildCount(); 3642 for (int j = 0; j < childCount; j++) { 3643 final View view = layout.getChildAt(j); 3644 Object tag = view.getTag(); 3645 if (tag instanceof ShortcutInfo) { 3646 ShortcutInfo info = (ShortcutInfo) tag; 3647 // We need to check for ACTION_MAIN otherwise getComponent() might 3648 // return null for some shortcuts (for instance, for shortcuts to 3649 // web pages.) 3650 final Intent intent = info.intent; 3651 final ComponentName name = intent.getComponent(); 3652 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 3653 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 3654 final int appCount = apps.size(); 3655 for (int k = 0; k < appCount; k++) { 3656 ApplicationInfo app = apps.get(k); 3657 if (app.componentName.equals(name)) { 3658 BubbleTextView shortcut = (BubbleTextView) view; 3659 info.updateIcon(mIconCache); 3660 info.title = app.title.toString(); 3661 shortcut.applyFromShortcutInfo(info, mIconCache); 3662 } 3663 } 3664 } 3665 } 3666 } 3667 } 3668 } 3669 3670 void moveToDefaultScreen(boolean animate) { 3671 if (!isSmall()) { 3672 if (animate) { 3673 snapToPage(mDefaultPage); 3674 } else { 3675 setCurrentPage(mDefaultPage); 3676 } 3677 } 3678 getChildAt(mDefaultPage).requestFocus(); 3679 } 3680 3681 @Override 3682 public void syncPages() { 3683 } 3684 3685 @Override 3686 public void syncPageItems(int page, boolean immediate) { 3687 } 3688 3689 @Override 3690 protected String getCurrentPageDescription() { 3691 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 3692 return String.format(getContext().getString(R.string.workspace_scroll_format), 3693 page + 1, getChildCount()); 3694 } 3695 3696 public void getLocationInDragLayer(int[] loc) { 3697 mLauncher.getDragLayer().getLocationInDragLayer(this, loc); 3698 } 3699 3700 void setFadeForOverScroll(float fade) { 3701 if (!isScrollingIndicatorEnabled()) return; 3702 3703 mOverscrollFade = fade; 3704 float reducedFade = 0.5f + 0.5f * (1 - fade); 3705 final ViewGroup parent = (ViewGroup) getParent(); 3706 final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider)); 3707 final ImageView dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider)); 3708 final View scrollIndicator = getScrollingIndicator(); 3709 3710 cancelScrollingIndicatorAnimations(); 3711 if (qsbDivider != null) qsbDivider.setAlpha(reducedFade); 3712 if (dockDivider != null) dockDivider.setAlpha(reducedFade); 3713 scrollIndicator.setAlpha(1 - fade); 3714 } 3715} 3716