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