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