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