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