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