Federico Fuga

Engineering, Tech, Informatics & science

TouchWiz and the onOffsetChanged missing call

11 Jul 2013 17:38 +0000 Android Code

I am currently working on a Live Wallpaper project for a customer.

Interestingly, I found that though my app works perfectly on different devices - I have a Nexus7, Nexus10, a Galaxy Tab p1010, different chinese allWinner devices, and a Note II phone - my wallpaper can’t scroll when the screen is swiped on some Samsung Device.

Usually to detect the swiping on a Live Wallpaper, you use the[ WallpaperService.Engine.onOffsetChanged](http://developer.android.com/reference/android/service/wallpaper/WallpaperService.Engine.html#onOffsetsChanged(float, float, float, float, int, int)), i.e. overriding this method on your engine class.

Unfortunately, the Samsung launcher app, TouchWiz, seems to have a bug, or maybe it’s an undocumented/not compliant feature: the onOffsetChanged method is never reached, because simply they don’t call it (or maybe they don’t call the setWallpaperOffset, as explained in the Android Docs).

So you need to use some workaround for this.

Here’s mine: override the onTouchEvent method and simulate the scrolling.

The scroll will need a value for the offset, and it will be calculated from the swipe gesture; but unfortunately you’ll not be able to have the exact position of the screens, this means you’ll not detect if the screen “bounces back” as when you make a short swipe.

So your scrolling will be “desynchronized” with the screen. This could not be a problem, but you may prefer to use the correct method (onOffsetChanged) when available, and the “hack” when the o.s. doesn’t provide the call.

But you don’t have any way to know how the launcher app will behave, you need to start assuming that onOffsetChanged is NOT available and switch on standard method if it does.

This is how:

First, set a mScrollingWorking boolean as false. You’ll need to do this anytime the surface or view is created, because the use may change the launcher.

Then, whenever the onOffsetChanged is called, set it as “true”.

Then, override the onTouchEvent and if mScrollingWorking is false, simulate the scrolling.

This is my code:

 

      private boolean mScrollingWorking = false;
      private float tw_oldx, tw_fx, tw_sign;

      @Override
	public void onTouchEvent(MotionEvent event,
			GLWallpaperService.GLEngine engine) {
		if (!mScrollingWorking) {
			if (event.getPointerCount() < 1)
 				return;
                        float xinc = 0.0f;
 			float xstep = 0.333f;
 			float x;
 			switch(event.getAction()) {
 			case MotionEvent.ACTION_DOWN:
 				tw_fx = event.getX(0);
 				tw_oldx = tw_fx;
 				return;
 			case MotionEvent.ACTION_MOVE:
 				xinc = (event.getX(0) - tw_oldx) / (float)screen_width;
 				x = screen_x - xinc * xstep;
 				tw_oldx = event.getX(0);
 				break;
 			case MotionEvent.ACTION_UP:
 				tw_sign = Math.signum(event.getX(0) - tw_fx);
 				if (tw_sign > 0.0) {
					x = (float) (Math.floor(screen_x / xstep) * xstep);
				} else {
					x = (float) (Math.ceil(screen_x / xstep) * xstep);
				}
				break;
			default:
				return;
			}

				if (x < 0.0f)
 					x = 0.0f;
 				if (x > 1.0f)
					x = 1.0f;

			setOffset(x, 0.0f, xstep, 0.0f);
		}
	}




        public void onOffsetChanged(float x, float y, float xstep, float ystep, int xpo, int ypo) {
		mScrollingWorking = true;
		setOffset(x, y, xstep, ystep);
	}	

	private void setOffset(float x, float y, float xstep, float ystep) {
      // Here you can scroll the background
                screen_x = x;
        }

Note that the code on the ACTION_UP label will round the offset with the nearest page as it would do a working onOffsetChanged() call. But as said, you may have a situation where the launcher bounces back to the starting page and your code is scrolling on the next. Sorry: blame Samsung, not me :-)

Remember that on newer version, you’ll need to use setOffsetNotificationsEnabled(true) to enable raw touch event delivery.

UPDATE:

The original code has two problems: first, as hybridtv noted, some variable was missing and needed some clarification.

While the role of tw_oldx and screen_width are trivial, screen_x need to be explained. It stores the older offset, and it is updated in the setOffset() function.

Second, there was a bug in the code. TouchWiz and our code need to be as synchronized as possible while moving, so when the finger is released from the screen (touch_up) we need to round the current screen. Also the offset need to be limited between 0.0 and 1.0.

Thanks to hybridtv for pointing out.