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