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