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