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