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