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