Support back gesture on trackpad

TODO: change back animation API to consider trackpad events

Bug: 255697805
Test: https://recall.googleplex.com/projects/3388b17c-d22f-46f8-b140-a102690377b4/sessions/3f751251-07f2-4cce-b577-474746bf4eab
Change-Id: I50963a3a495c9f2069bdc9e2a8606bb403b51f1c
This commit is contained in:
Tracy Zhou
2022-12-30 17:58:03 +00:00
parent 0bf138d6cc
commit 397468da2e
8 changed files with 377 additions and 19 deletions

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.plugins;
import android.view.MotionEvent;
/** Handles both trackpad and touch events and report displacements in both axis's. */
public interface MotionEventsHandlerBase {
void onMotionEvent(MotionEvent ev);
float getDisplacementX(MotionEvent ev);
float getDisplacementY(MotionEvent ev);
String dump();
}

View File

@@ -48,6 +48,9 @@ public interface NavigationEdgeBackPlugin extends Plugin {
/** Sets the base LayoutParams for the UI. */
void setLayoutParams(WindowManager.LayoutParams layoutParams);
/** Sets the motion events handler for the plugin. */
default void setMotionEventsHandler(MotionEventsHandlerBase motionEventsHandler) {}
/** Updates the UI based on the motion events passed in device coordinates. */
void onMotionEvent(MotionEvent motionEvent);

View File

@@ -38,6 +38,7 @@ import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.MotionEventsHandlerBase
import com.android.systemui.plugins.NavigationEdgeBackPlugin
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -498,6 +499,10 @@ class BackPanelController private constructor(
windowManager.addView(mView, layoutParams)
}
override fun setMotionEventsHandler(motionEventsHandler: MotionEventsHandlerBase?) {
TODO("Not yet implemented")
}
private fun isFlung() = velocityTracker!!.run {
computeCurrentVelocity(1000)
abs(xVelocity) > MIN_FLING_VELOCITY

View File

@@ -18,6 +18,8 @@ package com.android.systemui.navigationbar.gestural;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import static com.android.systemui.navigationbar.gestural.Utilities.getTrackpadScale;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMotionEvent;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -241,6 +243,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private boolean mIsBackGestureAllowed;
private boolean mGestureBlockingActivityRunning;
private boolean mIsNewBackAffordanceEnabled;
private boolean mIsTrackpadGestureBackEnabled;
private boolean mIsButtonForceVisible;
private InputMonitor mInputMonitor;
@@ -269,6 +272,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES);
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
private final MotionEventsHandler mMotionEventsHandler;
private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
new NavigationEdgeBackPlugin.BackCallback() {
@@ -401,6 +405,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged);
mMotionEventsHandler = new MotionEventsHandler(featureFlags, getTrackpadScale(context));
updateCurrentUserResources();
}
@@ -578,6 +583,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
// Add a nav bar panel window
mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
mIsTrackpadGestureBackEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK);
resetEdgeBackPlugin();
mPluginManager.addPluginListener(
this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
@@ -611,6 +617,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
mEdgeBackPlugin = edgeBackPlugin;
mEdgeBackPlugin.setBackCallback(mBackCallback);
mEdgeBackPlugin.setMotionEventsHandler(mMotionEventsHandler);
mEdgeBackPlugin.setLayoutParams(createLayoutParams());
updateDisplaySize();
}
@@ -854,6 +861,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
private void onMotionEvent(MotionEvent ev) {
mMotionEventsHandler.onMotionEvent(ev);
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
if (DEBUG_MISSING_GESTURE) {
@@ -863,15 +871,24 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
// Verify if this is in within the touch region and we aren't in immersive mode, and
// either the bouncer is showing or the notification panel is hidden
mInputEventReceiver.setBatchingEnabled(false);
mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
boolean isTrackpadEvent = isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev);
if (isTrackpadEvent) {
// TODO: show the back arrow based on the direction of the swipe.
mIsOnLeftEdge = false;
} else {
mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
}
mMLResults = 0;
mLogGesture = false;
mInRejectedExclusion = false;
boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed && isWithinInsets
// Trackpad back gestures don't have zones, so we don't need to check if the down event
// is within insets.
mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
&& (isTrackpadEvent || isWithinInsets)
&& !mGestureBlockingActivityRunning
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
&& isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
&& (isTrackpadEvent || isWithinTouchRegion((int) ev.getX(), (int) ev.getY()));
if (mAllowGesture) {
mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
mEdgeBackPlugin.onMotionEvent(ev);
@@ -885,8 +902,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
// For debugging purposes, only log edge points
(isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format(
"Gesture [%d,alw=%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]",
System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge,
"Gesture [%d,alw=%B,%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]",
System.currentTimeMillis(), isTrackpadEvent, mAllowGesture, mIsOnLeftEdge,
mIsBackGestureAllowed,
QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize,
mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
@@ -920,8 +937,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mLogGesture = false;
return;
}
float dx = Math.abs(ev.getX() - mDownPoint.x);
float dy = Math.abs(ev.getY() - mDownPoint.y);
float dx = Math.abs(mMotionEventsHandler.getDisplacementX(ev));
float dy = Math.abs(mMotionEventsHandler.getDisplacementY(ev));
if (dy > dx && dy > mTouchSlop) {
if (mAllowGesture) {
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE);
@@ -1055,6 +1072,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
pw.println(" mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets));
pw.println(" mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets));
pw.println(" mEdgeBackPlugin=" + mEdgeBackPlugin);
pw.println(" mMotionEventsHandler=" + mMotionEventsHandler);
if (mEdgeBackPlugin != null) {
mEdgeBackPlugin.dump(pw);
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.navigationbar.gestural;
import static android.view.MotionEvent.AXIS_GESTURE_X_OFFSET;
import static android.view.MotionEvent.AXIS_GESTURE_Y_OFFSET;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMotionEvent;
import android.graphics.PointF;
import android.view.MotionEvent;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.MotionEventsHandlerBase;
/** Handles both trackpad and touch events and report displacements in both axis's. */
public class MotionEventsHandler implements MotionEventsHandlerBase {
private final boolean mIsTrackpadGestureBackEnabled;
private final int mScale;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private float mCurrentTrackpadOffsetX = 0;
private float mCurrentTrackpadOffsetY = 0;
public MotionEventsHandler(FeatureFlags featureFlags, int scale) {
mIsTrackpadGestureBackEnabled = featureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK);
mScale = scale;
}
@Override
public void onMotionEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
onActionDown(ev);
break;
case MotionEvent.ACTION_MOVE:
onActionMove(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
onActionUp(ev);
break;
default:
break;
}
}
private void onActionDown(MotionEvent ev) {
reset();
if (!isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev)) {
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
}
}
private void onActionMove(MotionEvent ev) {
updateMovements(ev);
}
private void onActionUp(MotionEvent ev) {
updateMovements(ev);
}
private void updateMovements(MotionEvent ev) {
if (isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev)) {
mCurrentTrackpadOffsetX += ev.getAxisValue(AXIS_GESTURE_X_OFFSET) * mScale;
mCurrentTrackpadOffsetY += ev.getAxisValue(AXIS_GESTURE_Y_OFFSET) * mScale;
} else {
mLastPos.set(ev.getX(), ev.getY());
}
}
private void reset() {
mDownPos.set(0, 0);
mLastPos.set(0, 0);
mCurrentTrackpadOffsetX = 0;
mCurrentTrackpadOffsetY = 0;
}
@Override
public float getDisplacementX(MotionEvent ev) {
return isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev) ? mCurrentTrackpadOffsetX
: mLastPos.x - mDownPos.x;
}
@Override
public float getDisplacementY(MotionEvent ev) {
return isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev) ? mCurrentTrackpadOffsetY
: mLastPos.y - mDownPos.y;
}
@Override
public String dump() {
return "mDownPos: " + mDownPos + ", mLastPos: " + mLastPos + ", mCurrentTrackpadOffsetX: "
+ mCurrentTrackpadOffsetX + ", mCurrentTrackpadOffsetY: " + mCurrentTrackpadOffsetY;
}
}

View File

@@ -55,6 +55,8 @@ import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.MotionEventsHandlerBase;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.statusbar.VibratorHelper;
@@ -200,8 +202,6 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
*/
private boolean mIsLeftPanel;
private float mStartX;
private float mStartY;
private float mCurrentAngle;
/**
* The current translation of the arrow
@@ -232,6 +232,8 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
private final Handler mHandler = new Handler();
private final Runnable mFailsafeRunnable = this::onFailsafe;
private MotionEventsHandlerBase mMotionEventsHandler;
private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
= new DynamicAnimation.OnAnimationEndListener() {
@Override
@@ -437,6 +439,11 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
mWindowManager.addView(this, mLayoutParams);
}
@Override
public void setMotionEventsHandler(MotionEventsHandlerBase motionEventsHandler) {
mMotionEventsHandler = motionEventsHandler;
}
/**
* Adjusts the sampling rect to conform to the actual visible bounding box of the arrow.
*/
@@ -481,8 +488,6 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
case MotionEvent.ACTION_DOWN:
mDragSlopPassed = false;
resetOnDown();
mStartX = event.getX();
mStartY = event.getY();
setVisibility(VISIBLE);
updatePosition(event.getY());
mRegionSamplingHelper.start(mSamplingRect);
@@ -726,10 +731,9 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
}
private void handleMoveEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
float touchTranslation = MathUtils.abs(x - mStartX);
float yOffset = y - mStartY;
float xOffset = mMotionEventsHandler.getDisplacementX(event);
float touchTranslation = MathUtils.abs(xOffset);
float yOffset = mMotionEventsHandler.getDisplacementY(event);
float delta = touchTranslation - mPreviousTouchTranslation;
if (Math.abs(delta) > 0) {
if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) {
@@ -790,16 +794,14 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
}
// Last if the direction in Y is bigger than X * 2 we also abort
if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
if (Math.abs(yOffset) > Math.abs(xOffset) * 2) {
triggerBack = false;
}
if (DEBUG_MISSING_GESTURE && mTriggerBack != triggerBack) {
Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=" + triggerBack
+ ", mTotalTouchDelta=" + mTotalTouchDelta
+ ", mMinDeltaForSwitch=" + mMinDeltaForSwitch
+ ", yOffset=" + yOffset
+ ", x=" + x
+ ", mStartX=" + mStartX);
+ ", yOffset=" + yOffset + mMotionEventsHandler.dump());
}
setTriggerBack(triggerBack, true /* animated */);

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.navigationbar.gestural;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
public final class Utilities {
private static final int TRACKPAD_GESTURE_SCALE = 60;
public static boolean isTrackpadMotionEvent(boolean isTrackpadGestureBackEnabled,
MotionEvent event) {
// TODO: ideally should use event.getClassification(), but currently only the move
// events get assigned the correct classification.
return isTrackpadGestureBackEnabled
&& (event.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN;
}
public static int getTrackpadScale(Context context) {
return ViewConfiguration.get(context).getScaledTouchSlop() * TRACKPAD_GESTURE_SCALE;
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.navigationbar.gestural;
import static android.view.InputDevice.SOURCE_TOUCHPAD;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.AXIS_GESTURE_X_OFFSET;
import static android.view.MotionEvent.AXIS_GESTURE_Y_OFFSET;
import static com.google.common.truth.Truth.assertThat;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for {@link MotionEventsHandler}.
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MotionEventsHandlerTest extends SysuiTestCase {
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private static int SCALE = 100;
private MotionEventsHandler mMotionEventsHandler;
@Before
public void setUp() {
mFeatureFlags.set(Flags.TRACKPAD_GESTURE_BACK, true);
mMotionEventsHandler = new MotionEventsHandler(mFeatureFlags, SCALE);
}
@Test
public void onTouchEvent_touchScreen_hasCorrectDisplacements() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 100, 100, 0);
// TODO: change to use classification after gesture library is ported.
down.setSource(SOURCE_TOUCHSCREEN);
MotionEvent move1 = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 150, 125, 0);
move1.setSource(SOURCE_TOUCHSCREEN);
MotionEvent move2 = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 200, 150, 0);
move2.setSource(SOURCE_TOUCHSCREEN);
MotionEvent up = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 250, 175, 0);
up.setSource(SOURCE_TOUCHSCREEN);
mMotionEventsHandler.onMotionEvent(down);
mMotionEventsHandler.onMotionEvent(move1);
assertThat(mMotionEventsHandler.getDisplacementX(move1)).isEqualTo(50);
assertThat(mMotionEventsHandler.getDisplacementY(move1)).isEqualTo(25);
mMotionEventsHandler.onMotionEvent(move2);
assertThat(mMotionEventsHandler.getDisplacementX(move2)).isEqualTo(100);
assertThat(mMotionEventsHandler.getDisplacementY(move2)).isEqualTo(50);
mMotionEventsHandler.onMotionEvent(up);
assertThat(mMotionEventsHandler.getDisplacementX(up)).isEqualTo(150);
assertThat(mMotionEventsHandler.getDisplacementY(up)).isEqualTo(75);
}
@Test
public void onTouchEvent_trackpad_hasCorrectDisplacements() {
MotionEvent.PointerCoords[] downPointerCoords = new MotionEvent.PointerCoords[1];
downPointerCoords[0] = new MotionEvent.PointerCoords();
downPointerCoords[0].setAxisValue(AXIS_GESTURE_X_OFFSET, 0.1f);
downPointerCoords[0].setAxisValue(AXIS_GESTURE_Y_OFFSET, 0.1f);
MotionEvent.PointerProperties[] downPointerProperties =
new MotionEvent.PointerProperties[1];
downPointerProperties[0] = new MotionEvent.PointerProperties();
downPointerProperties[0].id = 1;
downPointerProperties[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1,
downPointerProperties, downPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0,
SOURCE_TOUCHPAD, 0);
MotionEvent.PointerCoords[] movePointerCoords1 = new MotionEvent.PointerCoords[1];
movePointerCoords1[0] = new MotionEvent.PointerCoords();
movePointerCoords1[0].setAxisValue(AXIS_GESTURE_X_OFFSET, 0.2f);
movePointerCoords1[0].setAxisValue(AXIS_GESTURE_Y_OFFSET, 0.1f);
MotionEvent.PointerProperties[] movePointerProperties1 =
new MotionEvent.PointerProperties[1];
movePointerProperties1[0] = new MotionEvent.PointerProperties();
movePointerProperties1[0].id = 1;
movePointerProperties1[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
MotionEvent move1 = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 1,
movePointerProperties1, movePointerCoords1, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_TOUCHPAD,
0);
MotionEvent.PointerCoords[] movePointerCoords2 = new MotionEvent.PointerCoords[1];
movePointerCoords2[0] = new MotionEvent.PointerCoords();
movePointerCoords2[0].setAxisValue(AXIS_GESTURE_X_OFFSET, 0.1f);
movePointerCoords2[0].setAxisValue(AXIS_GESTURE_Y_OFFSET, 0.4f);
MotionEvent.PointerProperties[] movePointerProperties2 =
new MotionEvent.PointerProperties[1];
movePointerProperties2[0] = new MotionEvent.PointerProperties();
movePointerProperties2[0].id = 1;
movePointerProperties2[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
MotionEvent move2 = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 1,
movePointerProperties2, movePointerCoords2, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_TOUCHPAD,
0);
MotionEvent.PointerCoords[] upPointerCoords = new MotionEvent.PointerCoords[1];
upPointerCoords[0] = new MotionEvent.PointerCoords();
upPointerCoords[0].setAxisValue(AXIS_GESTURE_X_OFFSET, 0.1f);
upPointerCoords[0].setAxisValue(AXIS_GESTURE_Y_OFFSET, 0.1f);
MotionEvent.PointerProperties[] upPointerProperties2 =
new MotionEvent.PointerProperties[1];
upPointerProperties2[0] = new MotionEvent.PointerProperties();
upPointerProperties2[0].id = 1;
upPointerProperties2[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
MotionEvent up = MotionEvent.obtain(0, 2, MotionEvent.ACTION_UP, 1,
upPointerProperties2, upPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_TOUCHPAD, 0);
mMotionEventsHandler.onMotionEvent(down);
mMotionEventsHandler.onMotionEvent(move1);
assertThat(mMotionEventsHandler.getDisplacementX(move1)).isEqualTo(20f);
assertThat(mMotionEventsHandler.getDisplacementY(move1)).isEqualTo(10f);
mMotionEventsHandler.onMotionEvent(move2);
assertThat(mMotionEventsHandler.getDisplacementX(move2)).isEqualTo(30f);
assertThat(mMotionEventsHandler.getDisplacementY(move2)).isEqualTo(50f);
mMotionEventsHandler.onMotionEvent(up);
assertThat(mMotionEventsHandler.getDisplacementX(up)).isEqualTo(40f);
assertThat(mMotionEventsHandler.getDisplacementY(up)).isEqualTo(60f);
}
}