Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust for missing MotionEvents #23

Merged
merged 5 commits into from
Mar 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ext {

version = [
supportLibVersion: '25.4.0',
timber : '4.6.1',
junit : '4.12',
mockito : '2.13.0',
robolectric : '3.7',
Expand All @@ -28,6 +29,9 @@ ext {
supportV4 : "com.android.support:support-v4:${version.supportLibVersion}",
supportDesign : "com.android.support:design:${version.supportLibVersion}",

// timber
timber : "com.jakewharton.timber:timber:${version.timber}",

// instrumentation test
testRunner : "com.android.support.test:runner:${version.testRunnerVersion}",
testRules : "com.android.support.test:rules:${version.testRunnerVersion}",
Expand Down
1 change: 1 addition & 0 deletions library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation dependenciesList.timber

implementation dependenciesList.supportAppcompatV7
testImplementation dependenciesList.junit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.List;
import java.util.NoSuchElementException;

import timber.log.Timber;

/**
* Base class for all multi finger gesture detectors.
*
Expand Down Expand Up @@ -46,6 +48,14 @@ public abstract class MultiFingerGesture<L> extends BaseGesture<L> {
final HashMap<PointerDistancePair, MultiFingerDistancesObject> pointersDistanceMap = new HashMap<>();
private PointF focalPoint = new PointF();

private static final int BITS_PER_ALLOWED_ACTION = 4;
private static final int ALLOWED_ACTION_MASK = ((1 << BITS_PER_ALLOWED_ACTION) - 1);
/**
* Variable that holds all possible at this point MotionEvents based on the previous one.
* Each one of them is written on {@link #BITS_PER_ALLOWED_ACTION} successive bits.
*/
private long allowedActions = MotionEvent.ACTION_DOWN;

public MultiFingerGesture(Context context, AndroidGesturesManager gesturesManager) {
super(context, gesturesManager);

Expand All @@ -56,35 +66,98 @@ public MultiFingerGesture(Context context, AndroidGesturesManager gesturesManage
@Override
protected boolean analyzeEvent(MotionEvent motionEvent) {
int action = motionEvent.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
pointerIdList.add(motionEvent.getPointerId(motionEvent.getActionIndex()));
break;

case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
pointerIdList.remove(Integer.valueOf(motionEvent.getPointerId(motionEvent.getActionIndex())));
break;

case MotionEvent.ACTION_MOVE:

boolean isMissingActions = isMissingAction(action);
if (isMissingActions) {
// stopping ProgressiveGestures and clearing pointers
if (this instanceof ProgressiveGesture && ((ProgressiveGesture) this).isInProgress()) {
((ProgressiveGesture) this).gestureStopped();
}
pointerIdList.clear();
pointersDistanceMap.clear();

allowedActions = MotionEvent.ACTION_DOWN;
}

if (!isMissingActions || action == MotionEvent.ACTION_DOWN) {
// if we are not missing any actions or the invalid one happens
// to be ACTION_DOWN (therefore, we can start over immediately), then update pointers
updatePointerList(motionEvent);
updateAllowedActions();
}

if (isMissingActions) {
Timber.w("Some MotionEvents were not passed to the library.");
return false;
} else {
if (action == MotionEvent.ACTION_MOVE) {
if (pointerIdList.size() >= getRequiredPointersCount() && checkPressure()) {
calculateDistances();
if (!isSloppyGesture()) {
focalPoint = Utils.determineFocalPoint(motionEvent);
return analyzeMovement();
}
return false;
}
break;

default:
break;
}
}

return false;
}

private void updatePointerList(MotionEvent motionEvent) {
int action = motionEvent.getActionMasked();

if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
pointerIdList.add(motionEvent.getPointerId(motionEvent.getActionIndex()));
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
pointerIdList.remove(Integer.valueOf(motionEvent.getPointerId(motionEvent.getActionIndex())));
}
}

private boolean isMissingAction(int action) {
if (action == allowedActions) {
// this will only happen for action == allowedActions == ACTION_DOWN
return false;
}

while (allowedActions != 0) {
// get one of actions, the one on the first BITS_PER_ALLOWED_ACTION bits
long testCase = allowedActions & ALLOWED_ACTION_MASK;
if (action == testCase) {
// we got a match, all good
return false;
}

// remove the one we just checked and iterate
allowedActions = allowedActions >> BITS_PER_ALLOWED_ACTION;
}

// no available matching actions, we are missing some!
return true;
}

private void updateAllowedActions() {
allowedActions = 0;

if (pointerIdList.size() == 0) {
// only ACTION_DOWN available when no other pointers registered
allowedActions = MotionEvent.ACTION_DOWN;
} else if (pointerIdList.size() >= 1) {
// add available actions accordingly, shifting by BITS_PER_ALLOWED_ACTION with each addition
allowedActions += MotionEvent.ACTION_POINTER_DOWN;
allowedActions = allowedActions << BITS_PER_ALLOWED_ACTION;
allowedActions += MotionEvent.ACTION_MOVE;

if (pointerIdList.size() == 1) {
allowedActions = allowedActions << BITS_PER_ALLOWED_ACTION;
allowedActions += MotionEvent.ACTION_UP;
} else if (pointerIdList.size() > 1) {
allowedActions = allowedActions << BITS_PER_ALLOWED_ACTION;
allowedActions += MotionEvent.ACTION_POINTER_UP;
}
}
}

boolean checkPressure() {
float currentPressure = getCurrentEvent().getPressure();
float previousPressure = getPreviousEvent().getPressure();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,37 +45,23 @@ protected boolean analyzeEvent(MotionEvent motionEvent) {

boolean movementHandled = super.analyzeEvent(motionEvent);

if (!movementHandled) {
int action = motionEvent.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:

if (velocityTracker != null) {
velocityTracker.clear();
}
break;

case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (pointerIdList.size() < getRequiredPointersCount() && isInProgress) {
gestureStopped();
return true;
}
break;

case MotionEvent.ACTION_CANCEL:
if (velocityTracker != null) {
velocityTracker.clear();
}
if (isInProgress) {
gestureStopped();
return true;
}
break;

default:
break;
int action = motionEvent.getActionMasked();
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
if (velocityTracker != null) {
velocityTracker.clear();
}
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
if (pointerIdList.size() < getRequiredPointersCount() && isInProgress) {
gestureStopped();
return true;
}
} else if (action == MotionEvent.ACTION_CANCEL) {
if (velocityTracker != null) {
velocityTracker.clear();
}
if (isInProgress) {
gestureStopped();
return true;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.mapbox.android.gestures;

import android.view.MotionEvent;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

import static com.mapbox.android.gestures.TestUtils.getMotionEvent;
import static org.mockito.Mockito.spy;

@RunWith(RobolectricTestRunner.class)
public class PointersManagementTest extends
AbstractGestureDetectorTest<StandardScaleGestureDetector,
StandardScaleGestureDetector.StandardOnScaleGestureListener> {

@Override
StandardScaleGestureDetector getDetectorObject() {
return spy(androidGesturesManager.getStandardScaleGestureDetector());
}

private void checkResult(int expected) {
int pointersCount = androidGesturesManager.getStandardScaleGestureDetector().getPointersCount();
Assert.assertTrue(
String.format("Expected %d pointers, was %d.", expected, pointersCount),
pointersCount == expected
);
}

@Test
public void missingDownTest() {
MotionEvent pointerDownEvent = getMotionEvent(MotionEvent.ACTION_POINTER_DOWN, 0, 0);
androidGesturesManager.onTouchEvent(pointerDownEvent);

checkResult(0);
}

@Test
public void missingUpTest() {
MotionEvent downEvent = getMotionEvent(MotionEvent.ACTION_DOWN, 0, 0);
androidGesturesManager.onTouchEvent(downEvent);

downEvent = getMotionEvent(MotionEvent.ACTION_DOWN, 0, 0, downEvent);
androidGesturesManager.onTouchEvent(downEvent);

checkResult(1);
}

@Test
public void missingPointerDownTest() {
MotionEvent downEvent = getMotionEvent(MotionEvent.ACTION_DOWN, 0, 0);
androidGesturesManager.onTouchEvent(downEvent);

MotionEvent pointerDownEvent = getMotionEvent(MotionEvent.ACTION_POINTER_DOWN, 0, 0, downEvent);
androidGesturesManager.onTouchEvent(pointerDownEvent);

MotionEvent pointerUpEvent = getMotionEvent(MotionEvent.ACTION_POINTER_UP, 0, 0, pointerDownEvent);
androidGesturesManager.onTouchEvent(pointerUpEvent);

pointerUpEvent = getMotionEvent(MotionEvent.ACTION_POINTER_UP, 0, 0, pointerUpEvent);
androidGesturesManager.onTouchEvent(pointerUpEvent);

pointerDownEvent = getMotionEvent(MotionEvent.ACTION_POINTER_DOWN, 0, 0, pointerUpEvent);
androidGesturesManager.onTouchEvent(pointerDownEvent);

checkResult(0); //expecting 0, because we are waiting for ACTION_DOWN to synchronise again
}

@Test
public void missingPointerUpTest() {
MotionEvent downEvent = getMotionEvent(MotionEvent.ACTION_DOWN, 0, 0);
androidGesturesManager.onTouchEvent(downEvent);

MotionEvent pointerDownEvent = getMotionEvent(MotionEvent.ACTION_POINTER_DOWN, 0, 0, downEvent);
androidGesturesManager.onTouchEvent(pointerDownEvent);

pointerDownEvent = getMotionEvent(MotionEvent.ACTION_POINTER_DOWN, 0, 0, pointerDownEvent);
androidGesturesManager.onTouchEvent(pointerDownEvent);

MotionEvent pointerUpEvent = getMotionEvent(MotionEvent.ACTION_POINTER_UP, 0, 0, pointerDownEvent);
androidGesturesManager.onTouchEvent(pointerUpEvent);

MotionEvent upEvent = getMotionEvent(MotionEvent.ACTION_UP, 0, 0, pointerUpEvent);
androidGesturesManager.onTouchEvent(upEvent);

checkResult(0);
}

@Test
public void addingRemovingPointersTest() {
MotionEvent downEvent = getMotionEvent(MotionEvent.ACTION_DOWN, 0, 0);
androidGesturesManager.onTouchEvent(downEvent);

MotionEvent pointerDownEvent = getMotionEvent(MotionEvent.ACTION_POINTER_DOWN, 0, 0, downEvent);
androidGesturesManager.onTouchEvent(pointerDownEvent);

pointerDownEvent = getMotionEvent(MotionEvent.ACTION_POINTER_DOWN, 0, 0, pointerDownEvent);
androidGesturesManager.onTouchEvent(pointerDownEvent);

pointerDownEvent = getMotionEvent(MotionEvent.ACTION_POINTER_DOWN, 0, 0, pointerDownEvent);
androidGesturesManager.onTouchEvent(pointerDownEvent);

checkResult(4);

MotionEvent pointerUpEvent = getMotionEvent(MotionEvent.ACTION_POINTER_UP, 0, 0, pointerDownEvent);
androidGesturesManager.onTouchEvent(pointerUpEvent);

pointerUpEvent = getMotionEvent(MotionEvent.ACTION_POINTER_UP, 0, 0, pointerUpEvent);
androidGesturesManager.onTouchEvent(pointerUpEvent);

pointerUpEvent = getMotionEvent(MotionEvent.ACTION_POINTER_UP, 0, 0, pointerUpEvent);
androidGesturesManager.onTouchEvent(pointerUpEvent);

MotionEvent upEvent = getMotionEvent(MotionEvent.ACTION_UP, 0, 0, pointerUpEvent);
androidGesturesManager.onTouchEvent(upEvent);

checkResult(0);
}
}