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