Workspace.java revision 58993ad14da65dac1e43f3490cba8f751c92085b
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 = getScrollForPage(index + 1) - getScrollX() + 1413 getLayoutTransitionOffsetForPage(index + 1); 1414 translationX = scrollDelta; 1415 progress = (1.0f * scrollDelta) / 1416 (getScrollForPage(index + 1) - getScrollForPage(index)); 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 boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview); 1954 boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal); 1955 1956 mNewScale = 1.0f; 1957 1958 if (oldStateIsOverview) { 1959 disableFreeScroll(snapPage); 1960 } else if (stateIsOverview) { 1961 enableFreeScroll(); 1962 } 1963 1964 if (state != State.NORMAL) { 1965 if (stateIsSpringLoaded) { 1966 mNewScale = mSpringLoadedShrinkFactor; 1967 } else if (stateIsOverview) { 1968 mNewScale = mOverviewModeShrinkFactor; 1969 } else if (stateIsSmall){ 1970 mNewScale = mOverviewModeShrinkFactor - 0.3f; 1971 } 1972 if (workspaceToAllApps) { 1973 updateChildrenLayersEnabled(false); 1974 } 1975 } 1976 1977 final int duration = workspaceToAllApps ? 1978 getResources().getInteger(R.integer.config_workspaceUnshrinkTime) : 1979 getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime); 1980 for (int i = 0; i < getChildCount(); i++) { 1981 final CellLayout cl = (CellLayout) getChildAt(i); 1982 boolean isCurrentPage = (i == getNextPage()); 1983 float initialAlpha = cl.getShortcutsAndWidgets().getAlpha(); 1984 float finalAlpha = stateIsSmall ? 0f : 1f; 1985 1986 // If we are animating to/from the small state, then hide the side pages and fade the 1987 // current page in 1988 if (!mIsSwitchingState) { 1989 if (workspaceToAllApps || allAppsToWorkspace) { 1990 if (allAppsToWorkspace && isCurrentPage) { 1991 initialAlpha = 0f; 1992 } else if (!isCurrentPage) { 1993 initialAlpha = finalAlpha = 0f; 1994 } 1995 cl.setShortcutAndWidgetAlpha(initialAlpha); 1996 } 1997 } 1998 1999 mOldAlphas[i] = initialAlpha; 2000 mNewAlphas[i] = finalAlpha; 2001 if (animated) { 2002 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha(); 2003 mNewBackgroundAlphas[i] = finalBackgroundAlpha; 2004 } else { 2005 cl.setBackgroundAlpha(finalBackgroundAlpha); 2006 cl.setShortcutAndWidgetAlpha(finalAlpha); 2007 } 2008 } 2009 2010 final View searchBar = mLauncher.getQsbBar(); 2011 final View overviewPanel = mLauncher.getOverviewPanel(); 2012 final View hotseat = mLauncher.getHotseat(); 2013 if (animated) { 2014 LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this); 2015 scale.scaleX(mNewScale) 2016 .scaleY(mNewScale) 2017 .translationY(finalWorkspaceTranslationY) 2018 .setInterpolator(mZoomInInterpolator); 2019 anim.play(scale); 2020 for (int index = 0; index < getChildCount(); index++) { 2021 final int i = index; 2022 final CellLayout cl = (CellLayout) getChildAt(i); 2023 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha(); 2024 if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) { 2025 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]); 2026 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]); 2027 } else { 2028 if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) { 2029 LauncherViewPropertyAnimator alphaAnim = 2030 new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets()); 2031 alphaAnim.alpha(mNewAlphas[i]) 2032 .setDuration(duration) 2033 .setInterpolator(mZoomInInterpolator); 2034 anim.play(alphaAnim); 2035 } 2036 if (mOldBackgroundAlphas[i] != 0 || 2037 mNewBackgroundAlphas[i] != 0) { 2038 ValueAnimator bgAnim = 2039 LauncherAnimUtils.ofFloat(cl, 0f, 1f).setDuration(duration); 2040 bgAnim.setInterpolator(mZoomInInterpolator); 2041 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { 2042 public void onAnimationUpdate(float a, float b) { 2043 cl.setBackgroundAlpha( 2044 a * mOldBackgroundAlphas[i] + 2045 b * mNewBackgroundAlphas[i]); 2046 } 2047 }); 2048 anim.play(bgAnim); 2049 } 2050 } 2051 } 2052 ObjectAnimator pageIndicatorAlpha = null; 2053 if (getPageIndicator() != null) { 2054 pageIndicatorAlpha = ObjectAnimator.ofFloat(getPageIndicator(), "alpha", 2055 finalHotseatAndPageIndicatorAlpha); 2056 } 2057 ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(hotseat, "alpha", 2058 finalHotseatAndPageIndicatorAlpha); 2059 ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(searchBar, 2060 "alpha", finalSearchBarAlpha); 2061 ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(overviewPanel, 2062 "alpha", finalOverviewPanelAlpha); 2063 2064 overviewPanelAlpha.addUpdateListener(new AlphaUpdateListener(overviewPanel)); 2065 hotseatAlpha.addUpdateListener(new AlphaUpdateListener(hotseat)); 2066 searchBarAlpha.addUpdateListener(new AlphaUpdateListener(searchBar)); 2067 2068 if (workspaceToOverview) { 2069 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2)); 2070 } else if (overviewToWorkspace) { 2071 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2)); 2072 } 2073 2074 if (getPageIndicator() != null) { 2075 pageIndicatorAlpha.addUpdateListener(new AlphaUpdateListener(getPageIndicator())); 2076 } 2077 2078 anim.play(overviewPanelAlpha); 2079 anim.play(hotseatAlpha); 2080 anim.play(searchBarAlpha); 2081 anim.play(pageIndicatorAlpha); 2082 anim.setStartDelay(delay); 2083 } else { 2084 overviewPanel.setAlpha(finalOverviewPanelAlpha); 2085 AlphaUpdateListener.updateVisibility(overviewPanel); 2086 hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha); 2087 AlphaUpdateListener.updateVisibility(hotseat); 2088 if (getPageIndicator() != null) { 2089 getPageIndicator().setAlpha(finalHotseatAndPageIndicatorAlpha); 2090 AlphaUpdateListener.updateVisibility(getPageIndicator()); 2091 } 2092 searchBar.setAlpha(finalSearchBarAlpha); 2093 AlphaUpdateListener.updateVisibility(searchBar); 2094 updateCustomContentVisibility(); 2095 setScaleX(mNewScale); 2096 setScaleY(mNewScale); 2097 setTranslationY(finalWorkspaceTranslationY); 2098 } 2099 mLauncher.updateVoiceButtonProxyVisible(false); 2100 2101 if (stateIsSpringLoaded) { 2102 // Right now we're covered by Apps Customize 2103 // Show the background gradient immediately, so the gradient will 2104 // be showing once AppsCustomize disappears 2105 animateBackgroundGradient(getResources().getInteger( 2106 R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false); 2107 } else if (stateIsOverview) { 2108 animateBackgroundGradient(getResources().getInteger( 2109 R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, true); 2110 } else { 2111 // Fade the background gradient away 2112 animateBackgroundGradient(0f, animated); 2113 } 2114 return anim; 2115 } 2116 2117 static class AlphaUpdateListener implements AnimatorUpdateListener { 2118 View view; 2119 public AlphaUpdateListener(View v) { 2120 view = v; 2121 } 2122 2123 @Override 2124 public void onAnimationUpdate(ValueAnimator arg0) { 2125 updateVisibility(view); 2126 } 2127 2128 public static void updateVisibility(View view) { 2129 // We want to avoid the extra layout pass by setting the views to GONE unless 2130 // accessibility is on, in which case not setting them to GONE causes a glitch. 2131 int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE; 2132 if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) { 2133 view.setVisibility(invisibleState); 2134 } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD 2135 && view.getVisibility() != VISIBLE) { 2136 view.setVisibility(VISIBLE); 2137 } 2138 } 2139 } 2140 2141 @Override 2142 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { 2143 onTransitionPrepare(); 2144 } 2145 2146 @Override 2147 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { 2148 } 2149 2150 @Override 2151 public void onLauncherTransitionStep(Launcher l, float t) { 2152 mTransitionProgress = t; 2153 } 2154 2155 @Override 2156 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { 2157 onTransitionEnd(); 2158 } 2159 2160 private void onTransitionPrepare() { 2161 mIsSwitchingState = true; 2162 2163 // Invalidate here to ensure that the pages are rendered during the state change transition. 2164 invalidate(); 2165 2166 updateChildrenLayersEnabled(false); 2167 hideCustomContentIfNecessary(); 2168 } 2169 2170 void updateCustomContentVisibility() { 2171 int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE; 2172 if (hasCustomContent()) { 2173 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility); 2174 } 2175 } 2176 2177 void showCustomContentIfNecessary() { 2178 boolean show = mState == Workspace.State.NORMAL; 2179 if (show && hasCustomContent()) { 2180 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE); 2181 } 2182 } 2183 2184 void hideCustomContentIfNecessary() { 2185 boolean hide = mState != Workspace.State.NORMAL; 2186 if (hide && hasCustomContent()) { 2187 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE); 2188 } 2189 } 2190 2191 private void onTransitionEnd() { 2192 mIsSwitchingState = false; 2193 updateChildrenLayersEnabled(false); 2194 // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure 2195 // ensure that only the current page is visible during (and subsequently, after) the 2196 // transition animation. If fade adjacent pages is disabled, then re-enable the page 2197 // visibility after the transition animation. 2198 if (!mWorkspaceFadeInAdjacentScreens) { 2199 for (int i = 0; i < getChildCount(); i++) { 2200 final CellLayout cl = (CellLayout) getChildAt(i); 2201 cl.setShortcutAndWidgetAlpha(1f); 2202 } 2203 } 2204 showCustomContentIfNecessary(); 2205 } 2206 2207 @Override 2208 public View getContent() { 2209 return this; 2210 } 2211 2212 /** 2213 * Draw the View v into the given Canvas. 2214 * 2215 * @param v the view to draw 2216 * @param destCanvas the canvas to draw on 2217 * @param padding the horizontal and vertical padding to use when drawing 2218 */ 2219 private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) { 2220 final Rect clipRect = mTempRect; 2221 v.getDrawingRect(clipRect); 2222 2223 boolean textVisible = false; 2224 2225 destCanvas.save(); 2226 if (v instanceof TextView && pruneToDrawable) { 2227 Drawable d = ((TextView) v).getCompoundDrawables()[1]; 2228 clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding); 2229 destCanvas.translate(padding / 2, padding / 2); 2230 d.draw(destCanvas); 2231 } else { 2232 if (v instanceof FolderIcon) { 2233 // For FolderIcons the text can bleed into the icon area, and so we need to 2234 // hide the text completely (which can't be achieved by clipping). 2235 if (((FolderIcon) v).getTextVisible()) { 2236 ((FolderIcon) v).setTextVisible(false); 2237 textVisible = true; 2238 } 2239 } else if (v instanceof BubbleTextView) { 2240 final BubbleTextView tv = (BubbleTextView) v; 2241 clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + 2242 tv.getLayout().getLineTop(0); 2243 } else if (v instanceof TextView) { 2244 final TextView tv = (TextView) v; 2245 clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() + 2246 tv.getLayout().getLineTop(0); 2247 } 2248 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2); 2249 destCanvas.clipRect(clipRect, Op.REPLACE); 2250 v.draw(destCanvas); 2251 2252 // Restore text visibility of FolderIcon if necessary 2253 if (textVisible) { 2254 ((FolderIcon) v).setTextVisible(true); 2255 } 2256 } 2257 destCanvas.restore(); 2258 } 2259 2260 /** 2261 * Returns a new bitmap to show when the given View is being dragged around. 2262 * Responsibility for the bitmap is transferred to the caller. 2263 */ 2264 public Bitmap createDragBitmap(View v, Canvas canvas, int padding) { 2265 Bitmap b; 2266 2267 if (v instanceof TextView) { 2268 Drawable d = ((TextView) v).getCompoundDrawables()[1]; 2269 b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding, 2270 d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888); 2271 } else { 2272 b = Bitmap.createBitmap( 2273 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 2274 } 2275 2276 canvas.setBitmap(b); 2277 drawDragView(v, canvas, padding, true); 2278 canvas.setBitmap(null); 2279 2280 return b; 2281 } 2282 2283 /** 2284 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 2285 * Responsibility for the bitmap is transferred to the caller. 2286 */ 2287 private Bitmap createDragOutline(View v, Canvas canvas, int padding) { 2288 final int outlineColor = getResources().getColor(R.color.outline_color); 2289 final Bitmap b = Bitmap.createBitmap( 2290 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 2291 2292 canvas.setBitmap(b); 2293 drawDragView(v, canvas, padding, true); 2294 mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); 2295 canvas.setBitmap(null); 2296 return b; 2297 } 2298 2299 /** 2300 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 2301 * Responsibility for the bitmap is transferred to the caller. 2302 */ 2303 private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h, 2304 boolean clipAlpha) { 2305 final int outlineColor = getResources().getColor(R.color.outline_color); 2306 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 2307 canvas.setBitmap(b); 2308 2309 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight()); 2310 float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(), 2311 (h - padding) / (float) orig.getHeight()); 2312 int scaledWidth = (int) (scaleFactor * orig.getWidth()); 2313 int scaledHeight = (int) (scaleFactor * orig.getHeight()); 2314 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight); 2315 2316 // center the image 2317 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2); 2318 2319 canvas.drawBitmap(orig, src, dst, null); 2320 mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor, 2321 clipAlpha); 2322 canvas.setBitmap(null); 2323 2324 return b; 2325 } 2326 2327 void startDrag(CellLayout.CellInfo cellInfo) { 2328 View child = cellInfo.cell; 2329 2330 // Make sure the drag was started by a long press as opposed to a long click. 2331 if (!child.isInTouchMode()) { 2332 return; 2333 } 2334 2335 mDragInfo = cellInfo; 2336 child.setVisibility(INVISIBLE); 2337 CellLayout layout = (CellLayout) child.getParent().getParent(); 2338 layout.prepareChildForDrag(child); 2339 2340 child.clearFocus(); 2341 child.setPressed(false); 2342 2343 final Canvas canvas = new Canvas(); 2344 2345 // The outline is used to visualize where the item will land if dropped 2346 mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING); 2347 beginDragShared(child, this); 2348 } 2349 2350 public void beginDragShared(View child, DragSource source) { 2351 // The drag bitmap follows the touch point around on the screen 2352 final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING); 2353 2354 final int bmpWidth = b.getWidth(); 2355 final int bmpHeight = b.getHeight(); 2356 2357 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY); 2358 int dragLayerX = 2359 Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2); 2360 int dragLayerY = 2361 Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2 2362 - DRAG_BITMAP_PADDING / 2); 2363 2364 LauncherAppState app = LauncherAppState.getInstance(); 2365 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 2366 Point dragVisualizeOffset = null; 2367 Rect dragRect = null; 2368 if (child instanceof BubbleTextView || child instanceof PagedViewIcon) { 2369 int iconSize = grid.iconSizePx; 2370 int top = child.getPaddingTop(); 2371 int left = (bmpWidth - iconSize) / 2; 2372 int right = left + iconSize; 2373 int bottom = top + iconSize; 2374 dragLayerY += top; 2375 // Note: The drag region is used to calculate drag layer offsets, but the 2376 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. 2377 dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2); 2378 dragRect = new Rect(left, top, right, bottom); 2379 } else if (child instanceof FolderIcon) { 2380 int previewSize = grid.folderIconSizePx; 2381 dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize); 2382 } 2383 2384 // Clear the pressed state if necessary 2385 if (child instanceof BubbleTextView) { 2386 BubbleTextView icon = (BubbleTextView) child; 2387 icon.clearPressedOrFocusedBackground(); 2388 } 2389 2390 mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), 2391 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale); 2392 2393 if (child.getParent() instanceof ShortcutAndWidgetContainer) { 2394 mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); 2395 } 2396 2397 b.recycle(); 2398 } 2399 2400 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId, 2401 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) { 2402 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info); 2403 2404 final int[] cellXY = new int[2]; 2405 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); 2406 addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst); 2407 2408 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0], 2409 cellXY[1]); 2410 } 2411 2412 public boolean transitionStateShouldAllowDrop() { 2413 return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL); 2414 } 2415 2416 /** 2417 * {@inheritDoc} 2418 */ 2419 public boolean acceptDrop(DragObject d) { 2420 // If it's an external drop (e.g. from All Apps), check if it should be accepted 2421 CellLayout dropTargetLayout = mDropToLayout; 2422 if (d.dragSource != this) { 2423 // Don't accept the drop if we're not over a screen at time of drop 2424 if (dropTargetLayout == null) { 2425 return false; 2426 } 2427 if (!transitionStateShouldAllowDrop()) return false; 2428 2429 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, 2430 d.dragView, mDragViewVisualCenter); 2431 2432 // We want the point to be mapped to the dragTarget. 2433 if (mLauncher.isHotseatLayout(dropTargetLayout)) { 2434 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 2435 } else { 2436 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); 2437 } 2438 2439 int spanX = 1; 2440 int spanY = 1; 2441 if (mDragInfo != null) { 2442 final CellLayout.CellInfo dragCellInfo = mDragInfo; 2443 spanX = dragCellInfo.spanX; 2444 spanY = dragCellInfo.spanY; 2445 } else { 2446 final ItemInfo dragInfo = (ItemInfo) d.dragInfo; 2447 spanX = dragInfo.spanX; 2448 spanY = dragInfo.spanY; 2449 } 2450 2451 int minSpanX = spanX; 2452 int minSpanY = spanY; 2453 if (d.dragInfo instanceof PendingAddWidgetInfo) { 2454 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX; 2455 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY; 2456 } 2457 2458 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2459 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout, 2460 mTargetCell); 2461 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2462 mDragViewVisualCenter[1], mTargetCell); 2463 if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, 2464 mTargetCell, distance, true)) { 2465 return true; 2466 } 2467 if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, 2468 mTargetCell, distance)) { 2469 return true; 2470 } 2471 2472 int[] resultSpan = new int[2]; 2473 mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], 2474 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 2475 null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP); 2476 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 2477 2478 // Don't accept the drop if there's no room for the item 2479 if (!foundCell) { 2480 // Don't show the message if we are dropping on the AllApps button and the hotseat 2481 // is full 2482 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 2483 if (mTargetCell != null && isHotseat) { 2484 Hotseat hotseat = mLauncher.getHotseat(); 2485 if (hotseat.isAllAppsButtonRank( 2486 hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) { 2487 return false; 2488 } 2489 } 2490 2491 mLauncher.showOutOfSpaceMessage(isHotseat); 2492 return false; 2493 } 2494 } 2495 2496 long screenId = getIdForScreen(dropTargetLayout); 2497 if (screenId == EXTRA_EMPTY_SCREEN_ID) { 2498 commitExtraEmptyScreen(); 2499 } 2500 2501 return true; 2502 } 2503 2504 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float 2505 distance, boolean considerTimeout) { 2506 if (distance > mMaxDistanceForFolderCreation) return false; 2507 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2508 2509 if (dropOverView != null) { 2510 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 2511 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { 2512 return false; 2513 } 2514 } 2515 2516 boolean hasntMoved = false; 2517 if (mDragInfo != null) { 2518 hasntMoved = dropOverView == mDragInfo.cell; 2519 } 2520 2521 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) { 2522 return false; 2523 } 2524 2525 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo); 2526 boolean willBecomeShortcut = 2527 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 2528 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); 2529 2530 return (aboveShortcut && willBecomeShortcut); 2531 } 2532 2533 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell, 2534 float distance) { 2535 if (distance > mMaxDistanceForFolderCreation) return false; 2536 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2537 2538 if (dropOverView != null) { 2539 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); 2540 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { 2541 return false; 2542 } 2543 } 2544 2545 if (dropOverView instanceof FolderIcon) { 2546 FolderIcon fi = (FolderIcon) dropOverView; 2547 if (fi.acceptDrop(dragInfo)) { 2548 return true; 2549 } 2550 } 2551 return false; 2552 } 2553 2554 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target, 2555 int[] targetCell, float distance, boolean external, DragView dragView, 2556 Runnable postAnimationRunnable) { 2557 if (distance > mMaxDistanceForFolderCreation) return false; 2558 View v = target.getChildAt(targetCell[0], targetCell[1]); 2559 2560 boolean hasntMoved = false; 2561 if (mDragInfo != null) { 2562 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); 2563 hasntMoved = (mDragInfo.cellX == targetCell[0] && 2564 mDragInfo.cellY == targetCell[1]) && (cellParent == target); 2565 } 2566 2567 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; 2568 mCreateUserFolderOnDrop = false; 2569 final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target); 2570 2571 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo); 2572 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo); 2573 2574 if (aboveShortcut && willBecomeShortcut) { 2575 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag(); 2576 ShortcutInfo destInfo = (ShortcutInfo) v.getTag(); 2577 // if the drag started here, we need to remove it from the workspace 2578 if (!external) { 2579 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2580 } 2581 2582 Rect folderLocation = new Rect(); 2583 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation); 2584 target.removeView(v); 2585 2586 FolderIcon fi = 2587 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]); 2588 destInfo.cellX = -1; 2589 destInfo.cellY = -1; 2590 sourceInfo.cellX = -1; 2591 sourceInfo.cellY = -1; 2592 2593 // If the dragView is null, we can't animate 2594 boolean animate = dragView != null; 2595 if (animate) { 2596 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, 2597 postAnimationRunnable); 2598 } else { 2599 fi.addItem(destInfo); 2600 fi.addItem(sourceInfo); 2601 } 2602 return true; 2603 } 2604 return false; 2605 } 2606 2607 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, 2608 float distance, DragObject d, boolean external) { 2609 if (distance > mMaxDistanceForFolderCreation) return false; 2610 2611 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2612 if (!mAddToExistingFolderOnDrop) return false; 2613 mAddToExistingFolderOnDrop = false; 2614 2615 if (dropOverView instanceof FolderIcon) { 2616 FolderIcon fi = (FolderIcon) dropOverView; 2617 if (fi.acceptDrop(d.dragInfo)) { 2618 fi.onDrop(d); 2619 2620 // if the drag started here, we need to remove it from the workspace 2621 if (!external) { 2622 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2623 } 2624 return true; 2625 } 2626 } 2627 return false; 2628 } 2629 2630 public void onDrop(final DragObject d) { 2631 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, 2632 mDragViewVisualCenter); 2633 2634 CellLayout dropTargetLayout = mDropToLayout; 2635 2636 // We want the point to be mapped to the dragTarget. 2637 if (dropTargetLayout != null) { 2638 if (mLauncher.isHotseatLayout(dropTargetLayout)) { 2639 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 2640 } else { 2641 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); 2642 } 2643 } 2644 2645 int snapScreen = -1; 2646 boolean resizeOnDrop = false; 2647 if (d.dragSource != this) { 2648 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], 2649 (int) mDragViewVisualCenter[1] }; 2650 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d); 2651 } else if (mDragInfo != null) { 2652 final View cell = mDragInfo.cell; 2653 2654 Runnable resizeRunnable = null; 2655 if (dropTargetLayout != null && !d.cancelled) { 2656 // Move internally 2657 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); 2658 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 2659 long container = hasMovedIntoHotseat ? 2660 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 2661 LauncherSettings.Favorites.CONTAINER_DESKTOP; 2662 long screenId = (mTargetCell[0] < 0) ? 2663 mDragInfo.screenId : getIdForScreen(dropTargetLayout); 2664 int spanX = mDragInfo != null ? mDragInfo.spanX : 1; 2665 int spanY = mDragInfo != null ? mDragInfo.spanY : 1; 2666 // First we find the cell nearest to point at which the item is 2667 // dropped, without any consideration to whether there is an item there. 2668 2669 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) 2670 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); 2671 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], 2672 mDragViewVisualCenter[1], mTargetCell); 2673 2674 // If the item being dropped is a shortcut and the nearest drop 2675 // cell also contains a shortcut, then create a folder with the two shortcuts. 2676 if (!mInScrollArea && createUserFolderIfNecessary(cell, container, 2677 dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) { 2678 stripEmptyScreens(); 2679 return; 2680 } 2681 2682 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, 2683 distance, d, false)) { 2684 stripEmptyScreens(); 2685 return; 2686 } 2687 2688 // Aside from the special case where we're dropping a shortcut onto a shortcut, 2689 // we need to find the nearest cell location that is vacant 2690 ItemInfo item = (ItemInfo) d.dragInfo; 2691 int minSpanX = item.spanX; 2692 int minSpanY = item.spanY; 2693 if (item.minSpanX > 0 && item.minSpanY > 0) { 2694 minSpanX = item.minSpanX; 2695 minSpanY = item.minSpanY; 2696 } 2697 2698 int[] resultSpan = new int[2]; 2699 mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], 2700 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, 2701 mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); 2702 2703 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; 2704 2705 // if the widget resizes on drop 2706 if (foundCell && (cell instanceof AppWidgetHostView) && 2707 (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) { 2708 resizeOnDrop = true; 2709 item.spanX = resultSpan[0]; 2710 item.spanY = resultSpan[1]; 2711 AppWidgetHostView awhv = (AppWidgetHostView) cell; 2712 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], 2713 resultSpan[1]); 2714 } 2715 2716 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) { 2717 snapScreen = getPageIndexForScreenId(screenId); 2718 snapToPage(snapScreen); 2719 } 2720 2721 if (foundCell) { 2722 final ItemInfo info = (ItemInfo) cell.getTag(); 2723 if (hasMovedLayouts) { 2724 // Reparent the view 2725 getParentCellLayoutForView(cell).removeView(cell); 2726 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], 2727 info.spanX, info.spanY); 2728 } 2729 2730 // update the item's position after drop 2731 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 2732 lp.cellX = lp.tmpCellX = mTargetCell[0]; 2733 lp.cellY = lp.tmpCellY = mTargetCell[1]; 2734 lp.cellHSpan = item.spanX; 2735 lp.cellVSpan = item.spanY; 2736 lp.isLockedToGrid = true; 2737 cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId, 2738 mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); 2739 2740 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && 2741 cell instanceof LauncherAppWidgetHostView) { 2742 final CellLayout cellLayout = dropTargetLayout; 2743 // We post this call so that the widget has a chance to be placed 2744 // in its final location 2745 2746 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; 2747 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo(); 2748 if (pinfo != null && 2749 pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) { 2750 final Runnable addResizeFrame = new Runnable() { 2751 public void run() { 2752 DragLayer dragLayer = mLauncher.getDragLayer(); 2753 dragLayer.addResizeFrame(info, hostView, cellLayout); 2754 } 2755 }; 2756 resizeRunnable = (new Runnable() { 2757 public void run() { 2758 if (!isPageMoving()) { 2759 addResizeFrame.run(); 2760 } else { 2761 mDelayedResizeRunnable = addResizeFrame; 2762 } 2763 } 2764 }); 2765 } 2766 } 2767 2768 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX, 2769 lp.cellY, item.spanX, item.spanY); 2770 } else { 2771 // If we can't find a drop location, we return the item to its original position 2772 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 2773 mTargetCell[0] = lp.cellX; 2774 mTargetCell[1] = lp.cellY; 2775 CellLayout layout = (CellLayout) cell.getParent().getParent(); 2776 layout.markCellsAsOccupiedForView(cell); 2777 } 2778 } 2779 2780 final CellLayout parent = (CellLayout) cell.getParent().getParent(); 2781 final Runnable finalResizeRunnable = resizeRunnable; 2782 // Prepare it to be animated into its new position 2783 // This must be called after the view has been re-parented 2784 final Runnable onCompleteRunnable = new Runnable() { 2785 @Override 2786 public void run() { 2787 mAnimatingViewIntoPlace = false; 2788 updateChildrenLayersEnabled(false); 2789 if (finalResizeRunnable != null) { 2790 finalResizeRunnable.run(); 2791 } 2792 stripEmptyScreens(); 2793 } 2794 }; 2795 mAnimatingViewIntoPlace = true; 2796 if (d.dragView.hasDrawn()) { 2797 final ItemInfo info = (ItemInfo) cell.getTag(); 2798 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) { 2799 int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : 2800 ANIMATE_INTO_POSITION_AND_DISAPPEAR; 2801 animateWidgetDrop(info, parent, d.dragView, 2802 onCompleteRunnable, animationType, cell, false); 2803 } else { 2804 int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; 2805 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, 2806 onCompleteRunnable, this); 2807 } 2808 } else { 2809 d.deferDragViewCleanupPostAnimation = false; 2810 cell.setVisibility(VISIBLE); 2811 } 2812 parent.onDropChild(cell); 2813 } 2814 } 2815 2816 public void setFinalScrollForPageChange(int pageIndex) { 2817 CellLayout cl = (CellLayout) getChildAt(pageIndex); 2818 if (cl != null) { 2819 mSavedScrollX = getScrollX(); 2820 mSavedTranslationX = cl.getTranslationX(); 2821 mSavedRotationY = cl.getRotationY(); 2822 final int newX = getScrollForPage(pageIndex); 2823 setScrollX(newX); 2824 cl.setTranslationX(0f); 2825 cl.setRotationY(0f); 2826 } 2827 } 2828 2829 public void resetFinalScrollForPageChange(int pageIndex) { 2830 if (pageIndex >= 0) { 2831 CellLayout cl = (CellLayout) getChildAt(pageIndex); 2832 setScrollX(mSavedScrollX); 2833 cl.setTranslationX(mSavedTranslationX); 2834 cl.setRotationY(mSavedRotationY); 2835 } 2836 } 2837 2838 public void getViewLocationRelativeToSelf(View v, int[] location) { 2839 getLocationInWindow(location); 2840 int x = location[0]; 2841 int y = location[1]; 2842 2843 v.getLocationInWindow(location); 2844 int vX = location[0]; 2845 int vY = location[1]; 2846 2847 location[0] = vX - x; 2848 location[1] = vY - y; 2849 } 2850 2851 public void onDragEnter(DragObject d) { 2852 mDragEnforcer.onDragEnter(); 2853 mCreateUserFolderOnDrop = false; 2854 mAddToExistingFolderOnDrop = false; 2855 2856 mDropToLayout = null; 2857 CellLayout layout = getCurrentDropLayout(); 2858 setCurrentDropLayout(layout); 2859 setCurrentDragOverlappingLayout(layout); 2860 2861 // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we 2862 // don't need to show the outlines 2863 if (LauncherAppState.getInstance().isScreenLarge()) { 2864 showOutlines(); 2865 } 2866 } 2867 2868 /** Return a rect that has the cellWidth/cellHeight (left, top), and 2869 * widthGap/heightGap (right, bottom) */ 2870 static Rect getCellLayoutMetrics(Launcher launcher, int orientation) { 2871 LauncherAppState app = LauncherAppState.getInstance(); 2872 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 2873 2874 Resources res = launcher.getResources(); 2875 Display display = launcher.getWindowManager().getDefaultDisplay(); 2876 Point smallestSize = new Point(); 2877 Point largestSize = new Point(); 2878 display.getCurrentSizeRange(smallestSize, largestSize); 2879 int countX = (int) grid.numColumns; 2880 int countY = (int) grid.numRows; 2881 int constrainedLongEdge = largestSize.y; 2882 int constrainedShortEdge = smallestSize.y; 2883 if (orientation == CellLayout.LANDSCAPE) { 2884 if (mLandscapeCellLayoutMetrics == null) { 2885 Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE); 2886 int width = constrainedLongEdge - padding.left - padding.right; 2887 int height = constrainedShortEdge - padding.top - padding.bottom; 2888 mLandscapeCellLayoutMetrics = new Rect(); 2889 mLandscapeCellLayoutMetrics.set( 2890 grid.calculateCellWidth(width, countX), 2891 grid.calculateCellHeight(height, countY), 0, 0); 2892 } 2893 return mLandscapeCellLayoutMetrics; 2894 } else if (orientation == CellLayout.PORTRAIT) { 2895 if (mPortraitCellLayoutMetrics == null) { 2896 Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT); 2897 int width = constrainedShortEdge - padding.left - padding.right; 2898 int height = constrainedLongEdge - padding.top - padding.bottom; 2899 mPortraitCellLayoutMetrics = new Rect(); 2900 mPortraitCellLayoutMetrics.set( 2901 grid.calculateCellWidth(width, countX), 2902 grid.calculateCellHeight(height, countY), 0, 0); 2903 } 2904 return mPortraitCellLayoutMetrics; 2905 } 2906 return null; 2907 } 2908 2909 public void onDragExit(DragObject d) { 2910 mDragEnforcer.onDragExit(); 2911 2912 // Here we store the final page that will be dropped to, if the workspace in fact 2913 // receives the drop 2914 if (mInScrollArea) { 2915 if (isPageMoving()) { 2916 // If the user drops while the page is scrolling, we should use that page as the 2917 // destination instead of the page that is being hovered over. 2918 mDropToLayout = (CellLayout) getPageAt(getNextPage()); 2919 } else { 2920 mDropToLayout = mDragOverlappingLayout; 2921 } 2922 } else { 2923 mDropToLayout = mDragTargetLayout; 2924 } 2925 2926 if (mDragMode == DRAG_MODE_CREATE_FOLDER) { 2927 mCreateUserFolderOnDrop = true; 2928 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) { 2929 mAddToExistingFolderOnDrop = true; 2930 } 2931 2932 // Reset the scroll area and previous drag target 2933 onResetScrollArea(); 2934 setCurrentDropLayout(null); 2935 setCurrentDragOverlappingLayout(null); 2936 2937 mSpringLoadedDragController.cancel(); 2938 2939 if (!mIsPageMoving) { 2940 hideOutlines(); 2941 } 2942 } 2943 2944 void setCurrentDropLayout(CellLayout layout) { 2945 if (mDragTargetLayout != null) { 2946 mDragTargetLayout.revertTempState(); 2947 mDragTargetLayout.onDragExit(); 2948 } 2949 mDragTargetLayout = layout; 2950 if (mDragTargetLayout != null) { 2951 mDragTargetLayout.onDragEnter(); 2952 } 2953 cleanupReorder(true); 2954 cleanupFolderCreation(); 2955 setCurrentDropOverCell(-1, -1); 2956 } 2957 2958 void setCurrentDragOverlappingLayout(CellLayout layout) { 2959 if (mDragOverlappingLayout != null) { 2960 mDragOverlappingLayout.setIsDragOverlapping(false); 2961 } 2962 mDragOverlappingLayout = layout; 2963 if (mDragOverlappingLayout != null) { 2964 mDragOverlappingLayout.setIsDragOverlapping(true); 2965 } 2966 invalidate(); 2967 } 2968 2969 void setCurrentDropOverCell(int x, int y) { 2970 if (x != mDragOverX || y != mDragOverY) { 2971 mDragOverX = x; 2972 mDragOverY = y; 2973 setDragMode(DRAG_MODE_NONE); 2974 } 2975 } 2976 2977 void setDragMode(int dragMode) { 2978 if (dragMode != mDragMode) { 2979 if (dragMode == DRAG_MODE_NONE) { 2980 cleanupAddToFolder(); 2981 // We don't want to cancel the re-order alarm every time the target cell changes 2982 // as this feels to slow / unresponsive. 2983 cleanupReorder(false); 2984 cleanupFolderCreation(); 2985 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) { 2986 cleanupReorder(true); 2987 cleanupFolderCreation(); 2988 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) { 2989 cleanupAddToFolder(); 2990 cleanupReorder(true); 2991 } else if (dragMode == DRAG_MODE_REORDER) { 2992 cleanupAddToFolder(); 2993 cleanupFolderCreation(); 2994 } 2995 mDragMode = dragMode; 2996 } 2997 } 2998 2999 private void cleanupFolderCreation() { 3000 if (mDragFolderRingAnimator != null) { 3001 mDragFolderRingAnimator.animateToNaturalState(); 3002 } 3003 mFolderCreationAlarm.cancelAlarm(); 3004 } 3005 3006 private void cleanupAddToFolder() { 3007 if (mDragOverFolderIcon != null) { 3008 mDragOverFolderIcon.onDragExit(null); 3009 mDragOverFolderIcon = null; 3010 } 3011 } 3012 3013 private void cleanupReorder(boolean cancelAlarm) { 3014 // Any pending reorders are canceled 3015 if (cancelAlarm) { 3016 mReorderAlarm.cancelAlarm(); 3017 } 3018 mLastReorderX = -1; 3019 mLastReorderY = -1; 3020 } 3021 3022 /* 3023 * 3024 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 3025 * coordinate space. The argument xy is modified with the return result. 3026 * 3027 * if cachedInverseMatrix is not null, this method will just use that matrix instead of 3028 * computing it itself; we use this to avoid redundant matrix inversions in 3029 * findMatchingPageForDragOver 3030 * 3031 */ 3032 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { 3033 xy[0] = xy[0] - v.getLeft(); 3034 xy[1] = xy[1] - v.getTop(); 3035 } 3036 3037 boolean isPointInSelfOverHotseat(int x, int y, Rect r) { 3038 if (r == null) { 3039 r = new Rect(); 3040 } 3041 mTempPt[0] = x; 3042 mTempPt[1] = y; 3043 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); 3044 3045 LauncherAppState app = LauncherAppState.getInstance(); 3046 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 3047 r = grid.getHotseatRect(); 3048 if (r.contains(mTempPt[0], mTempPt[1])) { 3049 return true; 3050 } 3051 return false; 3052 } 3053 3054 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { 3055 mTempPt[0] = (int) xy[0]; 3056 mTempPt[1] = (int) xy[1]; 3057 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); 3058 mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt); 3059 3060 xy[0] = mTempPt[0]; 3061 xy[1] = mTempPt[1]; 3062 } 3063 3064 /* 3065 * 3066 * Convert the 2D coordinate xy from this CellLayout's coordinate space to 3067 * the parent View's coordinate space. The argument xy is modified with the return result. 3068 * 3069 */ 3070 void mapPointFromChildToSelf(View v, float[] xy) { 3071 xy[0] += v.getLeft(); 3072 xy[1] += v.getTop(); 3073 } 3074 3075 static private float squaredDistance(float[] point1, float[] point2) { 3076 float distanceX = point1[0] - point2[0]; 3077 float distanceY = point2[1] - point2[1]; 3078 return distanceX * distanceX + distanceY * distanceY; 3079 } 3080 3081 /* 3082 * 3083 * This method returns the CellLayout that is currently being dragged to. In order to drag 3084 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second 3085 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one 3086 * 3087 * Return null if no CellLayout is currently being dragged over 3088 * 3089 */ 3090 private CellLayout findMatchingPageForDragOver( 3091 DragView dragView, float originX, float originY, boolean exact) { 3092 // We loop through all the screens (ie CellLayouts) and see which ones overlap 3093 // with the item being dragged and then choose the one that's closest to the touch point 3094 final int screenCount = getChildCount(); 3095 CellLayout bestMatchingScreen = null; 3096 float smallestDistSoFar = Float.MAX_VALUE; 3097 3098 for (int i = 0; i < screenCount; i++) { 3099 // The custom content screen is not a valid drag over option 3100 if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) { 3101 continue; 3102 } 3103 3104 CellLayout cl = (CellLayout) getChildAt(i); 3105 3106 final float[] touchXy = {originX, originY}; 3107 // Transform the touch coordinates to the CellLayout's local coordinates 3108 // If the touch point is within the bounds of the cell layout, we can return immediately 3109 cl.getMatrix().invert(mTempInverseMatrix); 3110 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); 3111 3112 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 3113 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 3114 return cl; 3115 } 3116 3117 if (!exact) { 3118 // Get the center of the cell layout in screen coordinates 3119 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; 3120 cellLayoutCenter[0] = cl.getWidth()/2; 3121 cellLayoutCenter[1] = cl.getHeight()/2; 3122 mapPointFromChildToSelf(cl, cellLayoutCenter); 3123 3124 touchXy[0] = originX; 3125 touchXy[1] = originY; 3126 3127 // Calculate the distance between the center of the CellLayout 3128 // and the touch point 3129 float dist = squaredDistance(touchXy, cellLayoutCenter); 3130 3131 if (dist < smallestDistSoFar) { 3132 smallestDistSoFar = dist; 3133 bestMatchingScreen = cl; 3134 } 3135 } 3136 } 3137 return bestMatchingScreen; 3138 } 3139 3140 // This is used to compute the visual center of the dragView. This point is then 3141 // used to visualize drop locations and determine where to drop an item. The idea is that 3142 // the visual center represents the user's interpretation of where the item is, and hence 3143 // is the appropriate point to use when determining drop location. 3144 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, 3145 DragView dragView, float[] recycle) { 3146 float res[]; 3147 if (recycle == null) { 3148 res = new float[2]; 3149 } else { 3150 res = recycle; 3151 } 3152 3153 // First off, the drag view has been shifted in a way that is not represented in the 3154 // x and y values or the x/yOffsets. Here we account for that shift. 3155 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX); 3156 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); 3157 3158 // These represent the visual top and left of drag view if a dragRect was provided. 3159 // If a dragRect was not provided, then they correspond to the actual view left and 3160 // top, as the dragRect is in that case taken to be the entire dragView. 3161 // R.dimen.dragViewOffsetY. 3162 int left = x - xOffset; 3163 int top = y - yOffset; 3164 3165 // In order to find the visual center, we shift by half the dragRect 3166 res[0] = left + dragView.getDragRegion().width() / 2; 3167 res[1] = top + dragView.getDragRegion().height() / 2; 3168 3169 return res; 3170 } 3171 3172 private boolean isDragWidget(DragObject d) { 3173 return (d.dragInfo instanceof LauncherAppWidgetInfo || 3174 d.dragInfo instanceof PendingAddWidgetInfo); 3175 } 3176 private boolean isExternalDragWidget(DragObject d) { 3177 return d.dragSource != this && isDragWidget(d); 3178 } 3179 3180 public void onDragOver(DragObject d) { 3181 // Skip drag over events while we are dragging over side pages 3182 if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return; 3183 3184 Rect r = new Rect(); 3185 CellLayout layout = null; 3186 ItemInfo item = (ItemInfo) d.dragInfo; 3187 3188 // Ensure that we have proper spans for the item that we are dropping 3189 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); 3190 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, 3191 d.dragView, mDragViewVisualCenter); 3192 3193 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 3194 // Identify whether we have dragged over a side page 3195 if (isSmall()) { 3196 if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) { 3197 if (isPointInSelfOverHotseat(d.x, d.y, r)) { 3198 layout = mLauncher.getHotseat().getLayout(); 3199 } 3200 } 3201 if (layout == null) { 3202 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false); 3203 } 3204 if (layout != mDragTargetLayout) { 3205 setCurrentDropLayout(layout); 3206 setCurrentDragOverlappingLayout(layout); 3207 3208 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED); 3209 if (isInSpringLoadedMode) { 3210 if (mLauncher.isHotseatLayout(layout)) { 3211 mSpringLoadedDragController.cancel(); 3212 } else { 3213 mSpringLoadedDragController.setAlarm(mDragTargetLayout); 3214 } 3215 } 3216 } 3217 } else { 3218 // Test to see if we are over the hotseat otherwise just use the current page 3219 if (mLauncher.getHotseat() != null && !isDragWidget(d)) { 3220 if (isPointInSelfOverHotseat(d.x, d.y, r)) { 3221 layout = mLauncher.getHotseat().getLayout(); 3222 } 3223 } 3224 if (layout == null) { 3225 layout = getCurrentDropLayout(); 3226 } 3227 if (layout != mDragTargetLayout) { 3228 setCurrentDropLayout(layout); 3229 setCurrentDragOverlappingLayout(layout); 3230 } 3231 } 3232 3233 // Handle the drag over 3234 if (mDragTargetLayout != null) { 3235 // We want the point to be mapped to the dragTarget. 3236 if (mLauncher.isHotseatLayout(mDragTargetLayout)) { 3237 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); 3238 } else { 3239 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null); 3240 } 3241 3242 ItemInfo info = (ItemInfo) d.dragInfo; 3243 3244 int minSpanX = item.spanX; 3245 int minSpanY = item.spanY; 3246 if (item.minSpanX > 0 && item.minSpanY > 0) { 3247 minSpanX = item.minSpanX; 3248 minSpanY = item.minSpanY; 3249 } 3250 3251 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 3252 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, 3253 mDragTargetLayout, mTargetCell); 3254 int reorderX = mTargetCell[0]; 3255 int reorderY = mTargetCell[1]; 3256 3257 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]); 3258 3259 float targetCellDistance = mDragTargetLayout.getDistanceFromCell( 3260 mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); 3261 3262 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], 3263 mTargetCell[1]); 3264 3265 manageFolderFeedback(info, mDragTargetLayout, mTargetCell, 3266 targetCellDistance, dragOverView); 3267 3268 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) 3269 mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, 3270 item.spanY, child, mTargetCell); 3271 3272 if (!nearestDropOccupied) { 3273 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, 3274 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], 3275 mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, 3276 d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion()); 3277 } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) 3278 && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX || 3279 mLastReorderY != reorderY)) { 3280 3281 // Otherwise, if we aren't adding to or creating a folder and there's no pending 3282 // reorder, then we schedule a reorder 3283 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, 3284 minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child); 3285 mReorderAlarm.setOnAlarmListener(listener); 3286 mReorderAlarm.setAlarm(REORDER_TIMEOUT); 3287 } 3288 3289 if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER || 3290 !nearestDropOccupied) { 3291 if (mDragTargetLayout != null) { 3292 mDragTargetLayout.revertTempState(); 3293 } 3294 } 3295 } 3296 } 3297 3298 private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout, 3299 int[] targetCell, float distance, View dragOverView) { 3300 boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance, 3301 false); 3302 3303 if (mDragMode == DRAG_MODE_NONE && userFolderPending && 3304 !mFolderCreationAlarm.alarmPending()) { 3305 mFolderCreationAlarm.setOnAlarmListener(new 3306 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1])); 3307 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); 3308 return; 3309 } 3310 3311 boolean willAddToFolder = 3312 willAddToExistingUserFolder(info, targetLayout, targetCell, distance); 3313 3314 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) { 3315 mDragOverFolderIcon = ((FolderIcon) dragOverView); 3316 mDragOverFolderIcon.onDragEnter(info); 3317 if (targetLayout != null) { 3318 targetLayout.clearDragOutlines(); 3319 } 3320 setDragMode(DRAG_MODE_ADD_TO_FOLDER); 3321 return; 3322 } 3323 3324 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) { 3325 setDragMode(DRAG_MODE_NONE); 3326 } 3327 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) { 3328 setDragMode(DRAG_MODE_NONE); 3329 } 3330 3331 return; 3332 } 3333 3334 class FolderCreationAlarmListener implements OnAlarmListener { 3335 CellLayout layout; 3336 int cellX; 3337 int cellY; 3338 3339 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { 3340 this.layout = layout; 3341 this.cellX = cellX; 3342 this.cellY = cellY; 3343 } 3344 3345 public void onAlarm(Alarm alarm) { 3346 if (mDragFolderRingAnimator == null) { 3347 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null); 3348 } 3349 mDragFolderRingAnimator.setCell(cellX, cellY); 3350 mDragFolderRingAnimator.setCellLayout(layout); 3351 mDragFolderRingAnimator.animateToAcceptState(); 3352 layout.showFolderAccept(mDragFolderRingAnimator); 3353 layout.clearDragOutlines(); 3354 setDragMode(DRAG_MODE_CREATE_FOLDER); 3355 } 3356 } 3357 3358 class ReorderAlarmListener implements OnAlarmListener { 3359 float[] dragViewCenter; 3360 int minSpanX, minSpanY, spanX, spanY; 3361 DragView dragView; 3362 View child; 3363 3364 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, 3365 int spanY, DragView dragView, View child) { 3366 this.dragViewCenter = dragViewCenter; 3367 this.minSpanX = minSpanX; 3368 this.minSpanY = minSpanY; 3369 this.spanX = spanX; 3370 this.spanY = spanY; 3371 this.child = child; 3372 this.dragView = dragView; 3373 } 3374 3375 public void onAlarm(Alarm alarm) { 3376 int[] resultSpan = new int[2]; 3377 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 3378 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout, 3379 mTargetCell); 3380 mLastReorderX = mTargetCell[0]; 3381 mLastReorderY = mTargetCell[1]; 3382 3383 mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0], 3384 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, 3385 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER); 3386 3387 if (mTargetCell[0] < 0 || mTargetCell[1] < 0) { 3388 mDragTargetLayout.revertTempState(); 3389 } else { 3390 setDragMode(DRAG_MODE_REORDER); 3391 } 3392 3393 boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; 3394 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, 3395 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], 3396 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, 3397 dragView.getDragVisualizeOffset(), dragView.getDragRegion()); 3398 } 3399 } 3400 3401 @Override 3402 public void getHitRectRelativeToDragLayer(Rect outRect) { 3403 // We want the workspace to have the whole area of the display (it will find the correct 3404 // cell layout to drop to in the existing drag/drop logic. 3405 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect); 3406 } 3407 3408 /** 3409 * Add the item specified by dragInfo to the given layout. 3410 * @return true if successful 3411 */ 3412 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) { 3413 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) { 3414 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false); 3415 return true; 3416 } 3417 mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout)); 3418 return false; 3419 } 3420 3421 private void onDropExternal(int[] touchXY, Object dragInfo, 3422 CellLayout cellLayout, boolean insertAtFirst) { 3423 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null); 3424 } 3425 3426 /** 3427 * Drop an item that didn't originate on one of the workspace screens. 3428 * It may have come from Launcher (e.g. from all apps or customize), or it may have 3429 * come from another app altogether. 3430 * 3431 * NOTE: This can also be called when we are outside of a drag event, when we want 3432 * to add an item to one of the workspace screens. 3433 */ 3434 private void onDropExternal(final int[] touchXY, final Object dragInfo, 3435 final CellLayout cellLayout, boolean insertAtFirst, DragObject d) { 3436 final Runnable exitSpringLoadedRunnable = new Runnable() { 3437 @Override 3438 public void run() { 3439 mLauncher.exitSpringLoadedDragModeDelayed(true, false, null); 3440 } 3441 }; 3442 3443 ItemInfo info = (ItemInfo) dragInfo; 3444 int spanX = info.spanX; 3445 int spanY = info.spanY; 3446 if (mDragInfo != null) { 3447 spanX = mDragInfo.spanX; 3448 spanY = mDragInfo.spanY; 3449 } 3450 3451 final long container = mLauncher.isHotseatLayout(cellLayout) ? 3452 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 3453 LauncherSettings.Favorites.CONTAINER_DESKTOP; 3454 final long screenId = getIdForScreen(cellLayout); 3455 if (!mLauncher.isHotseatLayout(cellLayout) 3456 && screenId != getScreenIdForPageIndex(mCurrentPage) 3457 && mState != State.SPRING_LOADED) { 3458 snapToScreenId(screenId, null); 3459 } 3460 3461 if (info instanceof PendingAddItemInfo) { 3462 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo; 3463 3464 boolean findNearestVacantCell = true; 3465 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { 3466 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, 3467 cellLayout, mTargetCell); 3468 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 3469 mDragViewVisualCenter[1], mTargetCell); 3470 if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell, 3471 distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo, 3472 cellLayout, mTargetCell, distance)) { 3473 findNearestVacantCell = false; 3474 } 3475 } 3476 3477 final ItemInfo item = (ItemInfo) d.dragInfo; 3478 boolean updateWidgetSize = false; 3479 if (findNearestVacantCell) { 3480 int minSpanX = item.spanX; 3481 int minSpanY = item.spanY; 3482 if (item.minSpanX > 0 && item.minSpanY > 0) { 3483 minSpanX = item.minSpanX; 3484 minSpanY = item.minSpanY; 3485 } 3486 int[] resultSpan = new int[2]; 3487 mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], 3488 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY, 3489 null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL); 3490 3491 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) { 3492 updateWidgetSize = true; 3493 } 3494 item.spanX = resultSpan[0]; 3495 item.spanY = resultSpan[1]; 3496 } 3497 3498 Runnable onAnimationCompleteRunnable = new Runnable() { 3499 @Override 3500 public void run() { 3501 // When dragging and dropping from customization tray, we deal with creating 3502 // widgets/shortcuts/folders in a slightly different way 3503 switch (pendingInfo.itemType) { 3504 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 3505 int span[] = new int[2]; 3506 span[0] = item.spanX; 3507 span[1] = item.spanY; 3508 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo, 3509 container, screenId, mTargetCell, span, null); 3510 break; 3511 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 3512 mLauncher.processShortcutFromDrop(pendingInfo.componentName, 3513 container, screenId, mTargetCell, null); 3514 break; 3515 default: 3516 throw new IllegalStateException("Unknown item type: " + 3517 pendingInfo.itemType); 3518 } 3519 } 3520 }; 3521 View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET 3522 ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; 3523 3524 if (finalView instanceof AppWidgetHostView && updateWidgetSize) { 3525 AppWidgetHostView awhv = (AppWidgetHostView) finalView; 3526 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX, 3527 item.spanY); 3528 } 3529 3530 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR; 3531 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && 3532 ((PendingAddWidgetInfo) pendingInfo).info.configure != null) { 3533 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN; 3534 } 3535 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, 3536 animationStyle, finalView, true); 3537 } else { 3538 // This is for other drag/drop cases, like dragging from All Apps 3539 View view = null; 3540 3541 switch (info.itemType) { 3542 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 3543 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 3544 if (info.container == NO_ID && info instanceof AppInfo) { 3545 // Came from all apps -- make a copy 3546 info = new ShortcutInfo((AppInfo) info); 3547 } 3548 view = mLauncher.createShortcut(R.layout.application, cellLayout, 3549 (ShortcutInfo) info); 3550 break; 3551 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 3552 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, 3553 (FolderInfo) info, mIconCache); 3554 break; 3555 default: 3556 throw new IllegalStateException("Unknown item type: " + info.itemType); 3557 } 3558 3559 // First we find the cell nearest to point at which the item is 3560 // dropped, without any consideration to whether there is an item there. 3561 if (touchXY != null) { 3562 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, 3563 cellLayout, mTargetCell); 3564 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], 3565 mDragViewVisualCenter[1], mTargetCell); 3566 d.postAnimationRunnable = exitSpringLoadedRunnable; 3567 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, 3568 true, d.dragView, d.postAnimationRunnable)) { 3569 return; 3570 } 3571 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, 3572 true)) { 3573 return; 3574 } 3575 } 3576 3577 if (touchXY != null) { 3578 // when dragging and dropping, just find the closest free spot 3579 mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], 3580 (int) mDragViewVisualCenter[1], 1, 1, 1, 1, 3581 null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL); 3582 } else { 3583 cellLayout.findCellForSpan(mTargetCell, 1, 1); 3584 } 3585 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX, 3586 info.spanY, insertAtFirst); 3587 cellLayout.onDropChild(view); 3588 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 3589 cellLayout.getShortcutsAndWidgets().measureChild(view); 3590 3591 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, 3592 lp.cellX, lp.cellY); 3593 3594 if (d.dragView != null) { 3595 // We wrap the animation call in the temporary set and reset of the current 3596 // cellLayout to its final transform -- this means we animate the drag view to 3597 // the correct final location. 3598 setFinalTransitionTransform(cellLayout); 3599 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, 3600 exitSpringLoadedRunnable); 3601 resetTransitionTransform(cellLayout); 3602 } 3603 } 3604 } 3605 3606 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { 3607 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX, 3608 widgetInfo.spanY, widgetInfo, false); 3609 int visibility = layout.getVisibility(); 3610 layout.setVisibility(VISIBLE); 3611 3612 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY); 3613 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY); 3614 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], 3615 Bitmap.Config.ARGB_8888); 3616 Canvas c = new Canvas(b); 3617 3618 layout.measure(width, height); 3619 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]); 3620 layout.draw(c); 3621 c.setBitmap(null); 3622 layout.setVisibility(visibility); 3623 return b; 3624 } 3625 3626 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY, 3627 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, 3628 boolean external, boolean scale) { 3629 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final 3630 // location and size on the home screen. 3631 int spanX = info.spanX; 3632 int spanY = info.spanY; 3633 3634 Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY); 3635 loc[0] = r.left; 3636 loc[1] = r.top; 3637 3638 setFinalTransitionTransform(layout); 3639 float cellLayoutScale = 3640 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true); 3641 resetTransitionTransform(layout); 3642 3643 float dragViewScaleX; 3644 float dragViewScaleY; 3645 if (scale) { 3646 dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); 3647 dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight(); 3648 } else { 3649 dragViewScaleX = 1f; 3650 dragViewScaleY = 1f; 3651 } 3652 3653 // The animation will scale the dragView about its center, so we need to center about 3654 // the final location. 3655 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2; 3656 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2; 3657 3658 scaleXY[0] = dragViewScaleX * cellLayoutScale; 3659 scaleXY[1] = dragViewScaleY * cellLayoutScale; 3660 } 3661 3662 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView, 3663 final Runnable onCompleteRunnable, int animationType, final View finalView, 3664 boolean external) { 3665 Rect from = new Rect(); 3666 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from); 3667 3668 int[] finalPos = new int[2]; 3669 float scaleXY[] = new float[2]; 3670 boolean scalePreview = !(info instanceof PendingAddShortcutInfo); 3671 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell, 3672 external, scalePreview); 3673 3674 Resources res = mLauncher.getResources(); 3675 int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200; 3676 3677 // In the case where we've prebound the widget, we remove it from the DragLayer 3678 if (finalView instanceof AppWidgetHostView && external) { 3679 Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView"); 3680 mLauncher.getDragLayer().removeView(finalView); 3681 } 3682 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) { 3683 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView); 3684 dragView.setCrossFadeBitmap(crossFadeBitmap); 3685 dragView.crossFade((int) (duration * 0.8f)); 3686 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) { 3687 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]); 3688 } 3689 3690 DragLayer dragLayer = mLauncher.getDragLayer(); 3691 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) { 3692 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f, 3693 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration); 3694 } else { 3695 int endStyle; 3696 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) { 3697 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE; 3698 } else { 3699 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;; 3700 } 3701 3702 Runnable onComplete = new Runnable() { 3703 @Override 3704 public void run() { 3705 if (finalView != null) { 3706 finalView.setVisibility(VISIBLE); 3707 } 3708 if (onCompleteRunnable != null) { 3709 onCompleteRunnable.run(); 3710 } 3711 } 3712 }; 3713 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0], 3714 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle, 3715 duration, this); 3716 } 3717 } 3718 3719 public void setFinalTransitionTransform(CellLayout layout) { 3720 if (isSwitchingState()) { 3721 mCurrentScale = getScaleX(); 3722 setScaleX(mNewScale); 3723 setScaleY(mNewScale); 3724 } 3725 } 3726 public void resetTransitionTransform(CellLayout layout) { 3727 if (isSwitchingState()) { 3728 setScaleX(mCurrentScale); 3729 setScaleY(mCurrentScale); 3730 } 3731 } 3732 3733 /** 3734 * Return the current {@link CellLayout}, correctly picking the destination 3735 * screen while a scroll is in progress. 3736 */ 3737 public CellLayout getCurrentDropLayout() { 3738 return (CellLayout) getChildAt(getNextPage()); 3739 } 3740 3741 /** 3742 * Return the current CellInfo describing our current drag; this method exists 3743 * so that Launcher can sync this object with the correct info when the activity is created/ 3744 * destroyed 3745 * 3746 */ 3747 public CellLayout.CellInfo getDragInfo() { 3748 return mDragInfo; 3749 } 3750 3751 public int getRestorePage() { 3752 return getNextPage() - numCustomPages(); 3753 } 3754 3755 /** 3756 * Calculate the nearest cell where the given object would be dropped. 3757 * 3758 * pixelX and pixelY should be in the coordinate system of layout 3759 */ 3760 private int[] findNearestArea(int pixelX, int pixelY, 3761 int spanX, int spanY, CellLayout layout, int[] recycle) { 3762 return layout.findNearestArea( 3763 pixelX, pixelY, spanX, spanY, recycle); 3764 } 3765 3766 void setup(DragController dragController) { 3767 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); 3768 mDragController = dragController; 3769 3770 // hardware layers on children are enabled on startup, but should be disabled until 3771 // needed 3772 updateChildrenLayersEnabled(false); 3773 setWallpaperDimension(); 3774 } 3775 3776 /** 3777 * Called at the end of a drag which originated on the workspace. 3778 */ 3779 public void onDropCompleted(final View target, final DragObject d, 3780 final boolean isFlingToDelete, final boolean success) { 3781 if (mDeferDropAfterUninstall) { 3782 mDeferredAction = new Runnable() { 3783 public void run() { 3784 onDropCompleted(target, d, isFlingToDelete, success); 3785 mDeferredAction = null; 3786 } 3787 }; 3788 return; 3789 } 3790 3791 boolean beingCalledAfterUninstall = mDeferredAction != null; 3792 3793 if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) { 3794 if (target != this && mDragInfo != null) { 3795 CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell); 3796 if (parentCell != null) { 3797 parentCell.removeView(mDragInfo.cell); 3798 } 3799 if (mDragInfo.cell instanceof DropTarget) { 3800 mDragController.removeDropTarget((DropTarget) mDragInfo.cell); 3801 } 3802 // If we move the item to anything not on the Workspace, check if any empty 3803 // screens need to be removed. If we dropped back on the workspace, this will 3804 // be done post drop animation. 3805 stripEmptyScreens(); 3806 } 3807 } else if (mDragInfo != null) { 3808 CellLayout cellLayout; 3809 if (mLauncher.isHotseatLayout(target)) { 3810 cellLayout = mLauncher.getHotseat().getLayout(); 3811 } else { 3812 cellLayout = getScreenWithId(mDragInfo.screenId); 3813 } 3814 cellLayout.onDropChild(mDragInfo.cell); 3815 } 3816 if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful)) 3817 && mDragInfo.cell != null) { 3818 mDragInfo.cell.setVisibility(VISIBLE); 3819 } 3820 mDragOutline = null; 3821 mDragInfo = null; 3822 } 3823 3824 public void deferCompleteDropAfterUninstallActivity() { 3825 mDeferDropAfterUninstall = true; 3826 } 3827 3828 /// maybe move this into a smaller part 3829 public void onUninstallActivityReturned(boolean success) { 3830 mDeferDropAfterUninstall = false; 3831 mUninstallSuccessful = success; 3832 if (mDeferredAction != null) { 3833 mDeferredAction.run(); 3834 } 3835 } 3836 3837 void updateItemLocationsInDatabase(CellLayout cl) { 3838 int count = cl.getShortcutsAndWidgets().getChildCount(); 3839 3840 long screenId = getIdForScreen(cl); 3841 int container = Favorites.CONTAINER_DESKTOP; 3842 3843 if (mLauncher.isHotseatLayout(cl)) { 3844 screenId = -1; 3845 container = Favorites.CONTAINER_HOTSEAT; 3846 } 3847 3848 for (int i = 0; i < count; i++) { 3849 View v = cl.getShortcutsAndWidgets().getChildAt(i); 3850 ItemInfo info = (ItemInfo) v.getTag(); 3851 // Null check required as the AllApps button doesn't have an item info 3852 if (info != null && info.requiresDbUpdate) { 3853 info.requiresDbUpdate = false; 3854 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX, 3855 info.cellY, info.spanX, info.spanY); 3856 } 3857 } 3858 } 3859 3860 ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplicates) { 3861 ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>(); 3862 getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, false); 3863 int count = getChildCount(); 3864 for (int i = 0; i < count; i++) { 3865 CellLayout cl = (CellLayout) getChildAt(i); 3866 getUniqueIntents(cl, uniqueIntents, duplicates, false); 3867 } 3868 return uniqueIntents; 3869 } 3870 3871 void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents, 3872 ArrayList<ComponentName> duplicates, boolean stripDuplicates) { 3873 int count = cl.getShortcutsAndWidgets().getChildCount(); 3874 3875 ArrayList<View> children = new ArrayList<View>(); 3876 for (int i = 0; i < count; i++) { 3877 View v = cl.getShortcutsAndWidgets().getChildAt(i); 3878 children.add(v); 3879 } 3880 3881 for (int i = 0; i < count; i++) { 3882 View v = children.get(i); 3883 ItemInfo info = (ItemInfo) v.getTag(); 3884 // Null check required as the AllApps button doesn't have an item info 3885 if (info instanceof ShortcutInfo) { 3886 ShortcutInfo si = (ShortcutInfo) info; 3887 ComponentName cn = si.intent.getComponent(); 3888 3889 Uri dataUri = si.intent.getData(); 3890 // If dataUri is not null / empty or if this component isn't one that would 3891 // have previously showed up in the AllApps list, then this is a widget-type 3892 // shortcut, so ignore it. 3893 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) { 3894 continue; 3895 } 3896 3897 if (!uniqueIntents.contains(cn)) { 3898 uniqueIntents.add(cn); 3899 } else { 3900 if (stripDuplicates) { 3901 cl.removeViewInLayout(v); 3902 LauncherModel.deleteItemFromDatabase(mLauncher, si); 3903 } 3904 if (duplicates != null) { 3905 duplicates.add(cn); 3906 } 3907 } 3908 } 3909 if (v instanceof FolderIcon) { 3910 FolderIcon fi = (FolderIcon) v; 3911 ArrayList<View> items = fi.getFolder().getItemsInReadingOrder(); 3912 for (int j = 0; j < items.size(); j++) { 3913 if (items.get(j).getTag() instanceof ShortcutInfo) { 3914 ShortcutInfo si = (ShortcutInfo) items.get(j).getTag(); 3915 ComponentName cn = si.intent.getComponent(); 3916 3917 Uri dataUri = si.intent.getData(); 3918 // If dataUri is not null / empty or if this component isn't one that would 3919 // have previously showed up in the AllApps list, then this is a widget-type 3920 // shortcut, so ignore it. 3921 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) { 3922 continue; 3923 } 3924 3925 if (!uniqueIntents.contains(cn)) { 3926 uniqueIntents.add(cn); 3927 } else { 3928 if (stripDuplicates) { 3929 fi.getFolderInfo().remove(si); 3930 LauncherModel.deleteItemFromDatabase(mLauncher, si); 3931 } 3932 if (duplicates != null) { 3933 duplicates.add(cn); 3934 } 3935 } 3936 } 3937 } 3938 } 3939 } 3940 } 3941 3942 void saveWorkspaceToDb() { 3943 saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout()); 3944 int count = getChildCount(); 3945 for (int i = 0; i < count; i++) { 3946 CellLayout cl = (CellLayout) getChildAt(i); 3947 saveWorkspaceScreenToDb(cl); 3948 } 3949 } 3950 3951 void saveWorkspaceScreenToDb(CellLayout cl) { 3952 int count = cl.getShortcutsAndWidgets().getChildCount(); 3953 3954 long screenId = getIdForScreen(cl); 3955 int container = Favorites.CONTAINER_DESKTOP; 3956 3957 Hotseat hotseat = mLauncher.getHotseat(); 3958 if (mLauncher.isHotseatLayout(cl)) { 3959 screenId = -1; 3960 container = Favorites.CONTAINER_HOTSEAT; 3961 } 3962 3963 for (int i = 0; i < count; i++) { 3964 View v = cl.getShortcutsAndWidgets().getChildAt(i); 3965 ItemInfo info = (ItemInfo) v.getTag(); 3966 // Null check required as the AllApps button doesn't have an item info 3967 if (info != null) { 3968 int cellX = info.cellX; 3969 int cellY = info.cellY; 3970 if (container == Favorites.CONTAINER_HOTSEAT) { 3971 cellX = hotseat.getCellXFromOrder((int) info.screenId); 3972 cellY = hotseat.getCellYFromOrder((int) info.screenId); 3973 } 3974 LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX, 3975 cellY, false); 3976 } 3977 if (v instanceof FolderIcon) { 3978 FolderIcon fi = (FolderIcon) v; 3979 fi.getFolder().addItemLocationsInDatabase(); 3980 } 3981 } 3982 } 3983 3984 @Override 3985 public boolean supportsFlingToDelete() { 3986 return true; 3987 } 3988 3989 @Override 3990 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { 3991 // Do nothing 3992 } 3993 3994 @Override 3995 public void onFlingToDeleteCompleted() { 3996 // Do nothing 3997 } 3998 3999 public boolean isDropEnabled() { 4000 return true; 4001 } 4002 4003 @Override 4004 protected void onRestoreInstanceState(Parcelable state) { 4005 super.onRestoreInstanceState(state); 4006 Launcher.setScreen(mCurrentPage); 4007 } 4008 4009 @Override 4010 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 4011 // We don't dispatch restoreInstanceState to our children using this code path. 4012 // Some pages will be restored immediately as their items are bound immediately, and 4013 // others we will need to wait until after their items are bound. 4014 mSavedStates = container; 4015 } 4016 4017 public void restoreInstanceStateForChild(int child) { 4018 if (mSavedStates != null) { 4019 mRestoredPages.add(child); 4020 CellLayout cl = (CellLayout) getChildAt(child); 4021 cl.restoreInstanceState(mSavedStates); 4022 } 4023 } 4024 4025 public void restoreInstanceStateForRemainingPages() { 4026 int count = getChildCount(); 4027 for (int i = 0; i < count; i++) { 4028 if (!mRestoredPages.contains(i)) { 4029 restoreInstanceStateForChild(i); 4030 } 4031 } 4032 mRestoredPages.clear(); 4033 } 4034 4035 @Override 4036 public void scrollLeft() { 4037 if (!isSmall() && !mIsSwitchingState) { 4038 super.scrollLeft(); 4039 } 4040 Folder openFolder = getOpenFolder(); 4041 if (openFolder != null) { 4042 openFolder.completeDragExit(); 4043 } 4044 } 4045 4046 @Override 4047 public void scrollRight() { 4048 if (!isSmall() && !mIsSwitchingState) { 4049 super.scrollRight(); 4050 } 4051 Folder openFolder = getOpenFolder(); 4052 if (openFolder != null) { 4053 openFolder.completeDragExit(); 4054 } 4055 } 4056 4057 @Override 4058 public boolean onEnterScrollArea(int x, int y, int direction) { 4059 // Ignore the scroll area if we are dragging over the hot seat 4060 boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext()); 4061 if (mLauncher.getHotseat() != null && isPortrait) { 4062 Rect r = new Rect(); 4063 mLauncher.getHotseat().getHitRect(r); 4064 if (r.contains(x, y)) { 4065 return false; 4066 } 4067 } 4068 4069 boolean result = false; 4070 if (!isSmall() && !mIsSwitchingState && getOpenFolder() == null) { 4071 mInScrollArea = true; 4072 4073 final int page = getNextPage() + 4074 (direction == DragController.SCROLL_LEFT ? -1 : 1); 4075 // We always want to exit the current layout to ensure parity of enter / exit 4076 setCurrentDropLayout(null); 4077 4078 if (0 <= page && page < getChildCount()) { 4079 // Ensure that we are not dragging over to the custom content screen 4080 if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) { 4081 return false; 4082 } 4083 4084 CellLayout layout = (CellLayout) getChildAt(page); 4085 setCurrentDragOverlappingLayout(layout); 4086 4087 // Workspace is responsible for drawing the edge glow on adjacent pages, 4088 // so we need to redraw the workspace when this may have changed. 4089 invalidate(); 4090 result = true; 4091 } 4092 } 4093 return result; 4094 } 4095 4096 @Override 4097 public boolean onExitScrollArea() { 4098 boolean result = false; 4099 if (mInScrollArea) { 4100 invalidate(); 4101 CellLayout layout = getCurrentDropLayout(); 4102 setCurrentDropLayout(layout); 4103 setCurrentDragOverlappingLayout(layout); 4104 4105 result = true; 4106 mInScrollArea = false; 4107 } 4108 return result; 4109 } 4110 4111 private void onResetScrollArea() { 4112 setCurrentDragOverlappingLayout(null); 4113 mInScrollArea = false; 4114 } 4115 4116 /** 4117 * Returns a specific CellLayout 4118 */ 4119 CellLayout getParentCellLayoutForView(View v) { 4120 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts(); 4121 for (CellLayout layout : layouts) { 4122 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) { 4123 return layout; 4124 } 4125 } 4126 return null; 4127 } 4128 4129 /** 4130 * Returns a list of all the CellLayouts in the workspace. 4131 */ 4132 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() { 4133 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>(); 4134 int screenCount = getChildCount(); 4135 for (int screen = 0; screen < screenCount; screen++) { 4136 layouts.add(((CellLayout) getChildAt(screen))); 4137 } 4138 if (mLauncher.getHotseat() != null) { 4139 layouts.add(mLauncher.getHotseat().getLayout()); 4140 } 4141 return layouts; 4142 } 4143 4144 /** 4145 * We should only use this to search for specific children. Do not use this method to modify 4146 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from 4147 * the hotseat and workspace pages 4148 */ 4149 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() { 4150 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 4151 new ArrayList<ShortcutAndWidgetContainer>(); 4152 int screenCount = getChildCount(); 4153 for (int screen = 0; screen < screenCount; screen++) { 4154 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets()); 4155 } 4156 if (mLauncher.getHotseat() != null) { 4157 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets()); 4158 } 4159 return childrenLayouts; 4160 } 4161 4162 public Folder getFolderForTag(Object tag) { 4163 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 4164 getAllShortcutAndWidgetContainers(); 4165 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 4166 int count = layout.getChildCount(); 4167 for (int i = 0; i < count; i++) { 4168 View child = layout.getChildAt(i); 4169 if (child instanceof Folder) { 4170 Folder f = (Folder) child; 4171 if (f.getInfo() == tag && f.getInfo().opened) { 4172 return f; 4173 } 4174 } 4175 } 4176 } 4177 return null; 4178 } 4179 4180 public View getViewForTag(Object tag) { 4181 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 4182 getAllShortcutAndWidgetContainers(); 4183 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 4184 int count = layout.getChildCount(); 4185 for (int i = 0; i < count; i++) { 4186 View child = layout.getChildAt(i); 4187 if (child.getTag() == tag) { 4188 return child; 4189 } 4190 } 4191 } 4192 return null; 4193 } 4194 4195 void clearDropTargets() { 4196 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = 4197 getAllShortcutAndWidgetContainers(); 4198 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 4199 int childCount = layout.getChildCount(); 4200 for (int j = 0; j < childCount; j++) { 4201 View v = layout.getChildAt(j); 4202 if (v instanceof DropTarget) { 4203 mDragController.removeDropTarget((DropTarget) v); 4204 } 4205 } 4206 } 4207 } 4208 4209 // Removes ALL items that match a given package name, this is usually called when a package 4210 // has been removed and we want to remove all components (widgets, shortcuts, apps) that 4211 // belong to that package. 4212 void removeItemsByPackageName(final ArrayList<String> packages) { 4213 final HashSet<String> packageNames = new HashSet<String>(); 4214 packageNames.addAll(packages); 4215 4216 // Filter out all the ItemInfos that this is going to affect 4217 final HashSet<ItemInfo> infos = new HashSet<ItemInfo>(); 4218 final HashSet<ComponentName> cns = new HashSet<ComponentName>(); 4219 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts(); 4220 for (CellLayout layoutParent : cellLayouts) { 4221 ViewGroup layout = layoutParent.getShortcutsAndWidgets(); 4222 int childCount = layout.getChildCount(); 4223 for (int i = 0; i < childCount; ++i) { 4224 View view = layout.getChildAt(i); 4225 infos.add((ItemInfo) view.getTag()); 4226 } 4227 } 4228 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() { 4229 @Override 4230 public boolean filterItem(ItemInfo parent, ItemInfo info, 4231 ComponentName cn) { 4232 if (packageNames.contains(cn.getPackageName())) { 4233 cns.add(cn); 4234 return true; 4235 } 4236 return false; 4237 } 4238 }; 4239 LauncherModel.filterItemInfos(infos, filter); 4240 4241 // Remove the affected components 4242 removeItemsByComponentName(cns); 4243 } 4244 4245 // Removes items that match the application info specified, when applications are removed 4246 // as a part of an update, this is called to ensure that other widgets and application 4247 // shortcuts are not removed. 4248 void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) { 4249 // Just create a hash table of all the specific components that this will affect 4250 HashSet<ComponentName> cns = new HashSet<ComponentName>(); 4251 for (AppInfo info : appInfos) { 4252 cns.add(info.componentName); 4253 } 4254 4255 // Remove all the things 4256 removeItemsByComponentName(cns); 4257 } 4258 4259 void removeItemsByComponentName(final HashSet<ComponentName> componentNames) { 4260 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts(); 4261 for (final CellLayout layoutParent: cellLayouts) { 4262 final ViewGroup layout = layoutParent.getShortcutsAndWidgets(); 4263 4264 final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>(); 4265 for (int j = 0; j < layout.getChildCount(); j++) { 4266 final View view = layout.getChildAt(j); 4267 children.put((ItemInfo) view.getTag(), view); 4268 } 4269 4270 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 4271 final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = 4272 new HashMap<FolderInfo, ArrayList<ShortcutInfo>>(); 4273 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() { 4274 @Override 4275 public boolean filterItem(ItemInfo parent, ItemInfo info, 4276 ComponentName cn) { 4277 if (parent instanceof FolderInfo) { 4278 if (componentNames.contains(cn)) { 4279 FolderInfo folder = (FolderInfo) parent; 4280 ArrayList<ShortcutInfo> appsToRemove; 4281 if (folderAppsToRemove.containsKey(folder)) { 4282 appsToRemove = folderAppsToRemove.get(folder); 4283 } else { 4284 appsToRemove = new ArrayList<ShortcutInfo>(); 4285 folderAppsToRemove.put(folder, appsToRemove); 4286 } 4287 appsToRemove.add((ShortcutInfo) info); 4288 return true; 4289 } 4290 } else { 4291 if (componentNames.contains(cn)) { 4292 childrenToRemove.add(children.get(info)); 4293 return true; 4294 } 4295 } 4296 return false; 4297 } 4298 }; 4299 LauncherModel.filterItemInfos(children.keySet(), filter); 4300 4301 // Remove all the apps from their folders 4302 for (FolderInfo folder : folderAppsToRemove.keySet()) { 4303 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder); 4304 for (ShortcutInfo info : appsToRemove) { 4305 folder.remove(info); 4306 } 4307 } 4308 4309 // Remove all the other children 4310 for (View child : childrenToRemove) { 4311 // Note: We can not remove the view directly from CellLayoutChildren as this 4312 // does not re-mark the spaces as unoccupied. 4313 layoutParent.removeViewInLayout(child); 4314 if (child instanceof DropTarget) { 4315 mDragController.removeDropTarget((DropTarget) child); 4316 } 4317 } 4318 4319 if (childrenToRemove.size() > 0) { 4320 layout.requestLayout(); 4321 layout.invalidate(); 4322 } 4323 } 4324 4325 // Strip all the empty screens 4326 stripEmptyScreens(); 4327 } 4328 4329 void updateShortcuts(ArrayList<AppInfo> apps) { 4330 ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers(); 4331 for (ShortcutAndWidgetContainer layout: childrenLayouts) { 4332 int childCount = layout.getChildCount(); 4333 for (int j = 0; j < childCount; j++) { 4334 final View view = layout.getChildAt(j); 4335 Object tag = view.getTag(); 4336 4337 if (LauncherModel.isShortcutInfoUpdateable((ItemInfo) tag)) { 4338 ShortcutInfo info = (ShortcutInfo) tag; 4339 4340 final Intent intent = info.intent; 4341 final ComponentName name = intent.getComponent(); 4342 final int appCount = apps.size(); 4343 for (int k = 0; k < appCount; k++) { 4344 AppInfo app = apps.get(k); 4345 if (app.componentName.equals(name)) { 4346 BubbleTextView shortcut = (BubbleTextView) view; 4347 info.updateIcon(mIconCache); 4348 info.title = app.title.toString(); 4349 shortcut.applyFromShortcutInfo(info, mIconCache); 4350 } 4351 } 4352 } 4353 } 4354 } 4355 } 4356 4357 private void moveToScreen(int page, boolean animate) { 4358 if (!isSmall()) { 4359 if (animate) { 4360 snapToPage(page); 4361 } else { 4362 setCurrentPage(page); 4363 } 4364 } 4365 View child = getChildAt(page); 4366 if (child != null) { 4367 child.requestFocus(); 4368 } 4369 } 4370 4371 void moveToDefaultScreen(boolean animate) { 4372 moveToScreen(mDefaultPage, animate); 4373 } 4374 4375 void moveToCustomContentScreen(boolean animate) { 4376 if (hasCustomContent()) { 4377 int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID); 4378 if (animate) { 4379 snapToPage(ccIndex); 4380 } else { 4381 setCurrentPage(ccIndex); 4382 } 4383 View child = getChildAt(ccIndex); 4384 if (child != null) { 4385 child.requestFocus(); 4386 } 4387 } 4388 } 4389 4390 @Override 4391 protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) { 4392 long screenId = getScreenIdForPageIndex(pageIndex); 4393 if (screenId == EXTRA_EMPTY_SCREEN_ID) { 4394 int count = mScreenOrder.size() - numCustomPages(); 4395 if (count > 1) { 4396 return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current, 4397 R.drawable.ic_pageindicator_add); 4398 } 4399 } 4400 4401 return super.getPageIndicatorMarker(pageIndex); 4402 } 4403 4404 @Override 4405 public void syncPages() { 4406 } 4407 4408 @Override 4409 public void syncPageItems(int page, boolean immediate) { 4410 } 4411 4412 protected String getPageIndicatorDescription() { 4413 String settings = getResources().getString(R.string.settings_button_text); 4414 return getCurrentPageDescription() + ", " + settings; 4415 } 4416 4417 protected String getCurrentPageDescription() { 4418 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 4419 int delta = numCustomPages(); 4420 if (hasCustomContent() && getNextPage() == 0) { 4421 return mCustomContentDescription; 4422 } 4423 return String.format(getContext().getString(R.string.workspace_scroll_format), 4424 page + 1 - delta, getChildCount() - delta); 4425 } 4426 4427 public void getLocationInDragLayer(int[] loc) { 4428 mLauncher.getDragLayer().getLocationInDragLayer(this, loc); 4429 } 4430} 4431