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