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