Workspace.java revision 3e056d13d2bddc83d30abfd6a8ea17807188d07f
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 1331 setBackgroundAlpha(progress * 0.8f); 1332 1333 if (mLauncher.getHotseat() != null) { 1334 mLauncher.getHotseat().setTranslationX(translationX); 1335 } 1336 if (getPageIndicator() != null) { 1337 getPageIndicator().setTranslationX(translationX); 1338 } 1339 } 1340 } 1341 1342 @Override 1343 protected void screenScrolled(int screenCenter) { 1344 final boolean isRtl = isLayoutRtl(); 1345 super.screenScrolled(screenCenter); 1346 1347 updatePageAlphaValues(screenCenter); 1348 updateStateForCustomContent(screenCenter); 1349 enableHwLayersOnVisiblePages(); 1350 1351 if ((mOverScrollX < 0 && !hasCustomContent()) || mOverScrollX > mMaxScrollX) { 1352 int index = 0; 1353 float pivotX = 0f; 1354 final float leftBiasedPivot = 0.25f; 1355 final float rightBiasedPivot = 0.75f; 1356 final int lowerIndex = 0; 1357 final int upperIndex = getChildCount() - 1; 1358 if (isRtl) { 1359 index = mOverScrollX < 0 ? upperIndex : lowerIndex; 1360 pivotX = (index == 0 ? leftBiasedPivot : rightBiasedPivot); 1361 } else { 1362 index = mOverScrollX < 0 ? lowerIndex : upperIndex; 1363 pivotX = (index == 0 ? rightBiasedPivot : leftBiasedPivot); 1364 } 1365 1366 CellLayout cl = (CellLayout) getChildAt(index); 1367 float scrollProgress = getScrollProgress(screenCenter, cl, index); 1368 final boolean isLeftPage = (isRtl ? index > 0 : index == 0); 1369 cl.setOverScrollAmount(Math.abs(scrollProgress), isLeftPage); 1370 float rotation = -WORKSPACE_OVERSCROLL_ROTATION * scrollProgress; 1371 cl.setRotationY(rotation); 1372 setFadeForOverScroll(Math.abs(scrollProgress)); 1373 if (!mOverscrollTransformsSet) { 1374 mOverscrollTransformsSet = true; 1375 cl.setCameraDistance(mDensity * mCameraDistance); 1376 cl.setPivotX(cl.getMeasuredWidth() * pivotX); 1377 cl.setPivotY(cl.getMeasuredHeight() * 0.5f); 1378 cl.setOverscrollTransformsDirty(true); 1379 } 1380 } else { 1381 if (mOverscrollFade != 0) { 1382 setFadeForOverScroll(0); 1383 } 1384 if (mOverscrollTransformsSet) { 1385 mOverscrollTransformsSet = false; 1386 ((CellLayout) getChildAt(0)).resetOverscrollTransforms(); 1387 ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms(); 1388 } 1389 } 1390 } 1391 1392 @Override 1393 protected void overScroll(float amount) { 1394 acceleratedOverScroll(amount); 1395 } 1396 1397 protected void onAttachedToWindow() { 1398 super.onAttachedToWindow(); 1399 mWindowToken = getWindowToken(); 1400 computeScroll(); 1401 mDragController.setWindowToken(mWindowToken); 1402 } 1403 1404 protected void onDetachedFromWindow() { 1405 mWindowToken = null; 1406 } 1407 1408 @Override 1409 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1410 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { 1411 mUpdateWallpaperOffsetImmediately = true; 1412 } 1413 super.onLayout(changed, left, top, right, bottom); 1414 } 1415 1416 @Override 1417 protected void onDraw(Canvas canvas) { 1418 updateWallpaperOffsets(); 1419 1420 // Draw the background gradient if necessary 1421 if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) { 1422 int alpha = (int) (mBackgroundAlpha * 255); 1423 mBackground.setAlpha(alpha); 1424 mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(), 1425 getMeasuredHeight()); 1426 mBackground.draw(canvas); 1427 } 1428 1429 super.onDraw(canvas); 1430 1431 // Call back to LauncherModel to finish binding after the first draw 1432 post(mBindPages); 1433 } 1434 1435 boolean isDrawingBackgroundGradient() { 1436 return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground); 1437 } 1438 1439 @Override 1440 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 1441 if (!mLauncher.isAllAppsVisible()) { 1442 final Folder openFolder = getOpenFolder(); 1443 if (openFolder != null) { 1444 return openFolder.requestFocus(direction, previouslyFocusedRect); 1445 } else { 1446 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 1447 } 1448 } 1449 return false; 1450 } 1451 1452 @Override 1453 public int getDescendantFocusability() { 1454 if (isSmall()) { 1455 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 1456 } 1457 return super.getDescendantFocusability(); 1458 } 1459 1460 @Override 1461 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1462 if (!mLauncher.isAllAppsVisible()) { 1463 final Folder openFolder = getOpenFolder(); 1464 if (openFolder != null) { 1465 openFolder.addFocusables(views, direction); 1466 } else { 1467 super.addFocusables(views, direction, focusableMode); 1468 } 1469 } 1470 } 1471 1472 public boolean isSmall() { 1473 return mState == State.SMALL || mState == State.SPRING_LOADED; 1474 } 1475 1476 void enableChildrenCache(int fromPage, int toPage) { 1477 if (fromPage > toPage) { 1478 final int temp = fromPage; 1479 fromPage = toPage; 1480 toPage = temp; 1481 } 1482 1483 final int screenCount = getChildCount(); 1484 1485 fromPage = Math.max(fromPage, 0); 1486 toPage = Math.min(toPage, screenCount - 1); 1487 1488 for (int i = fromPage; i <= toPage; i++) { 1489 final CellLayout layout = (CellLayout) getChildAt(i); 1490 layout.setChildrenDrawnWithCacheEnabled(true); 1491 layout.setChildrenDrawingCacheEnabled(true); 1492 } 1493 } 1494 1495 void clearChildrenCache() { 1496 final int screenCount = getChildCount(); 1497 for (int i = 0; i < screenCount; i++) { 1498 final CellLayout layout = (CellLayout) getChildAt(i); 1499 layout.setChildrenDrawnWithCacheEnabled(false); 1500 // In software mode, we don't want the items to continue to be drawn into bitmaps 1501 if (!isHardwareAccelerated()) { 1502 layout.setChildrenDrawingCacheEnabled(false); 1503 } 1504 } 1505 } 1506 1507 1508 private void updateChildrenLayersEnabled(boolean force) { 1509 boolean small = mState == State.SMALL || mIsSwitchingState; 1510 boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving(); 1511 1512 if (enableChildrenLayers != mChildrenLayersEnabled) { 1513 mChildrenLayersEnabled = enableChildrenLayers; 1514 if (mChildrenLayersEnabled) { 1515 enableHwLayersOnVisiblePages(); 1516 } else { 1517 for (int i = 0; i < getPageCount(); i++) { 1518 final CellLayout cl = (CellLayout) getChildAt(i); 1519 cl.disableHardwareLayers(); 1520 } 1521 } 1522 } 1523 } 1524 1525 private void enableHwLayersOnVisiblePages() { 1526 if (mChildrenLayersEnabled) { 1527 final int screenCount = getChildCount(); 1528 getVisiblePages(mTempVisiblePagesRange); 1529 int leftScreen = mTempVisiblePagesRange[0]; 1530 int rightScreen = mTempVisiblePagesRange[1]; 1531 if (leftScreen == rightScreen) { 1532 // make sure we're caching at least two pages always 1533 if (rightScreen < screenCount - 1) { 1534 rightScreen++; 1535 } else if (leftScreen > 0) { 1536 leftScreen--; 1537 } 1538 } 1539 for (int i = 0; i < screenCount; i++) { 1540 final CellLayout layout = (CellLayout) getPageAt(i); 1541 if (!(leftScreen <= i && i <= rightScreen && shouldDrawChild(layout))) { 1542 layout.disableHardwareLayers(); 1543 } 1544 } 1545 for (int i = 0; i < screenCount; i++) { 1546 final CellLayout layout = (CellLayout) getPageAt(i); 1547 if (leftScreen <= i && i <= rightScreen && shouldDrawChild(layout)) { 1548 layout.enableHardwareLayers(); 1549 } 1550 } 1551 } 1552 } 1553 1554 public void buildPageHardwareLayers() { 1555 // force layers to be enabled just for the call to buildLayer 1556 updateChildrenLayersEnabled(true); 1557 if (getWindowToken() != null) { 1558 final int childCount = getChildCount(); 1559 for (int i = 0; i < childCount; i++) { 1560 CellLayout cl = (CellLayout) getChildAt(i); 1561 cl.buildHardwareLayer(); 1562 } 1563 } 1564 updateChildrenLayersEnabled(false); 1565 } 1566 1567 protected void onWallpaperTap(MotionEvent ev) { 1568 final int[] position = mTempCell; 1569 getLocationOnScreen(position); 1570 1571 int pointerIndex = ev.getActionIndex(); 1572 position[0] += (int) ev.getX(pointerIndex); 1573 position[1] += (int) ev.getY(pointerIndex); 1574 1575 mWallpaperManager.sendWallpaperCommand(getWindowToken(), 1576 ev.getAction() == MotionEvent.ACTION_UP 1577 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP, 1578 position[0], position[1], 0, null); 1579 } 1580 1581 /* 1582 * This interpolator emulates the rate at which the perceived scale of an object changes 1583 * as its distance from a camera increases. When this interpolator is applied to a scale 1584 * animation on a view, it evokes the sense that the object is shrinking due to moving away 1585 * from the camera. 1586 */ 1587 static class ZInterpolator implements TimeInterpolator { 1588 private float focalLength; 1589 1590 public ZInterpolator(float foc) { 1591 focalLength = foc; 1592 } 1593 1594 public float getInterpolation(float input) { 1595 return (1.0f - focalLength / (focalLength + input)) / 1596 (1.0f - focalLength / (focalLength + 1.0f)); 1597 } 1598 } 1599 1600 /* 1601 * The exact reverse of ZInterpolator. 1602 */ 1603 static class InverseZInterpolator implements TimeInterpolator { 1604 private ZInterpolator zInterpolator; 1605 public InverseZInterpolator(float foc) { 1606 zInterpolator = new ZInterpolator(foc); 1607 } 1608 public float getInterpolation(float input) { 1609 return 1 - zInterpolator.getInterpolation(1 - input); 1610 } 1611 } 1612 1613 /* 1614 * ZInterpolator compounded with an ease-out. 1615 */ 1616 static class ZoomOutInterpolator implements TimeInterpolator { 1617 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f); 1618 private final ZInterpolator zInterpolator = new ZInterpolator(0.13f); 1619 1620 public float getInterpolation(float input) { 1621 return decelerate.getInterpolation(zInterpolator.getInterpolation(input)); 1622 } 1623 } 1624 1625 /* 1626 * InvereZInterpolator compounded with an ease-out. 1627 */ 1628 static class ZoomInInterpolator implements TimeInterpolator { 1629 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f); 1630 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f); 1631 1632 public float getInterpolation(float input) { 1633 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input)); 1634 } 1635 } 1636 1637 private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator(); 1638 1639 /* 1640 * 1641 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we 1642 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace 1643 * 1644 * These methods mark the appropriate pages as accepting drops (which alters their visual 1645 * appearance). 1646 * 1647 */ 1648 public void onDragStartedWithItem(View v) { 1649 final Canvas canvas = new Canvas(); 1650 1651 // The outline is used to visualize where the item will land if dropped 1652 mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING); 1653 } 1654 1655 public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) { 1656 final Canvas canvas = new Canvas(); 1657 1658 int[] size = estimateItemSize(info.spanX, info.spanY, info, false); 1659 1660 // The outline is used to visualize where the item will land if dropped 1661 mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0], 1662 size[1], clipAlpha); 1663 } 1664 1665 public void exitWidgetResizeMode() { 1666 DragLayer dragLayer = mLauncher.getDragLayer(); 1667 dragLayer.clearAllResizeFrames(); 1668 } 1669 1670 private void initAnimationArrays() { 1671 final int childCount = getChildCount(); 1672 if (mLastChildCount == childCount) return; 1673 1674 mOldBackgroundAlphas = new float[childCount]; 1675 mOldAlphas = new float[childCount]; 1676 mNewBackgroundAlphas = new float[childCount]; 1677 mNewAlphas = new float[childCount]; 1678 } 1679 1680 Animator getChangeStateAnimation(final State state, boolean animated) { 1681 return getChangeStateAnimation(state, animated, 0); 1682 } 1683 1684 void boundByReorderablePages(boolean isReordering, int[] range) { 1685 int count = mScreenOrder.size(); 1686 1687 int start = -1; 1688 int end = -1; 1689 // 1690 for (int i = 0; i < count; i++) { 1691 if (start < 0 && mScreenOrder.get(i) >= 0) { 1692 start = i; 1693 } 1694 if (start >=0 && mScreenOrder.get(i) >= 0) { 1695 end = i; 1696 } 1697 } 1698 range[0] = start; 1699 range[1] = end; 1700 } 1701 1702 protected void onStartReordering() { 1703 super.onStartReordering(); 1704 int count = getChildCount(); 1705 for (int i = 0; i < count; i++) { 1706 ((CellLayout) getChildAt(i)).setUseActiveGlowBackground(true); 1707 } 1708 showOutlines(); 1709 1710 // Reordering handles its own animations, disable the automatic ones. 1711 setLayoutTransition(null); 1712 } 1713 1714 protected void onEndReordering() { 1715 super.onEndReordering(); 1716 int count = getChildCount(); 1717 for (int i = 0; i < count; i++) { 1718 ((CellLayout) getChildAt(i)).setUseActiveGlowBackground(false); 1719 } 1720 hideOutlines(); 1721 1722 mScreenOrder.clear(); 1723 for (int i = 0; i < count; i++) { 1724 CellLayout cl = ((CellLayout) getChildAt(i)); 1725 mScreenOrder.add(getIdForScreen(cl)); 1726 } 1727 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder); 1728 1729 // Re-enable auto layout transitions for page deletion. 1730 setLayoutTransition(mLayoutTransition); 1731 } 1732 1733 Animator getChangeStateAnimation(final State state, boolean animated, int delay) { 1734 if (mState == state) { 1735 return null; 1736 } 1737 1738 // Initialize animation arrays for the first time if necessary 1739 initAnimationArrays(); 1740 1741 AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null; 1742 1743 // Stop any scrolling, move to the current page right away 1744 setCurrentPage(getNextPage()); 1745 1746 final State oldState = mState; 1747 final boolean oldStateIsNormal = (oldState == State.NORMAL); 1748 final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED); 1749 final boolean oldStateIsSmall = (oldState == State.SMALL); 1750 mState = state; 1751 final boolean stateIsNormal = (state == State.NORMAL); 1752 final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED); 1753 final boolean stateIsSmall = (state == State.SMALL); 1754 float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f; 1755 boolean zoomIn = true; 1756 mNewScale = 1.0f; 1757 1758 if (state != State.NORMAL) { 1759 mNewScale = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0); 1760 if (oldStateIsNormal && stateIsSmall) { 1761 zoomIn = false; 1762 updateChildrenLayersEnabled(false); 1763 } else { 1764 finalBackgroundAlpha = 1.0f; 1765 } 1766 } 1767 final int duration = zoomIn ? 1768 getResources().getInteger(R.integer.config_workspaceUnshrinkTime) : 1769 getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime); 1770 for (int i = 0; i < getChildCount(); i++) { 1771 final CellLayout cl = (CellLayout) getChildAt(i); 1772 float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded || 1773 (i == mCurrentPage)) ? 1f : 0f; 1774 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha(); 1775 float initialAlpha = currentAlpha; 1776 1777 // Determine the pages alpha during the state transition 1778 if ((oldStateIsSmall && stateIsNormal) || 1779 (oldStateIsNormal && stateIsSmall)) { 1780 // To/from workspace - only show the current page unless the transition is not 1781 // animated and the animation end callback below doesn't run; 1782 // or, if we're in spring-loaded mode 1783 if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) { 1784 finalAlpha = 1f; 1785 } else { 1786 initialAlpha = 0f; 1787 finalAlpha = 0f; 1788 } 1789 } 1790 1791 mOldAlphas[i] = initialAlpha; 1792 mNewAlphas[i] = finalAlpha; 1793 if (animated) { 1794 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha(); 1795 mNewBackgroundAlphas[i] = finalBackgroundAlpha; 1796 } else { 1797 setScaleX(mNewScale); 1798 setScaleY(mNewScale); 1799 cl.setBackgroundAlpha(finalBackgroundAlpha); 1800 cl.setShortcutAndWidgetAlpha(finalAlpha); 1801 } 1802 } 1803 1804 if (animated) { 1805 LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this); 1806 scale.scaleX(mNewScale) 1807 .scaleY(mNewScale) 1808 .setInterpolator(mZoomInInterpolator); 1809 anim.play(scale); 1810 for (int index = 0; index < getChildCount(); index++) { 1811 final int i = index; 1812 final CellLayout cl = (CellLayout) getChildAt(i); 1813 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha(); 1814 if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) { 1815 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]); 1816 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]); 1817 } else { 1818 1819 if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) { 1820 LauncherViewPropertyAnimator alphaAnim = 1821 new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets()); 1822 alphaAnim.alpha(mNewAlphas[i]) 1823 .setDuration(duration) 1824 .setInterpolator(mZoomInInterpolator); 1825 anim.play(alphaAnim); 1826 } 1827 if (mOldBackgroundAlphas[i] != 0 || 1828 mNewBackgroundAlphas[i] != 0) { 1829 ValueAnimator bgAnim = 1830 LauncherAnimUtils.ofFloat(cl, 0f, 1f).setDuration(duration); 1831 bgAnim.setInterpolator(mZoomInInterpolator); 1832 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { 1833 public void onAnimationUpdate(float a, float b) { 1834 cl.setBackgroundAlpha( 1835 a * mOldBackgroundAlphas[i] + 1836 b * mNewBackgroundAlphas[i]); 1837 } 1838 }); 1839 anim.play(bgAnim); 1840 } 1841 } 1842 } 1843 anim.setStartDelay(delay); 1844 } 1845 1846 if (stateIsSpringLoaded) { 1847 // Right now we're covered by Apps Customize 1848 // Show the background gradient immediately, so the gradient will 1849 // be showing once AppsCustomize disappears 1850 animateBackgroundGradient(getResources().getInteger( 1851 R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false); 1852 } else { 1853 // Fade the background gradient away 1854 animateBackgroundGradient(0f, true); 1855 } 1856 return anim; 1857 } 1858 1859 @Override 1860 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { 1861 mIsSwitchingState = true; 1862 updateChildrenLayersEnabled(false); 1863 } 1864 1865 @Override 1866 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { 1867 } 1868 1869 @Override 1870 public void onLauncherTransitionStep(Launcher l, float t) { 1871 mTransitionProgress = t; 1872 } 1873 1874 @Override 1875 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { 1876 mIsSwitchingState = false; 1877 mWallpaperOffset.setOverrideHorizontalCatchupConstant(false); 1878 updateChildrenLayersEnabled(false); 1879 // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure 1880 // ensure that only the current page is visible during (and subsequently, after) the 1881 // transition animation. If fade adjacent pages is disabled, then re-enable the page 1882 // visibility after the transition animation. 1883 if (!mWorkspaceFadeInAdjacentScreens) { 1884 for (int i = 0; i < getChildCount(); i++) { 1885 final CellLayout cl = (CellLayout) getChildAt(i); 1886 cl.setShortcutAndWidgetAlpha(1f); 1887 } 1888 } 1889 } 1890 1891 @Override 1892 public View getContent() { 1893 return this; 1894 } 1895 1896 /** 1897 * Draw the View v into the given Canvas. 1898 * 1899 * @param v the view to draw 1900 * @param destCanvas the canvas to draw on 1901 * @param padding the horizontal and vertical padding to use when drawing 1902 */ 1903 private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) { 1904 final Rect clipRect = mTempRect; 1905 v.getDrawingRect(clipRect); 1906 1907 boolean textVisible = false; 1908 1909 destCanvas.save(); 1910 if (v instanceof TextView && pruneToDrawable) { 1911 Drawable d = ((TextView) v).getCompoundDrawables()[1]; 1912 clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding); 1913 destCanvas.translate(padding / 2, padding / 2); 1914 d.draw(destCanvas); 1915 } else { 1916 if (v instanceof FolderIcon) { 1917 // For FolderIcons the text can bleed into the icon area, and so we need to 1918 // hide the text completely (which can't be achieved by clipping). 1919 if (((FolderIcon) v).getTextVisible()) { 1920 ((FolderIcon) v).setTextVisible(false); 1921 textVisible = true; 1922 } 1923 } else if (v instanceof BubbleTextView) { 1924 final BubbleTextView tv = (BubbleTextView) v; 1925 clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + 1926 tv.getLayout().getLineTop(0); 1927 } else if (v instanceof TextView) { 1928 final TextView tv = (TextView) v; 1929 clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() + 1930 tv.getLayout().getLineTop(0); 1931 } 1932 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2); 1933 destCanvas.clipRect(clipRect, Op.REPLACE); 1934 v.draw(destCanvas); 1935 1936 // Restore text visibility of FolderIcon if necessary 1937 if (textVisible) { 1938 ((FolderIcon) v).setTextVisible(true); 1939 } 1940 } 1941 destCanvas.restore(); 1942 } 1943 1944 /** 1945 * Returns a new bitmap to show when the given View is being dragged around. 1946 * Responsibility for the bitmap is transferred to the caller. 1947 */ 1948 public Bitmap createDragBitmap(View v, Canvas canvas, int padding) { 1949 Bitmap b; 1950 1951 if (v instanceof TextView) { 1952 Drawable d = ((TextView) v).getCompoundDrawables()[1]; 1953 b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding, 1954 d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888); 1955 } else { 1956 b = Bitmap.createBitmap( 1957 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 1958 } 1959 1960 canvas.setBitmap(b); 1961 drawDragView(v, canvas, padding, true); 1962 canvas.setBitmap(null); 1963 1964 return b; 1965 } 1966 1967 /** 1968 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 1969 * Responsibility for the bitmap is transferred to the caller. 1970 */ 1971 private Bitmap createDragOutline(View v, Canvas canvas, int padding) { 1972 final int outlineColor = getResources().getColor(android.R.color.holo_blue_light); 1973 final Bitmap b = Bitmap.createBitmap( 1974 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 1975 1976 canvas.setBitmap(b); 1977 drawDragView(v, canvas, padding, true); 1978 mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); 1979 canvas.setBitmap(null); 1980 return b; 1981 } 1982 1983 /** 1984 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 1985 * Responsibility for the bitmap is transferred to the caller. 1986 */ 1987 private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h, 1988 boolean clipAlpha) { 1989 final int outlineColor = getResources().getColor(android.R.color.holo_blue_light); 1990 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 1991 canvas.setBitmap(b); 1992 1993 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight()); 1994 float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(), 1995 (h - padding) / (float) orig.getHeight()); 1996 int scaledWidth = (int) (scaleFactor * orig.getWidth()); 1997 int scaledHeight = (int) (scaleFactor * orig.getHeight()); 1998 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight); 1999 2000 // center the image 2001 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2); 2002 2003 canvas.drawBitmap(orig, src, dst, null); 2004 mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor, 2005 clipAlpha); 2006 canvas.setBitmap(null); 2007 2008 return b; 2009 } 2010 2011 void startDrag(CellLayout.CellInfo cellInfo) { 2012 View child = cellInfo.cell; 2013 2014 // Make sure the drag was started by a long press as opposed to a long click. 2015 if (!child.isInTouchMode()) { 2016 return; 2017 } 2018 2019 mDragInfo = cellInfo; 2020 child.setVisibility(INVISIBLE); 2021 CellLayout layout = (CellLayout) child.getParent().getParent(); 2022 layout.prepareChildForDrag(child); 2023 2024 child.clearFocus(); 2025 child.setPressed(false); 2026 2027 final Canvas canvas = new Canvas(); 2028 2029 // The outline is used to visualize where the item will land if dropped 2030 mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING); 2031 beginDragShared(child, this); 2032 } 2033 2034 public void beginDragShared(View child, DragSource source) { 2035 Resources r = getResources(); 2036 2037 // The drag bitmap follows the touch point around on the screen 2038 final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING); 2039 2040 final int bmpWidth = b.getWidth(); 2041 final int bmpHeight = b.getHeight(); 2042 2043 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY); 2044 int dragLayerX = 2045 Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2); 2046 int dragLayerY = 2047 Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2 2048 - DRAG_BITMAP_PADDING / 2); 2049 2050 Point dragVisualizeOffset = null; 2051 Rect dragRect = null; 2052 if (child instanceof BubbleTextView || child instanceof PagedViewIcon) { 2053 int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size); 2054 int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top); 2055 int top = child.getPaddingTop(); 2056 int left = (bmpWidth - iconSize) / 2; 2057 int right = left + iconSize; 2058 int bottom = top + iconSize; 2059 dragLayerY += top; 2060 // Note: The drag region is used to calculate drag layer offsets, but the 2061 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. 2062 dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, 2063 iconPaddingTop - DRAG_BITMAP_PADDING / 2); 2064 dragRect = new Rect(left, top, right, bottom); 2065 } else if (child instanceof FolderIcon) { 2066 int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size); 2067 dragRect = new Rect(0, 0, child.getWidth(), previewSize); 2068 } 2069 2070 // Clear the pressed state if necessary 2071 if (child instanceof BubbleTextView) { 2072 BubbleTextView icon = (BubbleTextView) child; 2073 icon.clearPressedOrFocusedBackground(); 2074 } 2075 2076 mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), 2077 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale); 2078 b.recycle(); 2079 } 2080 2081 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId, 2082 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) { 2083 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info); 2084 2085 final int[] cellXY = new int[2]; 2086 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); 2087 addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst); 2088 2089 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0], 2090 cellXY[1]); 2091 } 2092 2093 public boolean transitionStateShouldAllowDrop() { 2094 return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL); 2095 } 2096 2097 /** 2098 * {@inheritDoc} 2099 */ 2100 public boolean acceptDrop(DragObject d) { 2101 // If it's an external drop (e.g. from All Apps), check if it should be accepted 2102 CellLayout dropTargetLayout = mDropToLayout; 2103 if (d.dragSource != this) { 2104 // Don't accept the drop if we're not over a screen at time of drop 2105 if (dropTargetLayout == null) { 2106 return false; 2107 } 2108 if (!transitionStateShouldAllowDrop()) return false; 2109 2110 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, 2111 d.dragView, mDragViewVisualCenter); 2112 2113 // We want the point to be mapped to the dragTarget. 2114 if (mLauncher.isHotseatLayout(dropTargetLayout)) { 2115 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 2116 } else { 2117 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); 2118 } 2119 2120 int spanX = 1; 2121 int spanY = 1; 2122 if (mDragInfo != null) { 2123 final CellLayout.CellInfo dragCellInfo = mDragInfo; 2124 spanX = dragCellInfo.spanX; 2125 spanY = dragCellInfo.spanY; 2126 } else { 2127 final ItemInfo dragInfo = (ItemInfo) d.dragInfo; 2128 spanX = dragInfo.spanX; 2129 spanY = dragInfo.spanY; 2130 } 2131 2132 int minSpanX = spanX; 2133 int minSpanY = spanY; 2134 if (d.dragInfo instanceof PendingAddWidgetInfo) { 2135 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX; 2136 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY; 2137 } 2138 2139 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2140 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout, 2141 mTargetCell); 2142 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2143 mDragViewVisualCenter[1], mTargetCell); 2144 if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, 2145 mTargetCell, distance, true)) { 2146 return true; 2147 } 2148 if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, 2149 mTargetCell, distance)) { 2150 return true; 2151 } 2152 2153 int[] resultSpan = new int[2]; 2154 mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], 2155 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 2156 null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP); 2157 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 2158 2159 // Don't accept the drop if there's no room for the item 2160 if (!foundCell) { 2161 // Don't show the message if we are dropping on the AllApps button and the hotseat 2162 // is full 2163 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 2164 if (mTargetCell != null && isHotseat) { 2165 Hotseat hotseat = mLauncher.getHotseat(); 2166 if (hotseat.isAllAppsButtonRank( 2167 hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) { 2168 return false; 2169 } 2170 } 2171 2172 mLauncher.showOutOfSpaceMessage(isHotseat); 2173 return false; 2174 } 2175 } 2176 2177 long screenId = getIdForScreen(dropTargetLayout); 2178 if (screenId == EXTRA_EMPTY_SCREEN_ID) { 2179 commitExtraEmptyScreen(); 2180 } 2181 2182 return true; 2183 } 2184 2185 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float 2186 distance, boolean considerTimeout) { 2187 if (distance > mMaxDistanceForFolderCreation) return false; 2188 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2189 2190 if (dropOverView != null) { 2191 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 2192 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { 2193 return false; 2194 } 2195 } 2196 2197 boolean hasntMoved = false; 2198 if (mDragInfo != null) { 2199 hasntMoved = dropOverView == mDragInfo.cell; 2200 } 2201 2202 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) { 2203 return false; 2204 } 2205 2206 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo); 2207 boolean willBecomeShortcut = 2208 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 2209 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); 2210 2211 return (aboveShortcut && willBecomeShortcut); 2212 } 2213 2214 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell, 2215 float distance) { 2216 if (distance > mMaxDistanceForFolderCreation) return false; 2217 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2218 2219 if (dropOverView != null) { 2220 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 2221 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { 2222 return false; 2223 } 2224 } 2225 2226 if (dropOverView instanceof FolderIcon) { 2227 FolderIcon fi = (FolderIcon) dropOverView; 2228 if (fi.acceptDrop(dragInfo)) { 2229 return true; 2230 } 2231 } 2232 return false; 2233 } 2234 2235 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target, 2236 int[] targetCell, float distance, boolean external, DragView dragView, 2237 Runnable postAnimationRunnable) { 2238 if (distance > mMaxDistanceForFolderCreation) return false; 2239 View v = target.getChildAt(targetCell[0], targetCell[1]); 2240 2241 boolean hasntMoved = false; 2242 if (mDragInfo != null) { 2243 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); 2244 hasntMoved = (mDragInfo.cellX == targetCell[0] && 2245 mDragInfo.cellY == targetCell[1]) && (cellParent == target); 2246 } 2247 2248 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; 2249 mCreateUserFolderOnDrop = false; 2250 final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target); 2251 2252 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo); 2253 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo); 2254 2255 if (aboveShortcut && willBecomeShortcut) { 2256 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag(); 2257 ShortcutInfo destInfo = (ShortcutInfo) v.getTag(); 2258 // if the drag started here, we need to remove it from the workspace 2259 if (!external) { 2260 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2261 } 2262 2263 Rect folderLocation = new Rect(); 2264 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation); 2265 target.removeView(v); 2266 2267 FolderIcon fi = 2268 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]); 2269 destInfo.cellX = -1; 2270 destInfo.cellY = -1; 2271 sourceInfo.cellX = -1; 2272 sourceInfo.cellY = -1; 2273 2274 // If the dragView is null, we can't animate 2275 boolean animate = dragView != null; 2276 if (animate) { 2277 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, 2278 postAnimationRunnable); 2279 } else { 2280 fi.addItem(destInfo); 2281 fi.addItem(sourceInfo); 2282 } 2283 return true; 2284 } 2285 return false; 2286 } 2287 2288 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, 2289 float distance, DragObject d, boolean external) { 2290 if (distance > mMaxDistanceForFolderCreation) return false; 2291 2292 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2293 if (!mAddToExistingFolderOnDrop) return false; 2294 mAddToExistingFolderOnDrop = false; 2295 2296 if (dropOverView instanceof FolderIcon) { 2297 FolderIcon fi = (FolderIcon) dropOverView; 2298 if (fi.acceptDrop(d.dragInfo)) { 2299 fi.onDrop(d); 2300 2301 // if the drag started here, we need to remove it from the workspace 2302 if (!external) { 2303 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2304 } 2305 return true; 2306 } 2307 } 2308 return false; 2309 } 2310 2311 public void onDrop(final DragObject d) { 2312 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, 2313 mDragViewVisualCenter); 2314 2315 CellLayout dropTargetLayout = mDropToLayout; 2316 2317 // We want the point to be mapped to the dragTarget. 2318 if (dropTargetLayout != null) { 2319 if (mLauncher.isHotseatLayout(dropTargetLayout)) { 2320 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 2321 } else { 2322 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); 2323 } 2324 } 2325 2326 int snapScreen = -1; 2327 boolean resizeOnDrop = false; 2328 if (d.dragSource != this) { 2329 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], 2330 (int) mDragViewVisualCenter[1] }; 2331 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d); 2332 } else if (mDragInfo != null) { 2333 final View cell = mDragInfo.cell; 2334 2335 Runnable resizeRunnable = null; 2336 if (dropTargetLayout != null && !d.cancelled) { 2337 // Move internally 2338 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); 2339 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 2340 long container = hasMovedIntoHotseat ? 2341 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 2342 LauncherSettings.Favorites.CONTAINER_DESKTOP; 2343 long screenId = (mTargetCell[0] < 0) ? 2344 mDragInfo.screenId : getIdForScreen(dropTargetLayout); 2345 int spanX = mDragInfo != null ? mDragInfo.spanX : 1; 2346 int spanY = mDragInfo != null ? mDragInfo.spanY : 1; 2347 // First we find the cell nearest to point at which the item is 2348 // dropped, without any consideration to whether there is an item there. 2349 2350 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) 2351 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); 2352 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2353 mDragViewVisualCenter[1], mTargetCell); 2354 2355 // If the item being dropped is a shortcut and the nearest drop 2356 // cell also contains a shortcut, then create a folder with the two shortcuts. 2357 if (!mInScrollArea && createUserFolderIfNecessary(cell, container, 2358 dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) { 2359 stripEmptyScreens(); 2360 return; 2361 } 2362 2363 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, 2364 distance, d, false)) { 2365 stripEmptyScreens(); 2366 return; 2367 } 2368 2369 // Aside from the special case where we're dropping a shortcut onto a shortcut, 2370 // we need to find the nearest cell location that is vacant 2371 ItemInfo item = (ItemInfo) d.dragInfo; 2372 int minSpanX = item.spanX; 2373 int minSpanY = item.spanY; 2374 if (item.minSpanX > 0 && item.minSpanY > 0) { 2375 minSpanX = item.minSpanX; 2376 minSpanY = item.minSpanY; 2377 } 2378 2379 int[] resultSpan = new int[2]; 2380 mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], 2381 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, 2382 mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); 2383 2384 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 2385 2386 // if the widget resizes on drop 2387 if (foundCell && (cell instanceof AppWidgetHostView) && 2388 (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) { 2389 resizeOnDrop = true; 2390 item.spanX = resultSpan[0]; 2391 item.spanY = resultSpan[1]; 2392 AppWidgetHostView awhv = (AppWidgetHostView) cell; 2393 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], 2394 resultSpan[1]); 2395 } 2396 2397 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) { 2398 snapScreen = getPageIndexForScreenId(screenId); 2399 snapToPage(snapScreen); 2400 } 2401 2402 if (foundCell) { 2403 final ItemInfo info = (ItemInfo) cell.getTag(); 2404 if (hasMovedLayouts) { 2405 // Reparent the view 2406 getParentCellLayoutForView(cell).removeView(cell); 2407 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], 2408 info.spanX, info.spanY); 2409 } 2410 2411 // update the item's position after drop 2412 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 2413 lp.cellX = lp.tmpCellX = mTargetCell[0]; 2414 lp.cellY = lp.tmpCellY = mTargetCell[1]; 2415 lp.cellHSpan = item.spanX; 2416 lp.cellVSpan = item.spanY; 2417 lp.isLockedToGrid = true; 2418 cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId, 2419 mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); 2420 2421 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && 2422 cell instanceof LauncherAppWidgetHostView) { 2423 final CellLayout cellLayout = dropTargetLayout; 2424 // We post this call so that the widget has a chance to be placed 2425 // in its final location 2426 2427 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; 2428 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo(); 2429 if (pinfo != null && 2430 pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) { 2431 final Runnable addResizeFrame = new Runnable() { 2432 public void run() { 2433 DragLayer dragLayer = mLauncher.getDragLayer(); 2434 dragLayer.addResizeFrame(info, hostView, cellLayout); 2435 } 2436 }; 2437 resizeRunnable = (new Runnable() { 2438 public void run() { 2439 if (!isPageMoving()) { 2440 addResizeFrame.run(); 2441 } else { 2442 mDelayedResizeRunnable = addResizeFrame; 2443 } 2444 } 2445 }); 2446 } 2447 } 2448 2449 LauncherModel.moveItemInDatabase(mLauncher, info, container, screenId, lp.cellX, 2450 lp.cellY); 2451 } else { 2452 // If we can't find a drop location, we return the item to its original position 2453 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 2454 mTargetCell[0] = lp.cellX; 2455 mTargetCell[1] = lp.cellY; 2456 CellLayout layout = (CellLayout) cell.getParent().getParent(); 2457 layout.markCellsAsOccupiedForView(cell); 2458 } 2459 } 2460 2461 final CellLayout parent = (CellLayout) cell.getParent().getParent(); 2462 final Runnable finalResizeRunnable = resizeRunnable; 2463 // Prepare it to be animated into its new position 2464 // This must be called after the view has been re-parented 2465 final Runnable onCompleteRunnable = new Runnable() { 2466 @Override 2467 public void run() { 2468 mAnimatingViewIntoPlace = false; 2469 updateChildrenLayersEnabled(false); 2470 if (finalResizeRunnable != null) { 2471 finalResizeRunnable.run(); 2472 } 2473 stripEmptyScreens(); 2474 } 2475 }; 2476 mAnimatingViewIntoPlace = true; 2477 if (d.dragView.hasDrawn()) { 2478 final ItemInfo info = (ItemInfo) cell.getTag(); 2479 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) { 2480 int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : 2481 ANIMATE_INTO_POSITION_AND_DISAPPEAR; 2482 animateWidgetDrop(info, parent, d.dragView, 2483 onCompleteRunnable, animationType, cell, false); 2484 } else { 2485 int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; 2486 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, 2487 onCompleteRunnable, this); 2488 } 2489 } else { 2490 d.deferDragViewCleanupPostAnimation = false; 2491 cell.setVisibility(VISIBLE); 2492 } 2493 parent.onDropChild(cell); 2494 } 2495 } 2496 2497 public void setFinalScrollForPageChange(int pageIndex) { 2498 CellLayout cl = (CellLayout) getChildAt(pageIndex); 2499 if (cl != null) { 2500 mSavedScrollX = getScrollX(); 2501 mSavedTranslationX = cl.getTranslationX(); 2502 mSavedRotationY = cl.getRotationY(); 2503 final int newX = getScrollForPage(pageIndex); 2504 setScrollX(newX); 2505 cl.setTranslationX(0f); 2506 cl.setRotationY(0f); 2507 } 2508 } 2509 2510 public void resetFinalScrollForPageChange(int pageIndex) { 2511 if (pageIndex >= 0) { 2512 CellLayout cl = (CellLayout) getChildAt(pageIndex); 2513 setScrollX(mSavedScrollX); 2514 cl.setTranslationX(mSavedTranslationX); 2515 cl.setRotationY(mSavedRotationY); 2516 } 2517 } 2518 2519 public void getViewLocationRelativeToSelf(View v, int[] location) { 2520 getLocationInWindow(location); 2521 int x = location[0]; 2522 int y = location[1]; 2523 2524 v.getLocationInWindow(location); 2525 int vX = location[0]; 2526 int vY = location[1]; 2527 2528 location[0] = vX - x; 2529 location[1] = vY - y; 2530 } 2531 2532 public void onDragEnter(DragObject d) { 2533 mDragEnforcer.onDragEnter(); 2534 mCreateUserFolderOnDrop = false; 2535 mAddToExistingFolderOnDrop = false; 2536 2537 mDropToLayout = null; 2538 CellLayout layout = getCurrentDropLayout(); 2539 setCurrentDropLayout(layout); 2540 setCurrentDragOverlappingLayout(layout); 2541 2542 // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we 2543 // don't need to show the outlines 2544 if (LauncherAppState.getInstance().isScreenLarge()) { 2545 showOutlines(); 2546 } 2547 } 2548 2549 static Rect getCellLayoutMetrics(Launcher launcher, int orientation) { 2550 Resources res = launcher.getResources(); 2551 Display display = launcher.getWindowManager().getDefaultDisplay(); 2552 Point smallestSize = new Point(); 2553 Point largestSize = new Point(); 2554 display.getCurrentSizeRange(smallestSize, largestSize); 2555 if (orientation == CellLayout.LANDSCAPE) { 2556 if (mLandscapeCellLayoutMetrics == null) { 2557 int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land); 2558 int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land); 2559 int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land); 2560 int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land); 2561 int width = largestSize.x - paddingLeft - paddingRight; 2562 int height = smallestSize.y - paddingTop - paddingBottom; 2563 mLandscapeCellLayoutMetrics = new Rect(); 2564 CellLayout.getMetrics(mLandscapeCellLayoutMetrics, res, 2565 width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(), 2566 orientation); 2567 } 2568 return mLandscapeCellLayoutMetrics; 2569 } else if (orientation == CellLayout.PORTRAIT) { 2570 if (mPortraitCellLayoutMetrics == null) { 2571 int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land); 2572 int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land); 2573 int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land); 2574 int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land); 2575 int width = smallestSize.x - paddingLeft - paddingRight; 2576 int height = largestSize.y - paddingTop - paddingBottom; 2577 mPortraitCellLayoutMetrics = new Rect(); 2578 CellLayout.getMetrics(mPortraitCellLayoutMetrics, res, 2579 width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(), 2580 orientation); 2581 } 2582 return mPortraitCellLayoutMetrics; 2583 } 2584 return null; 2585 } 2586 2587 public void onDragExit(DragObject d) { 2588 mDragEnforcer.onDragExit(); 2589 2590 // Here we store the final page that will be dropped to, if the workspace in fact 2591 // receives the drop 2592 if (mInScrollArea) { 2593 if (isPageMoving()) { 2594 // If the user drops while the page is scrolling, we should use that page as the 2595 // destination instead of the page that is being hovered over. 2596 mDropToLayout = (CellLayout) getPageAt(getNextPage()); 2597 } else { 2598 mDropToLayout = mDragOverlappingLayout; 2599 } 2600 } else { 2601 mDropToLayout = mDragTargetLayout; 2602 } 2603 2604 if (mDragMode == DRAG_MODE_CREATE_FOLDER) { 2605 mCreateUserFolderOnDrop = true; 2606 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) { 2607 mAddToExistingFolderOnDrop = true; 2608 } 2609 2610 // Reset the scroll area and previous drag target 2611 onResetScrollArea(); 2612 setCurrentDropLayout(null); 2613 setCurrentDragOverlappingLayout(null); 2614 2615 mSpringLoadedDragController.cancel(); 2616 2617 if (!mIsPageMoving) { 2618 hideOutlines(); 2619 } 2620 } 2621 2622 void setCurrentDropLayout(CellLayout layout) { 2623 if (mDragTargetLayout != null) { 2624 mDragTargetLayout.revertTempState(); 2625 mDragTargetLayout.onDragExit(); 2626 } 2627 mDragTargetLayout = layout; 2628 if (mDragTargetLayout != null) { 2629 mDragTargetLayout.onDragEnter(); 2630 } 2631 cleanupReorder(true); 2632 cleanupFolderCreation(); 2633 setCurrentDropOverCell(-1, -1); 2634 } 2635 2636 void setCurrentDragOverlappingLayout(CellLayout layout) { 2637 if (mDragOverlappingLayout != null) { 2638 mDragOverlappingLayout.setIsDragOverlapping(false); 2639 } 2640 mDragOverlappingLayout = layout; 2641 if (mDragOverlappingLayout != null) { 2642 mDragOverlappingLayout.setIsDragOverlapping(true); 2643 } 2644 invalidate(); 2645 } 2646 2647 void setCurrentDropOverCell(int x, int y) { 2648 if (x != mDragOverX || y != mDragOverY) { 2649 mDragOverX = x; 2650 mDragOverY = y; 2651 setDragMode(DRAG_MODE_NONE); 2652 } 2653 } 2654 2655 void setDragMode(int dragMode) { 2656 if (dragMode != mDragMode) { 2657 if (dragMode == DRAG_MODE_NONE) { 2658 cleanupAddToFolder(); 2659 // We don't want to cancel the re-order alarm every time the target cell changes 2660 // as this feels to slow / unresponsive. 2661 cleanupReorder(false); 2662 cleanupFolderCreation(); 2663 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) { 2664 cleanupReorder(true); 2665 cleanupFolderCreation(); 2666 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) { 2667 cleanupAddToFolder(); 2668 cleanupReorder(true); 2669 } else if (dragMode == DRAG_MODE_REORDER) { 2670 cleanupAddToFolder(); 2671 cleanupFolderCreation(); 2672 } 2673 mDragMode = dragMode; 2674 } 2675 } 2676 2677 private void cleanupFolderCreation() { 2678 if (mDragFolderRingAnimator != null) { 2679 mDragFolderRingAnimator.animateToNaturalState(); 2680 } 2681 mFolderCreationAlarm.cancelAlarm(); 2682 } 2683 2684 private void cleanupAddToFolder() { 2685 if (mDragOverFolderIcon != null) { 2686 mDragOverFolderIcon.onDragExit(null); 2687 mDragOverFolderIcon = null; 2688 } 2689 } 2690 2691 private void cleanupReorder(boolean cancelAlarm) { 2692 // Any pending reorders are canceled 2693 if (cancelAlarm) { 2694 mReorderAlarm.cancelAlarm(); 2695 } 2696 mLastReorderX = -1; 2697 mLastReorderY = -1; 2698 } 2699 2700 /* 2701 * 2702 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 2703 * coordinate space. The argument xy is modified with the return result. 2704 * 2705 * if cachedInverseMatrix is not null, this method will just use that matrix instead of 2706 * computing it itself; we use this to avoid redundant matrix inversions in 2707 * findMatchingPageForDragOver 2708 * 2709 */ 2710 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { 2711 xy[0] = xy[0] - v.getLeft(); 2712 xy[1] = xy[1] - v.getTop(); 2713 } 2714 2715 boolean isPointInSelfOverHotseat(int x, int y, Rect r) { 2716 if (r == null) { 2717 r = new Rect(); 2718 } 2719 mTempPt[0] = x; 2720 mTempPt[1] = y; 2721 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); 2722 mLauncher.getHotseat().getHitRect(r); 2723 if (r.contains(mTempPt[0], mTempPt[1])) { 2724 return true; 2725 } 2726 return false; 2727 } 2728 2729 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { 2730 mTempPt[0] = (int) xy[0]; 2731 mTempPt[1] = (int) xy[1]; 2732 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); 2733 mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt); 2734 2735 xy[0] = mTempPt[0]; 2736 xy[1] = mTempPt[1]; 2737 } 2738 2739 /* 2740 * 2741 * Convert the 2D coordinate xy from this CellLayout's coordinate space to 2742 * the parent View's coordinate space. The argument xy is modified with the return result. 2743 * 2744 */ 2745 void mapPointFromChildToSelf(View v, float[] xy) { 2746 xy[0] += v.getLeft(); 2747 xy[1] += v.getTop(); 2748 } 2749 2750 static private float squaredDistance(float[] point1, float[] point2) { 2751 float distanceX = point1[0] - point2[0]; 2752 float distanceY = point2[1] - point2[1]; 2753 return distanceX * distanceX + distanceY * distanceY; 2754 } 2755 2756 /* 2757 * 2758 * This method returns the CellLayout that is currently being dragged to. In order to drag 2759 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second 2760 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one 2761 * 2762 * Return null if no CellLayout is currently being dragged over 2763 * 2764 */ 2765 private CellLayout findMatchingPageForDragOver( 2766 DragView dragView, float originX, float originY, boolean exact) { 2767 // We loop through all the screens (ie CellLayouts) and see which ones overlap 2768 // with the item being dragged and then choose the one that's closest to the touch point 2769 final int screenCount = getChildCount(); 2770 CellLayout bestMatchingScreen = null; 2771 float smallestDistSoFar = Float.MAX_VALUE; 2772 2773 for (int i = 0; i < screenCount; i++) { 2774 CellLayout cl = (CellLayout) getChildAt(i); 2775 2776 final float[] touchXy = {originX, originY}; 2777 // Transform the touch coordinates to the CellLayout's local coordinates 2778 // If the touch point is within the bounds of the cell layout, we can return immediately 2779 cl.getMatrix().invert(mTempInverseMatrix); 2780 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); 2781 2782 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 2783 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 2784 return cl; 2785 } 2786 2787 if (!exact) { 2788 // Get the center of the cell layout in screen coordinates 2789 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; 2790 cellLayoutCenter[0] = cl.getWidth()/2; 2791 cellLayoutCenter[1] = cl.getHeight()/2; 2792 mapPointFromChildToSelf(cl, cellLayoutCenter); 2793 2794 touchXy[0] = originX; 2795 touchXy[1] = originY; 2796 2797 // Calculate the distance between the center of the CellLayout 2798 // and the touch point 2799 float dist = squaredDistance(touchXy, cellLayoutCenter); 2800 2801 if (dist < smallestDistSoFar) { 2802 smallestDistSoFar = dist; 2803 bestMatchingScreen = cl; 2804 } 2805 } 2806 } 2807 return bestMatchingScreen; 2808 } 2809 2810 // This is used to compute the visual center of the dragView. This point is then 2811 // used to visualize drop locations and determine where to drop an item. The idea is that 2812 // the visual center represents the user's interpretation of where the item is, and hence 2813 // is the appropriate point to use when determining drop location. 2814 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, 2815 DragView dragView, float[] recycle) { 2816 float res[]; 2817 if (recycle == null) { 2818 res = new float[2]; 2819 } else { 2820 res = recycle; 2821 } 2822 2823 // First off, the drag view has been shifted in a way that is not represented in the 2824 // x and y values or the x/yOffsets. Here we account for that shift. 2825 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX); 2826 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); 2827 2828 // These represent the visual top and left of drag view if a dragRect was provided. 2829 // If a dragRect was not provided, then they correspond to the actual view left and 2830 // top, as the dragRect is in that case taken to be the entire dragView. 2831 // R.dimen.dragViewOffsetY. 2832 int left = x - xOffset; 2833 int top = y - yOffset; 2834 2835 // In order to find the visual center, we shift by half the dragRect 2836 res[0] = left + dragView.getDragRegion().width() / 2; 2837 res[1] = top + dragView.getDragRegion().height() / 2; 2838 2839 return res; 2840 } 2841 2842 private boolean isDragWidget(DragObject d) { 2843 return (d.dragInfo instanceof LauncherAppWidgetInfo || 2844 d.dragInfo instanceof PendingAddWidgetInfo); 2845 } 2846 private boolean isExternalDragWidget(DragObject d) { 2847 return d.dragSource != this && isDragWidget(d); 2848 } 2849 2850 public void onDragOver(DragObject d) { 2851 // Skip drag over events while we are dragging over side pages 2852 if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return; 2853 2854 Rect r = new Rect(); 2855 CellLayout layout = null; 2856 ItemInfo item = (ItemInfo) d.dragInfo; 2857 2858 // Ensure that we have proper spans for the item that we are dropping 2859 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); 2860 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, 2861 d.dragView, mDragViewVisualCenter); 2862 2863 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 2864 // Identify whether we have dragged over a side page 2865 if (isSmall()) { 2866 if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) { 2867 if (isPointInSelfOverHotseat(d.x, d.y, r)) { 2868 layout = mLauncher.getHotseat().getLayout(); 2869 } 2870 } 2871 if (layout == null) { 2872 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false); 2873 } 2874 if (layout != mDragTargetLayout) { 2875 setCurrentDropLayout(layout); 2876 setCurrentDragOverlappingLayout(layout); 2877 2878 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED); 2879 if (isInSpringLoadedMode) { 2880 if (mLauncher.isHotseatLayout(layout)) { 2881 mSpringLoadedDragController.cancel(); 2882 } else { 2883 mSpringLoadedDragController.setAlarm(mDragTargetLayout); 2884 } 2885 } 2886 } 2887 } else { 2888 // Test to see if we are over the hotseat otherwise just use the current page 2889 if (mLauncher.getHotseat() != null && !isDragWidget(d)) { 2890 if (isPointInSelfOverHotseat(d.x, d.y, r)) { 2891 layout = mLauncher.getHotseat().getLayout(); 2892 } 2893 } 2894 if (layout == null) { 2895 layout = getCurrentDropLayout(); 2896 } 2897 if (layout != mDragTargetLayout) { 2898 setCurrentDropLayout(layout); 2899 setCurrentDragOverlappingLayout(layout); 2900 } 2901 } 2902 2903 // Handle the drag over 2904 if (mDragTargetLayout != null) { 2905 // We want the point to be mapped to the dragTarget. 2906 if (mLauncher.isHotseatLayout(mDragTargetLayout)) { 2907 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 2908 } else { 2909 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null); 2910 } 2911 2912 ItemInfo info = (ItemInfo) d.dragInfo; 2913 2914 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2915 (int) mDragViewVisualCenter[1], item.spanX, item.spanY, 2916 mDragTargetLayout, mTargetCell); 2917 2918 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]); 2919 2920 float targetCellDistance = mDragTargetLayout.getDistanceFromCell( 2921 mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); 2922 2923 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], 2924 mTargetCell[1]); 2925 2926 manageFolderFeedback(info, mDragTargetLayout, mTargetCell, 2927 targetCellDistance, dragOverView); 2928 2929 int minSpanX = item.spanX; 2930 int minSpanY = item.spanY; 2931 if (item.minSpanX > 0 && item.minSpanY > 0) { 2932 minSpanX = item.minSpanX; 2933 minSpanY = item.minSpanY; 2934 } 2935 2936 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) 2937 mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, 2938 item.spanY, child, mTargetCell); 2939 2940 if (!nearestDropOccupied) { 2941 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, 2942 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], 2943 mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, 2944 d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion()); 2945 } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) 2946 && !mReorderAlarm.alarmPending() && (mLastReorderX != mTargetCell[0] || 2947 mLastReorderY != mTargetCell[1])) { 2948 2949 // Otherwise, if we aren't adding to or creating a folder and there's no pending 2950 // reorder, then we schedule a reorder 2951 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, 2952 minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child); 2953 mReorderAlarm.setOnAlarmListener(listener); 2954 mReorderAlarm.setAlarm(REORDER_TIMEOUT); 2955 } 2956 2957 if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER || 2958 !nearestDropOccupied) { 2959 if (mDragTargetLayout != null) { 2960 mDragTargetLayout.revertTempState(); 2961 } 2962 } 2963 } 2964 } 2965 2966 private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout, 2967 int[] targetCell, float distance, View dragOverView) { 2968 boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance, 2969 false); 2970 2971 if (mDragMode == DRAG_MODE_NONE && userFolderPending && 2972 !mFolderCreationAlarm.alarmPending()) { 2973 mFolderCreationAlarm.setOnAlarmListener(new 2974 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1])); 2975 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); 2976 return; 2977 } 2978 2979 boolean willAddToFolder = 2980 willAddToExistingUserFolder(info, targetLayout, targetCell, distance); 2981 2982 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) { 2983 mDragOverFolderIcon = ((FolderIcon) dragOverView); 2984 mDragOverFolderIcon.onDragEnter(info); 2985 if (targetLayout != null) { 2986 targetLayout.clearDragOutlines(); 2987 } 2988 setDragMode(DRAG_MODE_ADD_TO_FOLDER); 2989 return; 2990 } 2991 2992 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) { 2993 setDragMode(DRAG_MODE_NONE); 2994 } 2995 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) { 2996 setDragMode(DRAG_MODE_NONE); 2997 } 2998 2999 return; 3000 } 3001 3002 class FolderCreationAlarmListener implements OnAlarmListener { 3003 CellLayout layout; 3004 int cellX; 3005 int cellY; 3006 3007 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { 3008 this.layout = layout; 3009 this.cellX = cellX; 3010 this.cellY = cellY; 3011 } 3012 3013 public void onAlarm(Alarm alarm) { 3014 if (mDragFolderRingAnimator == null) { 3015 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null); 3016 } 3017 mDragFolderRingAnimator.setCell(cellX, cellY); 3018 mDragFolderRingAnimator.setCellLayout(layout); 3019 mDragFolderRingAnimator.animateToAcceptState(); 3020 layout.showFolderAccept(mDragFolderRingAnimator); 3021 layout.clearDragOutlines(); 3022 setDragMode(DRAG_MODE_CREATE_FOLDER); 3023 } 3024 } 3025 3026 class ReorderAlarmListener implements OnAlarmListener { 3027 float[] dragViewCenter; 3028 int minSpanX, minSpanY, spanX, spanY; 3029 DragView dragView; 3030 View child; 3031 3032 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, 3033 int spanY, DragView dragView, View child) { 3034 this.dragViewCenter = dragViewCenter; 3035 this.minSpanX = minSpanX; 3036 this.minSpanY = minSpanY; 3037 this.spanX = spanX; 3038 this.spanY = spanY; 3039 this.child = child; 3040 this.dragView = dragView; 3041 } 3042 3043 public void onAlarm(Alarm alarm) { 3044 int[] resultSpan = new int[2]; 3045 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 3046 (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell); 3047 mLastReorderX = mTargetCell[0]; 3048 mLastReorderY = mTargetCell[1]; 3049 3050 mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0], 3051 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 3052 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER); 3053 3054 if (mTargetCell[0] < 0 || mTargetCell[1] < 0) { 3055 mDragTargetLayout.revertTempState(); 3056 } else { 3057 setDragMode(DRAG_MODE_REORDER); 3058 } 3059 3060 boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; 3061 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, 3062 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], 3063 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, 3064 dragView.getDragVisualizeOffset(), dragView.getDragRegion()); 3065 } 3066 } 3067 3068 @Override 3069 public void getHitRectRelativeToDragLayer(Rect outRect) { 3070 // We want the workspace to have the whole area of the display (it will find the correct 3071 // cell layout to drop to in the existing drag/drop logic. 3072 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect); 3073 } 3074 3075 /** 3076 * Add the item specified by dragInfo to the given layout. 3077 * @return true if successful 3078 */ 3079 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) { 3080 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) { 3081 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false); 3082 return true; 3083 } 3084 mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout)); 3085 return false; 3086 } 3087 3088 private void onDropExternal(int[] touchXY, Object dragInfo, 3089 CellLayout cellLayout, boolean insertAtFirst) { 3090 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null); 3091 } 3092 3093 /** 3094 * Drop an item that didn't originate on one of the workspace screens. 3095 * It may have come from Launcher (e.g. from all apps or customize), or it may have 3096 * come from another app altogether. 3097 * 3098 * NOTE: This can also be called when we are outside of a drag event, when we want 3099 * to add an item to one of the workspace screens. 3100 */ 3101 private void onDropExternal(final int[] touchXY, final Object dragInfo, 3102 final CellLayout cellLayout, boolean insertAtFirst, DragObject d) { 3103 final Runnable exitSpringLoadedRunnable = new Runnable() { 3104 @Override 3105 public void run() { 3106 mLauncher.exitSpringLoadedDragModeDelayed(true, false, null); 3107 } 3108 }; 3109 3110 ItemInfo info = (ItemInfo) dragInfo; 3111 int spanX = info.spanX; 3112 int spanY = info.spanY; 3113 if (mDragInfo != null) { 3114 spanX = mDragInfo.spanX; 3115 spanY = mDragInfo.spanY; 3116 } 3117 3118 final long container = mLauncher.isHotseatLayout(cellLayout) ? 3119 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 3120 LauncherSettings.Favorites.CONTAINER_DESKTOP; 3121 final long screenId = getIdForScreen(cellLayout); 3122 if (!mLauncher.isHotseatLayout(cellLayout) 3123 && screenId != getScreenIdForPageIndex(mCurrentPage) 3124 && mState != State.SPRING_LOADED) { 3125 snapToScreenId(screenId, null); 3126 } 3127 3128 if (info instanceof PendingAddItemInfo) { 3129 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo; 3130 3131 boolean findNearestVacantCell = true; 3132 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 3133 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, 3134 cellLayout, mTargetCell); 3135 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 3136 mDragViewVisualCenter[1], mTargetCell); 3137 if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell, 3138 distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo, 3139 cellLayout, mTargetCell, distance)) { 3140 findNearestVacantCell = false; 3141 } 3142 } 3143 3144 final ItemInfo item = (ItemInfo) d.dragInfo; 3145 boolean updateWidgetSize = false; 3146 if (findNearestVacantCell) { 3147 int minSpanX = item.spanX; 3148 int minSpanY = item.spanY; 3149 if (item.minSpanX > 0 && item.minSpanY > 0) { 3150 minSpanX = item.minSpanX; 3151 minSpanY = item.minSpanY; 3152 } 3153 int[] resultSpan = new int[2]; 3154 mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], 3155 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY, 3156 null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL); 3157 3158 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) { 3159 updateWidgetSize = true; 3160 } 3161 item.spanX = resultSpan[0]; 3162 item.spanY = resultSpan[1]; 3163 } 3164 3165 Runnable onAnimationCompleteRunnable = new Runnable() { 3166 @Override 3167 public void run() { 3168 // When dragging and dropping from customization tray, we deal with creating 3169 // widgets/shortcuts/folders in a slightly different way 3170 switch (pendingInfo.itemType) { 3171 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 3172 int span[] = new int[2]; 3173 span[0] = item.spanX; 3174 span[1] = item.spanY; 3175 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo, 3176 container, screenId, mTargetCell, span, null); 3177 break; 3178 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 3179 mLauncher.processShortcutFromDrop(pendingInfo.componentName, 3180 container, screenId, mTargetCell, null); 3181 break; 3182 default: 3183 throw new IllegalStateException("Unknown item type: " + 3184 pendingInfo.itemType); 3185 } 3186 } 3187 }; 3188 View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET 3189 ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; 3190 3191 if (finalView instanceof AppWidgetHostView && updateWidgetSize) { 3192 AppWidgetHostView awhv = (AppWidgetHostView) finalView; 3193 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX, 3194 item.spanY); 3195 } 3196 3197 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR; 3198 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && 3199 ((PendingAddWidgetInfo) pendingInfo).info.configure != null) { 3200 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN; 3201 } 3202 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, 3203 animationStyle, finalView, true); 3204 } else { 3205 // This is for other drag/drop cases, like dragging from All Apps 3206 View view = null; 3207 3208 switch (info.itemType) { 3209 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 3210 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 3211 if (info.container == NO_ID && info instanceof ApplicationInfo) { 3212 // Came from all apps -- make a copy 3213 info = new ShortcutInfo((ApplicationInfo) info); 3214 } 3215 view = mLauncher.createShortcut(R.layout.application, cellLayout, 3216 (ShortcutInfo) info); 3217 break; 3218 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 3219 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, 3220 (FolderInfo) info, mIconCache); 3221 break; 3222 default: 3223 throw new IllegalStateException("Unknown item type: " + info.itemType); 3224 } 3225 3226 // First we find the cell nearest to point at which the item is 3227 // dropped, without any consideration to whether there is an item there. 3228 if (touchXY != null) { 3229 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, 3230 cellLayout, mTargetCell); 3231 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 3232 mDragViewVisualCenter[1], mTargetCell); 3233 d.postAnimationRunnable = exitSpringLoadedRunnable; 3234 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, 3235 true, d.dragView, d.postAnimationRunnable)) { 3236 return; 3237 } 3238 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, 3239 true)) { 3240 return; 3241 } 3242 } 3243 3244 if (touchXY != null) { 3245 // when dragging and dropping, just find the closest free spot 3246 mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], 3247 (int) mDragViewVisualCenter[1], 1, 1, 1, 1, 3248 null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL); 3249 } else { 3250 cellLayout.findCellForSpan(mTargetCell, 1, 1); 3251 } 3252 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX, 3253 info.spanY, insertAtFirst); 3254 cellLayout.onDropChild(view); 3255 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 3256 cellLayout.getShortcutsAndWidgets().measureChild(view); 3257 3258 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, 3259 lp.cellX, lp.cellY); 3260 3261 if (d.dragView != null) { 3262 // We wrap the animation call in the temporary set and reset of the current 3263 // cellLayout to its final transform -- this means we animate the drag view to 3264 // the correct final location. 3265 setFinalTransitionTransform(cellLayout); 3266 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, 3267 exitSpringLoadedRunnable); 3268 resetTransitionTransform(cellLayout); 3269 } 3270 } 3271 } 3272 3273 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { 3274 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX, 3275 widgetInfo.spanY, widgetInfo, false); 3276 int visibility = layout.getVisibility(); 3277 layout.setVisibility(VISIBLE); 3278 3279 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY); 3280 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY); 3281 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], 3282 Bitmap.Config.ARGB_8888); 3283 Canvas c = new Canvas(b); 3284 3285 layout.measure(width, height); 3286 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]); 3287 layout.draw(c); 3288 c.setBitmap(null); 3289 layout.setVisibility(visibility); 3290 return b; 3291 } 3292 3293 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY, 3294 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, 3295 boolean external, boolean scale) { 3296 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final 3297 // location and size on the home screen. 3298 int spanX = info.spanX; 3299 int spanY = info.spanY; 3300 3301 Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY); 3302 loc[0] = r.left; 3303 loc[1] = r.top; 3304 3305 setFinalTransitionTransform(layout); 3306 float cellLayoutScale = 3307 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true); 3308 resetTransitionTransform(layout); 3309 3310 float dragViewScaleX; 3311 float dragViewScaleY; 3312 if (scale) { 3313 dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); 3314 dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight(); 3315 } else { 3316 dragViewScaleX = 1f; 3317 dragViewScaleY = 1f; 3318 } 3319 3320 // The animation will scale the dragView about its center, so we need to center about 3321 // the final location. 3322 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2; 3323 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2; 3324 3325 scaleXY[0] = dragViewScaleX * cellLayoutScale; 3326 scaleXY[1] = dragViewScaleY * cellLayoutScale; 3327 } 3328 3329 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView, 3330 final Runnable onCompleteRunnable, int animationType, final View finalView, 3331 boolean external) { 3332 Rect from = new Rect(); 3333 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from); 3334 3335 int[] finalPos = new int[2]; 3336 float scaleXY[] = new float[2]; 3337 boolean scalePreview = !(info instanceof PendingAddShortcutInfo); 3338 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell, 3339 external, scalePreview); 3340 3341 Resources res = mLauncher.getResources(); 3342 int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200; 3343 3344 // In the case where we've prebound the widget, we remove it from the DragLayer 3345 if (finalView instanceof AppWidgetHostView && external) { 3346 Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView"); 3347 mLauncher.getDragLayer().removeView(finalView); 3348 } 3349 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) { 3350 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView); 3351 dragView.setCrossFadeBitmap(crossFadeBitmap); 3352 dragView.crossFade((int) (duration * 0.8f)); 3353 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) { 3354 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]); 3355 } 3356 3357 DragLayer dragLayer = mLauncher.getDragLayer(); 3358 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) { 3359 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f, 3360 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration); 3361 } else { 3362 int endStyle; 3363 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) { 3364 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE; 3365 } else { 3366 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;; 3367 } 3368 3369 Runnable onComplete = new Runnable() { 3370 @Override 3371 public void run() { 3372 if (finalView != null) { 3373 finalView.setVisibility(VISIBLE); 3374 } 3375 if (onCompleteRunnable != null) { 3376 onCompleteRunnable.run(); 3377 } 3378 } 3379 }; 3380 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0], 3381 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle, 3382 duration, this); 3383 } 3384 } 3385 3386 public void setFinalTransitionTransform(CellLayout layout) { 3387 if (isSwitchingState()) { 3388 mCurrentScale = getScaleX(); 3389 setScaleX(mNewScale); 3390 setScaleY(mNewScale); 3391 } 3392 } 3393 public void resetTransitionTransform(CellLayout layout) { 3394 if (isSwitchingState()) { 3395 setScaleX(mCurrentScale); 3396 setScaleY(mCurrentScale); 3397 } 3398 } 3399 3400 /** 3401 * Return the current {@link CellLayout}, correctly picking the destination 3402 * screen while a scroll is in progress. 3403 */ 3404 public CellLayout getCurrentDropLayout() { 3405 return (CellLayout) getChildAt(getNextPage()); 3406 } 3407 3408 /** 3409 * Return the current CellInfo describing our current drag; this method exists 3410 * so that Launcher can sync this object with the correct info when the activity is created/ 3411 * destroyed 3412 * 3413 */ 3414 public CellLayout.CellInfo getDragInfo() { 3415 return mDragInfo; 3416 } 3417 3418 /** 3419 * Calculate the nearest cell where the given object would be dropped. 3420 * 3421 * pixelX and pixelY should be in the coordinate system of layout 3422 */ 3423 private int[] findNearestArea(int pixelX, int pixelY, 3424 int spanX, int spanY, CellLayout layout, int[] recycle) { 3425 return layout.findNearestArea( 3426 pixelX, pixelY, spanX, spanY, recycle); 3427 } 3428 3429 void setup(DragController dragController) { 3430 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); 3431 mDragController = dragController; 3432 3433 // hardware layers on children are enabled on startup, but should be disabled until 3434 // needed 3435 updateChildrenLayersEnabled(false); 3436 setWallpaperDimension(); 3437 } 3438 3439 /** 3440 * Called at the end of a drag which originated on the workspace. 3441 */ 3442 public void onDropCompleted(final View target, final DragObject d, 3443 final boolean isFlingToDelete, final boolean success) { 3444 if (mDeferDropAfterUninstall) { 3445 mDeferredAction = new Runnable() { 3446 public void run() { 3447 onDropCompleted(target, d, isFlingToDelete, success); 3448 mDeferredAction = null; 3449 } 3450 }; 3451 return; 3452 } 3453 3454 boolean beingCalledAfterUninstall = mDeferredAction != null; 3455 3456 if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) { 3457 if (target != this && mDragInfo != null) { 3458 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 3459 if (mDragInfo.cell instanceof DropTarget) { 3460 mDragController.removeDropTarget((DropTarget) mDragInfo.cell); 3461 } 3462 // If we move the item to anything not on the Workspace, check if any empty 3463 // screens need to be removed. If we dropped back on the workspace, this will 3464 // be done post drop animation. 3465 stripEmptyScreens(); 3466 } 3467 } else if (mDragInfo != null) { 3468 CellLayout cellLayout; 3469 if (mLauncher.isHotseatLayout(target)) { 3470 cellLayout = mLauncher.getHotseat().getLayout(); 3471 } else { 3472 cellLayout = getScreenWithId(mDragInfo.screenId); 3473 } 3474 cellLayout.onDropChild(mDragInfo.cell); 3475 } 3476 if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful)) 3477 && mDragInfo.cell != null) { 3478 mDragInfo.cell.setVisibility(VISIBLE); 3479 } 3480 mDragOutline = null; 3481 mDragInfo = null; 3482 } 3483 3484 public void deferCompleteDropAfterUninstallActivity() { 3485 mDeferDropAfterUninstall = true; 3486 } 3487 3488 /// maybe move this into a smaller part 3489 public void onUninstallActivityReturned(boolean success) { 3490 mDeferDropAfterUninstall = false; 3491 mUninstallSuccessful = success; 3492 if (mDeferredAction != null) { 3493 mDeferredAction.run(); 3494 } 3495 } 3496 3497 void updateItemLocationsInDatabase(CellLayout cl) { 3498 int count = cl.getShortcutsAndWidgets().getChildCount(); 3499 3500 long screenId = getIdForScreen(cl); 3501 int container = Favorites.CONTAINER_DESKTOP; 3502 3503 if (mLauncher.isHotseatLayout(cl)) { 3504 screenId = -1; 3505 container = Favorites.CONTAINER_HOTSEAT; 3506 } 3507 3508 for (int i = 0; i < count; i++) { 3509 View v = cl.getShortcutsAndWidgets().getChildAt(i); 3510 ItemInfo info = (ItemInfo) v.getTag(); 3511 // Null check required as the AllApps button doesn't have an item info 3512 if (info != null && info.requiresDbUpdate) { 3513 info.requiresDbUpdate = false; 3514 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX, 3515 info.cellY, info.spanX, info.spanY); 3516 } 3517 } 3518 } 3519 3520 ArrayList<ComponentName> stripDuplicateApps() { 3521 ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>(); 3522 stripDuplicateApps((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents); 3523 int count = getChildCount(); 3524 for (int i = 0; i < count; i++) { 3525 CellLayout cl = (CellLayout) getChildAt(i); 3526 stripDuplicateApps(cl, uniqueIntents); 3527 } 3528 return uniqueIntents; 3529 } 3530 3531 void stripDuplicateApps(CellLayout cl, ArrayList<ComponentName> uniqueIntents) { 3532 int count = cl.getShortcutsAndWidgets().getChildCount(); 3533 3534 ArrayList<View> children = new ArrayList<View>(); 3535 for (int i = 0; i < count; i++) { 3536 View v = cl.getShortcutsAndWidgets().getChildAt(i); 3537 children.add(v); 3538 } 3539 3540 for (int i = 0; i < count; i++) { 3541 View v = children.get(i); 3542 ItemInfo info = (ItemInfo) v.getTag(); 3543 // Null check required as the AllApps button doesn't have an item info 3544 if (info instanceof ShortcutInfo) { 3545 ShortcutInfo si = (ShortcutInfo) info; 3546 ComponentName cn = si.intent.getComponent(); 3547 3548 if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 3549 continue; 3550 } 3551 3552 if (!uniqueIntents.contains(cn)) { 3553 uniqueIntents.add(cn); 3554 } else { 3555 cl.removeViewInLayout(v); 3556 LauncherModel.deleteItemFromDatabase(mLauncher, si); 3557 } 3558 } 3559 if (v instanceof FolderIcon) { 3560 FolderIcon fi = (FolderIcon) v; 3561 ArrayList<View> items = fi.getFolder().getItemsInReadingOrder(); 3562 for (int j = 0; j < items.size(); j++) { 3563 if (items.get(j).getTag() instanceof ShortcutInfo) { 3564 ShortcutInfo si = (ShortcutInfo) items.get(j).getTag(); 3565 ComponentName cn = si.intent.getComponent(); 3566 3567 if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 3568 continue; 3569 } 3570 if (!uniqueIntents.contains(cn)) { 3571 uniqueIntents.add(cn); 3572 } else { 3573 fi.getFolderInfo().remove(si); 3574 LauncherModel.deleteItemFromDatabase(mLauncher, si); 3575 } 3576 } 3577 } 3578 } 3579 } 3580 } 3581 3582 void saveWorkspaceToDb() { 3583 saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout()); 3584 int count = getChildCount(); 3585 for (int i = 0; i < count; i++) { 3586 CellLayout cl = (CellLayout) getChildAt(i); 3587 saveWorkspaceScreenToDb(cl); 3588 } 3589 } 3590 3591 void saveWorkspaceScreenToDb(CellLayout cl) { 3592 int count = cl.getShortcutsAndWidgets().getChildCount(); 3593 3594 long screenId = getIdForScreen(cl); 3595 int container = Favorites.CONTAINER_DESKTOP; 3596 3597 Hotseat hotseat = mLauncher.getHotseat(); 3598 if (mLauncher.isHotseatLayout(cl)) { 3599 screenId = -1; 3600 container = Favorites.CONTAINER_HOTSEAT; 3601 } 3602 3603 for (int i = 0; i < count; i++) { 3604 View v = cl.getShortcutsAndWidgets().getChildAt(i); 3605 ItemInfo info = (ItemInfo) v.getTag(); 3606 // Null check required as the AllApps button doesn't have an item info 3607 if (info != null) { 3608 int cellX = info.cellX; 3609 int cellY = info.cellY; 3610 if (container == Favorites.CONTAINER_HOTSEAT) { 3611 cellX = hotseat.getCellXFromOrder((int) info.screenId); 3612 cellY = hotseat.getCellYFromOrder((int) info.screenId); 3613 } 3614 LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX, 3615 cellY, false); 3616 } 3617 if (v instanceof FolderIcon) { 3618 FolderIcon fi = (FolderIcon) v; 3619 fi.getFolder().addItemLocationsInDatabase(); 3620 } 3621 } 3622 } 3623 3624 @Override 3625 public boolean supportsFlingToDelete() { 3626 return true; 3627 } 3628 3629 @Override 3630 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { 3631 // Do nothing 3632 } 3633 3634 @Override 3635 public void onFlingToDeleteCompleted() { 3636 // Do nothing 3637 } 3638 3639 public boolean isDropEnabled() { 3640 return true; 3641 } 3642 3643 @Override 3644 protected void onRestoreInstanceState(Parcelable state) { 3645 super.onRestoreInstanceState(state); 3646 Launcher.setScreen(mCurrentPage); 3647 } 3648 3649 @Override 3650 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 3651 // We don't dispatch restoreInstanceState to our children using this code path. 3652 // Some pages will be restored immediately as their items are bound immediately, and 3653 // others we will need to wait until after their items are bound. 3654 mSavedStates = container; 3655 } 3656 3657 public void restoreInstanceStateForChild(int child) { 3658 if (mSavedStates != null) { 3659 mRestoredPages.add(child); 3660 CellLayout cl = (CellLayout) getChildAt(child); 3661 cl.restoreInstanceState(mSavedStates); 3662 } 3663 } 3664 3665 public void restoreInstanceStateForRemainingPages() { 3666 int count = getChildCount(); 3667 for (int i = 0; i < count; i++) { 3668 if (!mRestoredPages.contains(i)) { 3669 restoreInstanceStateForChild(i); 3670 } 3671 } 3672 mRestoredPages.clear(); 3673 } 3674 3675 @Override 3676 public void scrollLeft() { 3677 if (!isSmall() && !mIsSwitchingState) { 3678 super.scrollLeft(); 3679 } 3680 Folder openFolder = getOpenFolder(); 3681 if (openFolder != null) { 3682 openFolder.completeDragExit(); 3683 } 3684 } 3685 3686 @Override 3687 public void scrollRight() { 3688 if (!isSmall() && !mIsSwitchingState) { 3689 super.scrollRight(); 3690 } 3691 Folder openFolder = getOpenFolder(); 3692 if (openFolder != null) { 3693 openFolder.completeDragExit(); 3694 } 3695 } 3696 3697 @Override 3698 public boolean onEnterScrollArea(int x, int y, int direction) { 3699 // Ignore the scroll area if we are dragging over the hot seat 3700 boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext()); 3701 if (mLauncher.getHotseat() != null && isPortrait) { 3702 Rect r = new Rect(); 3703 mLauncher.getHotseat().getHitRect(r); 3704 if (r.contains(x, y)) { 3705 return false; 3706 } 3707 } 3708 3709 boolean result = false; 3710 if (!isSmall() && !mIsSwitchingState) { 3711 mInScrollArea = true; 3712 3713 final int page = getNextPage() + 3714 (direction == DragController.SCROLL_LEFT ? -1 : 1); 3715 3716 // We always want to exit the current layout to ensure parity of enter / exit 3717 setCurrentDropLayout(null); 3718 3719 if (0 <= page && page < getChildCount()) { 3720 CellLayout layout = (CellLayout) getChildAt(page); 3721 setCurrentDragOverlappingLayout(layout); 3722 3723 // Workspace is responsible for drawing the edge glow on adjacent pages, 3724 // so we need to redraw the workspace when this may have changed. 3725 invalidate(); 3726 result = true; 3727 } 3728 } 3729 return result; 3730 } 3731 3732 @Override 3733 public boolean onExitScrollArea() { 3734 boolean result = false; 3735 if (mInScrollArea) { 3736 invalidate(); 3737 CellLayout layout = getCurrentDropLayout(); 3738 setCurrentDropLayout(layout); 3739 setCurrentDragOverlappingLayout(layout); 3740 3741 result = true; 3742 mInScrollArea = false; 3743 } 3744 return result; 3745 } 3746 3747 private void onResetScrollArea() { 3748 setCurrentDragOverlappingLayout(null); 3749 mInScrollArea = false; 3750 } 3751 3752 /** 3753 * Returns a specific CellLayout 3754 */ 3755 CellLayout getParentCellLayoutForView(View v) { 3756 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts(); 3757 for (CellLayout layout : layouts) { 3758 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) { 3759 return layout; 3760 } 3761 } 3762 return null; 3763 } 3764 3765 /** 3766 * Returns a list of all the CellLayouts in the workspace. 3767 */ 3768 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() { 3769 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>(); 3770 int screenCount = getChildCount(); 3771 for (int screen = 0; screen < screenCount; screen++) { 3772 layouts.add(((CellLayout) getChildAt(screen))); 3773 } 3774 if (mLauncher.getHotseat() != null) { 3775 layouts.add(mLauncher.getHotseat().getLayout()); 3776 } 3777 return layouts; 3778 } 3779 3780 /** 3781 * We should only use this to search for specific children. Do not use this method to modify 3782 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from 3783 * the hotseat and workspace pages 3784 */ 3785 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() { 3786 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 3787 new ArrayList<ShortcutAndWidgetContainer>(); 3788 int screenCount = getChildCount(); 3789 for (int screen = 0; screen < screenCount; screen++) { 3790 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets()); 3791 } 3792 if (mLauncher.getHotseat() != null) { 3793 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets()); 3794 } 3795 return childrenLayouts; 3796 } 3797 3798 public Folder getFolderForTag(Object tag) { 3799 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 3800 getAllShortcutAndWidgetContainers(); 3801 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 3802 int count = layout.getChildCount(); 3803 for (int i = 0; i < count; i++) { 3804 View child = layout.getChildAt(i); 3805 if (child instanceof Folder) { 3806 Folder f = (Folder) child; 3807 if (f.getInfo() == tag && f.getInfo().opened) { 3808 return f; 3809 } 3810 } 3811 } 3812 } 3813 return null; 3814 } 3815 3816 public View getViewForTag(Object tag) { 3817 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 3818 getAllShortcutAndWidgetContainers(); 3819 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 3820 int count = layout.getChildCount(); 3821 for (int i = 0; i < count; i++) { 3822 View child = layout.getChildAt(i); 3823 if (child.getTag() == tag) { 3824 return child; 3825 } 3826 } 3827 } 3828 return null; 3829 } 3830 3831 void clearDropTargets() { 3832 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 3833 getAllShortcutAndWidgetContainers(); 3834 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 3835 int childCount = layout.getChildCount(); 3836 for (int j = 0; j < childCount; j++) { 3837 View v = layout.getChildAt(j); 3838 if (v instanceof DropTarget) { 3839 mDragController.removeDropTarget((DropTarget) v); 3840 } 3841 } 3842 } 3843 } 3844 3845 // Removes ALL items that match a given package name, this is usually called when a package 3846 // has been removed and we want to remove all components (widgets, shortcuts, apps) that 3847 // belong to that package. 3848 void removeItemsByPackageName(final ArrayList<String> packages) { 3849 final HashSet<String> packageNames = new HashSet<String>(); 3850 packageNames.addAll(packages); 3851 3852 // Filter out all the ItemInfos that this is going to affect 3853 final HashSet<ItemInfo> infos = new HashSet<ItemInfo>(); 3854 final HashSet<ComponentName> cns = new HashSet<ComponentName>(); 3855 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts(); 3856 for (CellLayout layoutParent : cellLayouts) { 3857 ViewGroup layout = layoutParent.getShortcutsAndWidgets(); 3858 int childCount = layout.getChildCount(); 3859 for (int i = 0; i < childCount; ++i) { 3860 View view = layout.getChildAt(i); 3861 infos.add((ItemInfo) view.getTag()); 3862 } 3863 } 3864 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() { 3865 @Override 3866 public boolean filterItem(ItemInfo parent, ItemInfo info, 3867 ComponentName cn) { 3868 if (packageNames.contains(cn.getPackageName())) { 3869 cns.add(cn); 3870 return true; 3871 } 3872 return false; 3873 } 3874 }; 3875 LauncherModel.filterItemInfos(infos, filter); 3876 3877 // Remove the affected components 3878 removeItemsByComponentName(cns); 3879 } 3880 3881 // Removes items that match the application info specified, when applications are removed 3882 // as a part of an update, this is called to ensure that other widgets and application 3883 // shortcuts are not removed. 3884 void removeItemsByApplicationInfo(final ArrayList<ApplicationInfo> appInfos) { 3885 // Just create a hash table of all the specific components that this will affect 3886 HashSet<ComponentName> cns = new HashSet<ComponentName>(); 3887 for (ApplicationInfo info : appInfos) { 3888 cns.add(info.componentName); 3889 } 3890 3891 // Remove all the things 3892 removeItemsByComponentName(cns); 3893 } 3894 3895 void removeItemsByComponentName(final HashSet<ComponentName> componentNames) { 3896 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts(); 3897 for (final CellLayout layoutParent: cellLayouts) { 3898 final ViewGroup layout = layoutParent.getShortcutsAndWidgets(); 3899 3900 final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>(); 3901 for (int j = 0; j < layout.getChildCount(); j++) { 3902 final View view = layout.getChildAt(j); 3903 children.put((ItemInfo) view.getTag(), view); 3904 } 3905 3906 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 3907 final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = 3908 new HashMap<FolderInfo, ArrayList<ShortcutInfo>>(); 3909 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() { 3910 @Override 3911 public boolean filterItem(ItemInfo parent, ItemInfo info, 3912 ComponentName cn) { 3913 if (parent instanceof FolderInfo) { 3914 if (componentNames.contains(cn)) { 3915 FolderInfo folder = (FolderInfo) parent; 3916 ArrayList<ShortcutInfo> appsToRemove; 3917 if (folderAppsToRemove.containsKey(folder)) { 3918 appsToRemove = folderAppsToRemove.get(folder); 3919 } else { 3920 appsToRemove = new ArrayList<ShortcutInfo>(); 3921 folderAppsToRemove.put(folder, appsToRemove); 3922 } 3923 appsToRemove.add((ShortcutInfo) info); 3924 return true; 3925 } 3926 } else { 3927 if (componentNames.contains(cn)) { 3928 childrenToRemove.add(children.get(info)); 3929 return true; 3930 } 3931 } 3932 return false; 3933 } 3934 }; 3935 LauncherModel.filterItemInfos(children.keySet(), filter); 3936 3937 // Remove all the apps from their folders 3938 for (FolderInfo folder : folderAppsToRemove.keySet()) { 3939 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder); 3940 for (ShortcutInfo info : appsToRemove) { 3941 folder.remove(info); 3942 } 3943 } 3944 3945 // Remove all the other children 3946 for (View child : childrenToRemove) { 3947 // Note: We can not remove the view directly from CellLayoutChildren as this 3948 // does not re-mark the spaces as unoccupied. 3949 layoutParent.removeViewInLayout(child); 3950 if (child instanceof DropTarget) { 3951 mDragController.removeDropTarget((DropTarget) child); 3952 } 3953 } 3954 3955 if (childrenToRemove.size() > 0) { 3956 layout.requestLayout(); 3957 layout.invalidate(); 3958 } 3959 } 3960 3961 // Strip all the empty screens 3962 stripEmptyScreens(); 3963 3964 // Clean up new-apps animation list 3965 final Context context = getContext(); 3966 post(new Runnable() { 3967 @Override 3968 public void run() { 3969 String spKey = LauncherAppState.getSharedPreferencesKey(); 3970 SharedPreferences sp = context.getSharedPreferences(spKey, 3971 Context.MODE_PRIVATE); 3972 Set<String> newApps = sp.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, 3973 null); 3974 3975 // Remove all queued items that match the same package 3976 if (newApps != null) { 3977 synchronized (newApps) { 3978 Iterator<String> iter = newApps.iterator(); 3979 while (iter.hasNext()) { 3980 try { 3981 Intent intent = Intent.parseUri(iter.next(), 0); 3982 if (componentNames.contains(intent.getComponent())) { 3983 iter.remove(); 3984 } 3985 } catch (URISyntaxException e) {} 3986 } 3987 } 3988 } 3989 } 3990 }); 3991 } 3992 3993 void updateShortcuts(ArrayList<ApplicationInfo> apps) { 3994 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers(); 3995 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 3996 int childCount = layout.getChildCount(); 3997 for (int j = 0; j < childCount; j++) { 3998 final View view = layout.getChildAt(j); 3999 Object tag = view.getTag(); 4000 4001 if (LauncherModel.isShortcutInfoUpdateable((ItemInfo) tag)) { 4002 ShortcutInfo info = (ShortcutInfo) tag; 4003 4004 final Intent intent = info.intent; 4005 final ComponentName name = intent.getComponent(); 4006 final int appCount = apps.size(); 4007 for (int k = 0; k < appCount; k++) { 4008 ApplicationInfo app = apps.get(k); 4009 if (app.componentName.equals(name)) { 4010 BubbleTextView shortcut = (BubbleTextView) view; 4011 info.updateIcon(mIconCache); 4012 info.title = app.title.toString(); 4013 shortcut.applyFromShortcutInfo(info, mIconCache); 4014 } 4015 } 4016 } 4017 } 4018 } 4019 } 4020 4021 void moveToDefaultScreen(boolean animate) { 4022 if (!isSmall()) { 4023 if (animate) { 4024 snapToPage(mDefaultPage); 4025 } else { 4026 setCurrentPage(mDefaultPage); 4027 } 4028 } 4029 View child = getChildAt(mDefaultPage); 4030 if (child != null) { 4031 child.requestFocus(); 4032 } 4033 } 4034 4035 @Override 4036 public void syncPages() { 4037 } 4038 4039 @Override 4040 public void syncPageItems(int page, boolean immediate) { 4041 } 4042 4043 protected String getCurrentPageDescription() { 4044 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 4045 return String.format(getContext().getString(R.string.workspace_scroll_format), 4046 page + 1, getChildCount()); 4047 } 4048 4049 public void getLocationInDragLayer(int[] loc) { 4050 mLauncher.getDragLayer().getLocationInDragLayer(this, loc); 4051 } 4052 4053 void setFadeForOverScroll(float fade) { 4054 mOverscrollFade = fade; 4055 float reducedFade = 0.5f + 0.5f * (1 - fade); 4056 final ViewGroup parent = (ViewGroup) getParent(); 4057 final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider)); 4058 4059 if (qsbDivider != null) qsbDivider.setAlpha(reducedFade); 4060 } 4061} 4062