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