Home | History | Annotate | Download | only in util
      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 
     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      */
     51     public void setLockToDefaultPage(boolean lockToDefaultPage) {
     52         mLockedToDefaultPage = lockToDefaultPage;
     53     }
     54 
     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      */
     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 
    116     public float wallpaperOffsetForScroll(int scroll) {
    117         wallpaperOffsetForScroll(scroll, getNumScreensExcludingEmpty(), sTempInt);
    118         return ((float) sTempInt[0]) / sTempInt[1];
    119     }
    120 
    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 
    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 
    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 
    157     public void jumpToFinal() {
    158         Message.obtain(mHandler, MSG_JUMP_TO_FINAL, mWindowToken).sendToTarget();
    159     }
    160 
    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
    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 
    200         public OffsetHandler(Context context) {
    201             super(UiThreadHelper.getBackgroundLooper());
    202             mInterpolator = Interpolators.DEACCEL_1_5;
    203             mWM = WallpaperManager.getInstance(context);
    204         }
    205 
    206         @Override
    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 }