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