1 package com.android.launcher3.util; 2 3 import android.app.WallpaperManager; 4 import android.os.IBinder; 5 import android.util.Log; 6 import android.view.Choreographer; 7 import android.view.animation.DecelerateInterpolator; 8 import android.view.animation.Interpolator; 9 10 import com.android.launcher3.Utilities; 11 import com.android.launcher3.Workspace; 12 13 /** 14 * Utility class to handle wallpaper scrolling along with workspace. 15 */ 16 public class WallpaperOffsetInterpolator implements Choreographer.FrameCallback { 17 private static final String TAG = "WPOffsetInterpolator"; 18 private static final int ANIMATION_DURATION = 250; 19 20 // Don't use all the wallpaper for parallax until you have at least this many pages 21 private static final int MIN_PARALLAX_PAGE_SPAN = 4; 22 23 private final Choreographer mChoreographer; 24 private final Interpolator mInterpolator; 25 private final WallpaperManager mWallpaperManager; 26 private final Workspace mWorkspace; 27 private final boolean mIsRtl; 28 29 private IBinder mWindowToken; 30 private boolean mWallpaperIsLiveWallpaper; 31 private float mLastSetWallpaperOffsetSteps = 0; 32 33 private float mFinalOffset = 0.0f; 34 private float mCurrentOffset = 0.5f; // to force an initial update 35 private boolean mWaitingForUpdate; 36 private boolean mLockedToDefaultPage; 37 38 private boolean mAnimating; 39 private long mAnimationStartTime; 40 private float mAnimationStartOffset; 41 int mNumScreens; 42 int mNumPagesForWallpaperParallax; 43 WallpaperOffsetInterpolator(Workspace workspace)44 public WallpaperOffsetInterpolator(Workspace workspace) { 45 mChoreographer = Choreographer.getInstance(); 46 mInterpolator = new DecelerateInterpolator(1.5f); 47 48 mWorkspace = workspace; 49 mWallpaperManager = WallpaperManager.getInstance(workspace.getContext()); 50 mIsRtl = Utilities.isRtl(workspace.getResources()); 51 } 52 53 @Override doFrame(long frameTimeNanos)54 public void doFrame(long frameTimeNanos) { 55 updateOffset(false); 56 } 57 updateOffset(boolean force)58 private void updateOffset(boolean force) { 59 if (mWaitingForUpdate || force) { 60 mWaitingForUpdate = false; 61 if (computeScrollOffset() && mWindowToken != null) { 62 try { 63 mWallpaperManager.setWallpaperOffsets(mWindowToken, getCurrX(), 0.5f); 64 setWallpaperOffsetSteps(); 65 } catch (IllegalArgumentException e) { 66 Log.e(TAG, "Error updating wallpaper offset: " + e); 67 } 68 } 69 } 70 } 71 72 /** 73 * Locks the wallpaper offset to the offset in the default state of Launcher. 74 */ setLockToDefaultPage(boolean lockToDefaultPage)75 public void setLockToDefaultPage(boolean lockToDefaultPage) { 76 mLockedToDefaultPage = lockToDefaultPage; 77 } 78 isLockedToDefaultPage()79 public boolean isLockedToDefaultPage() { 80 return mLockedToDefaultPage; 81 } 82 computeScrollOffset()83 public boolean computeScrollOffset() { 84 final float oldOffset = mCurrentOffset; 85 if (mAnimating) { 86 long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime; 87 float t0 = durationSinceAnimation / (float) ANIMATION_DURATION; 88 float t1 = mInterpolator.getInterpolation(t0); 89 mCurrentOffset = mAnimationStartOffset + 90 (mFinalOffset - mAnimationStartOffset) * t1; 91 mAnimating = durationSinceAnimation < ANIMATION_DURATION; 92 } else { 93 mCurrentOffset = mFinalOffset; 94 } 95 96 if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) { 97 scheduleUpdate(); 98 } 99 if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) { 100 return true; 101 } 102 return false; 103 } 104 105 /** 106 * TODO: do different behavior if it's a live wallpaper? 107 */ wallpaperOffsetForScroll(int scroll)108 public float wallpaperOffsetForScroll(int scroll) { 109 // To match the default wallpaper behavior in the system, we default to either the left 110 // or right edge on initialization 111 int numScrollingPages = getNumScreensExcludingEmptyAndCustom(); 112 if (mLockedToDefaultPage || numScrollingPages <= 1) { 113 return mIsRtl ? 1f : 0f; 114 } 115 116 // Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN workspace 117 // screens, not including the custom screen, and empty screens (if > MIN_PARALLAX_PAGE_SPAN) 118 if (mWallpaperIsLiveWallpaper) { 119 mNumPagesForWallpaperParallax = numScrollingPages; 120 } else { 121 mNumPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages); 122 } 123 124 // Offset by the custom screen 125 int leftPageIndex; 126 int rightPageIndex; 127 if (mIsRtl) { 128 rightPageIndex = mWorkspace.numCustomPages(); 129 leftPageIndex = rightPageIndex + numScrollingPages - 1; 130 } else { 131 leftPageIndex = mWorkspace.numCustomPages(); 132 rightPageIndex = leftPageIndex + numScrollingPages - 1; 133 } 134 135 // Calculate the scroll range 136 int leftPageScrollX = mWorkspace.getScrollForPage(leftPageIndex); 137 int rightPageScrollX = mWorkspace.getScrollForPage(rightPageIndex); 138 int scrollRange = rightPageScrollX - leftPageScrollX; 139 if (scrollRange == 0) { 140 return 0f; 141 } 142 143 // Sometimes the left parameter of the pages is animated during a layout transition; 144 // this parameter offsets it to keep the wallpaper from animating as well 145 int adjustedScroll = scroll - leftPageScrollX - 146 mWorkspace.getLayoutTransitionOffsetForPage(0); 147 float offset = Utilities.boundToRange((float) adjustedScroll / scrollRange, 0f, 1f); 148 149 // The offset is now distributed 0..1 between the left and right pages that we care about, 150 // so we just map that between the pages that we are using for parallax 151 float rtlOffset = 0; 152 if (mIsRtl) { 153 // In RTL, the pages are right aligned, so adjust the offset from the end 154 rtlOffset = (float) ((mNumPagesForWallpaperParallax - 1) - (numScrollingPages - 1)) / 155 (mNumPagesForWallpaperParallax - 1); 156 } 157 return rtlOffset + offset * 158 ((float) (numScrollingPages - 1) / (mNumPagesForWallpaperParallax - 1)); 159 } 160 wallpaperOffsetForCurrentScroll()161 private float wallpaperOffsetForCurrentScroll() { 162 return wallpaperOffsetForScroll(mWorkspace.getScrollX()); 163 } 164 numEmptyScreensToIgnore()165 private int numEmptyScreensToIgnore() { 166 int numScrollingPages = mWorkspace.getChildCount() - mWorkspace.numCustomPages(); 167 if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreen()) { 168 return 1; 169 } else { 170 return 0; 171 } 172 } 173 getNumScreensExcludingEmptyAndCustom()174 private int getNumScreensExcludingEmptyAndCustom() { 175 return mWorkspace.getChildCount() - numEmptyScreensToIgnore() - mWorkspace.numCustomPages(); 176 } 177 syncWithScroll()178 public void syncWithScroll() { 179 float offset = wallpaperOffsetForCurrentScroll(); 180 setFinalX(offset); 181 updateOffset(true); 182 } 183 getCurrX()184 public float getCurrX() { 185 return mCurrentOffset; 186 } 187 getFinalX()188 public float getFinalX() { 189 return mFinalOffset; 190 } 191 animateToFinal()192 private void animateToFinal() { 193 mAnimating = true; 194 mAnimationStartOffset = mCurrentOffset; 195 mAnimationStartTime = System.currentTimeMillis(); 196 } 197 setWallpaperOffsetSteps()198 private void setWallpaperOffsetSteps() { 199 // Set wallpaper offset steps (1 / (number of screens - 1)) 200 float xOffset = 1.0f / (mNumPagesForWallpaperParallax - 1); 201 if (xOffset != mLastSetWallpaperOffsetSteps) { 202 mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f); 203 mLastSetWallpaperOffsetSteps = xOffset; 204 } 205 } 206 setFinalX(float x)207 public void setFinalX(float x) { 208 scheduleUpdate(); 209 mFinalOffset = Math.max(0f, Math.min(x, 1f)); 210 if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) { 211 if (mNumScreens > 0 && Float.compare(mCurrentOffset, mFinalOffset) != 0) { 212 // Don't animate if we're going from 0 screens, or if the final offset is the same 213 // as the current offset 214 animateToFinal(); 215 } 216 mNumScreens = getNumScreensExcludingEmptyAndCustom(); 217 } 218 } 219 scheduleUpdate()220 private void scheduleUpdate() { 221 if (!mWaitingForUpdate) { 222 mChoreographer.postFrameCallback(this); 223 mWaitingForUpdate = true; 224 } 225 } 226 jumpToFinal()227 public void jumpToFinal() { 228 mCurrentOffset = mFinalOffset; 229 } 230 onResume()231 public void onResume() { 232 mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null; 233 // Force the wallpaper offset steps to be set again, because another app might have changed 234 // them 235 mLastSetWallpaperOffsetSteps = 0f; 236 } 237 setWindowToken(IBinder token)238 public void setWindowToken(IBinder token) { 239 mWindowToken = token; 240 } 241 }