• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.launcher3.util;
2 
3 import android.app.WallpaperManager;
4 import android.content.BroadcastReceiver;
5 import android.content.Context;
6 import android.content.Intent;
7 import android.content.IntentFilter;
8 import android.os.Handler;
9 import android.os.IBinder;
10 import android.os.Message;
11 import android.os.SystemClock;
12 import android.util.Log;
13 import android.view.animation.Interpolator;
14 
15 import com.android.launcher3.Utilities;
16 import com.android.launcher3.Workspace;
17 import com.android.launcher3.anim.Interpolators;
18 
19 /**
20  * Utility class to handle wallpaper scrolling along with workspace.
21  */
22 public class WallpaperOffsetInterpolator extends BroadcastReceiver {
23 
24     private static final int[] sTempInt = new int[2];
25     private static final String TAG = "WPOffsetInterpolator";
26     private static final int ANIMATION_DURATION = 250;
27 
28     // Don't use all the wallpaper for parallax until you have at least this many pages
29     private static final int MIN_PARALLAX_PAGE_SPAN = 4;
30 
31     private final Workspace mWorkspace;
32     private final boolean mIsRtl;
33     private final Handler mHandler;
34 
35     private boolean mRegistered = false;
36     private IBinder mWindowToken;
37     private boolean mWallpaperIsLiveWallpaper;
38 
39     private boolean mLockedToDefaultPage;
40     private int mNumScreens;
41 
WallpaperOffsetInterpolator(Workspace workspace)42     public WallpaperOffsetInterpolator(Workspace workspace) {
43         mWorkspace = workspace;
44         mIsRtl = Utilities.isRtl(workspace.getResources());
45         mHandler = new OffsetHandler(workspace.getContext());
46     }
47 
48     /**
49      * Locks the wallpaper offset to the offset in the default state of Launcher.
50      */
setLockToDefaultPage(boolean lockToDefaultPage)51     public void setLockToDefaultPage(boolean lockToDefaultPage) {
52         mLockedToDefaultPage = lockToDefaultPage;
53     }
54 
isLockedToDefaultPage()55     public boolean isLockedToDefaultPage() {
56         return mLockedToDefaultPage;
57     }
58 
59     /**
60      * Computes the wallpaper offset as an int ratio (out[0] / out[1])
61      *
62      * TODO: do different behavior if it's  a live wallpaper?
63      */
wallpaperOffsetForScroll(int scroll, int numScrollingPages, final int[] out)64     private void wallpaperOffsetForScroll(int scroll, int numScrollingPages, final int[] out) {
65         out[1] = 1;
66 
67         // To match the default wallpaper behavior in the system, we default to either the left
68         // or right edge on initialization
69         if (mLockedToDefaultPage || numScrollingPages <= 1) {
70             out[0] =  mIsRtl ? 1 : 0;
71             return;
72         }
73 
74         // Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN workspace
75         // screens, not including the custom screen, and empty screens (if > MIN_PARALLAX_PAGE_SPAN)
76         int numPagesForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollingPages :
77                         Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages);
78 
79         // Offset by the custom screen
80         int leftPageIndex;
81         int rightPageIndex;
82         if (mIsRtl) {
83             rightPageIndex = 0;
84             leftPageIndex = rightPageIndex + numScrollingPages - 1;
85         } else {
86             leftPageIndex = 0;
87             rightPageIndex = leftPageIndex + numScrollingPages - 1;
88         }
89 
90         // Calculate the scroll range
91         int leftPageScrollX = mWorkspace.getScrollForPage(leftPageIndex);
92         int rightPageScrollX = mWorkspace.getScrollForPage(rightPageIndex);
93         int scrollRange = rightPageScrollX - leftPageScrollX;
94         if (scrollRange <= 0) {
95             out[0] = 0;
96             return;
97         }
98 
99         // Sometimes the left parameter of the pages is animated during a layout transition;
100         // this parameter offsets it to keep the wallpaper from animating as well
101         int adjustedScroll = scroll - leftPageScrollX -
102                 mWorkspace.getLayoutTransitionOffsetForPage(0);
103         adjustedScroll = Utilities.boundToRange(adjustedScroll, 0, scrollRange);
104         out[1] = (numPagesForWallpaperParallax - 1) * scrollRange;
105 
106         // The offset is now distributed 0..1 between the left and right pages that we care about,
107         // so we just map that between the pages that we are using for parallax
108         int rtlOffset = 0;
109         if (mIsRtl) {
110             // In RTL, the pages are right aligned, so adjust the offset from the end
111             rtlOffset = out[1] - (numScrollingPages - 1) * scrollRange;
112         }
113         out[0] = rtlOffset + adjustedScroll * (numScrollingPages - 1);
114     }
115 
wallpaperOffsetForScroll(int scroll)116     public float wallpaperOffsetForScroll(int scroll) {
117         wallpaperOffsetForScroll(scroll, getNumScreensExcludingEmpty(), sTempInt);
118         return ((float) sTempInt[0]) / sTempInt[1];
119     }
120 
getNumScreensExcludingEmpty()121     private int getNumScreensExcludingEmpty() {
122         int numScrollingPages = mWorkspace.getChildCount();
123         if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreen()) {
124             return numScrollingPages - 1;
125         } else {
126             return numScrollingPages;
127         }
128     }
129 
syncWithScroll()130     public void syncWithScroll() {
131         int numScreens = getNumScreensExcludingEmpty();
132         wallpaperOffsetForScroll(mWorkspace.getScrollX(), numScreens, sTempInt);
133         Message msg = Message.obtain(mHandler, MSG_UPDATE_OFFSET, sTempInt[0], sTempInt[1],
134                 mWindowToken);
135         if (numScreens != mNumScreens) {
136             if (mNumScreens > 0) {
137                 // Don't animate if we're going from 0 screens
138                 msg.what = MSG_START_ANIMATION;
139             }
140             mNumScreens = numScreens;
141             updateOffset();
142         }
143         msg.sendToTarget();
144     }
145 
updateOffset()146     private void updateOffset() {
147         int numPagesForWallpaperParallax;
148         if (mWallpaperIsLiveWallpaper) {
149             numPagesForWallpaperParallax = mNumScreens;
150         } else {
151             numPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
152         }
153         Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, numPagesForWallpaperParallax, 0,
154                 mWindowToken).sendToTarget();
155     }
156 
jumpToFinal()157     public void jumpToFinal() {
158         Message.obtain(mHandler, MSG_JUMP_TO_FINAL, mWindowToken).sendToTarget();
159     }
160 
setWindowToken(IBinder token)161     public void setWindowToken(IBinder token) {
162         mWindowToken = token;
163         if (mWindowToken == null && mRegistered) {
164             mWorkspace.getContext().unregisterReceiver(this);
165             mRegistered = false;
166         } else if (mWindowToken != null && !mRegistered) {
167             mWorkspace.getContext()
168                     .registerReceiver(this, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
169             onReceive(mWorkspace.getContext(), null);
170             mRegistered = true;
171         }
172     }
173 
174     @Override
onReceive(Context context, Intent intent)175     public void onReceive(Context context, Intent intent) {
176         mWallpaperIsLiveWallpaper =
177                 WallpaperManager.getInstance(mWorkspace.getContext()).getWallpaperInfo() != null;
178         updateOffset();
179     }
180 
181     private static final int MSG_START_ANIMATION = 1;
182     private static final int MSG_UPDATE_OFFSET = 2;
183     private static final int MSG_APPLY_OFFSET = 3;
184     private static final int MSG_SET_NUM_PARALLAX = 4;
185     private static final int MSG_JUMP_TO_FINAL = 5;
186 
187     private static class OffsetHandler extends Handler {
188 
189         private final Interpolator mInterpolator;
190         private final WallpaperManager mWM;
191 
192         private float mCurrentOffset = 0.5f; // to force an initial update
193         private boolean mAnimating;
194         private long mAnimationStartTime;
195         private float mAnimationStartOffset;
196 
197         private float mFinalOffset;
198         private float mOffsetX;
199 
OffsetHandler(Context context)200         public OffsetHandler(Context context) {
201             super(UiThreadHelper.getBackgroundLooper());
202             mInterpolator = Interpolators.DEACCEL_1_5;
203             mWM = WallpaperManager.getInstance(context);
204         }
205 
206         @Override
handleMessage(Message msg)207         public void handleMessage(Message msg) {
208             final IBinder token = (IBinder) msg.obj;
209             if (token == null) {
210                 return;
211             }
212 
213             switch (msg.what) {
214                 case MSG_START_ANIMATION: {
215                     mAnimating = true;
216                     mAnimationStartOffset = mCurrentOffset;
217                     mAnimationStartTime = msg.getWhen();
218                     // Follow through
219                 }
220                 case MSG_UPDATE_OFFSET:
221                     mFinalOffset = ((float) msg.arg1) / msg.arg2;
222                     // Follow through
223                 case MSG_APPLY_OFFSET: {
224                     float oldOffset = mCurrentOffset;
225                     if (mAnimating) {
226                         long durationSinceAnimation = SystemClock.uptimeMillis()
227                                 - mAnimationStartTime;
228                         float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
229                         float t1 = mInterpolator.getInterpolation(t0);
230                         mCurrentOffset = mAnimationStartOffset +
231                                 (mFinalOffset - mAnimationStartOffset) * t1;
232                         mAnimating = durationSinceAnimation < ANIMATION_DURATION;
233                     } else {
234                         mCurrentOffset = mFinalOffset;
235                     }
236 
237                     if (Float.compare(mCurrentOffset, oldOffset) != 0) {
238                         setOffsetSafely(token);
239                         // Force the wallpaper offset steps to be set again, because another app
240                         // might have changed them
241                         mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f);
242                     }
243                     if (mAnimating) {
244                         // If we are animating, keep updating the offset
245                         Message.obtain(this, MSG_APPLY_OFFSET, token).sendToTarget();
246                     }
247                     return;
248                 }
249                 case MSG_SET_NUM_PARALLAX: {
250                     // Set wallpaper offset steps (1 / (number of screens - 1))
251                     mOffsetX = 1.0f / (msg.arg1 - 1);
252                     mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f);
253                     return;
254                 }
255                 case MSG_JUMP_TO_FINAL: {
256                     if (Float.compare(mCurrentOffset, mFinalOffset) != 0) {
257                         mCurrentOffset = mFinalOffset;
258                         setOffsetSafely(token);
259                     }
260                     mAnimating = false;
261                     return;
262                 }
263             }
264         }
265 
266         private void setOffsetSafely(IBinder token) {
267             try {
268                 mWM.setWallpaperOffsets(token, mCurrentOffset, 0.5f);
269             } catch (IllegalArgumentException e) {
270                 Log.e(TAG, "Error updating wallpaper offset: " + e);
271             }
272         }
273     }
274 }