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