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