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