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