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