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