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