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