blob: 67dda14afd616d74cba60c1b7c360590dd100716 [file] [log] [blame]
/*
* Copyright (C) 2016 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 android.support.v13.view;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
import android.graphics.Point;
import android.os.Build;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.filters.SdkSuppress;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.support.compat.test.R;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
@RequiresApi(13)
@RunWith(AndroidJUnit4.class)
public class DragStartHelperTest {
@Rule
public ActivityTestRule<DragStartHelperTestActivity> mActivityRule =
new ActivityTestRule<>(DragStartHelperTestActivity.class);
private Instrumentation mInstrumentation;
private View mDragSource;
interface DragStartListener {
boolean onDragStart(View view, DragStartHelper helper, Point touchPosition);
}
@NonNull
private DragStartListener createListener(boolean returnValue) {
final DragStartListener listener = mock(DragStartListener.class);
when(listener.onDragStart(any(View.class), any(DragStartHelper.class), any(Point.class)))
.thenReturn(returnValue);
return listener;
}
@NonNull
private DragStartHelper createDragStartHelper(final DragStartListener listener) {
return new DragStartHelper(mDragSource, new DragStartHelper.OnDragStartListener() {
@Override
public boolean onDragStart(View v, DragStartHelper helper) {
Point touchPosition = new Point();
helper.getTouchPosition(touchPosition);
return listener.onDragStart(v, helper, touchPosition);
}
});
}
private static int[] getViewCenter(View view) {
final int[] xy = new int[2];
view.getLocationOnScreen(xy);
xy[0] += view.getWidth() / 2;
xy[1] += view.getHeight() / 2;
return xy;
}
private static MotionEvent obtainTouchEvent(
int action, View anchor, int offsetX, int offsetY) {
final long eventTime = SystemClock.uptimeMillis();
final int[] xy = getViewCenter(anchor);
return MotionEvent.obtain(
eventTime, eventTime, action, xy[0] + offsetX, xy[1] + offsetY, 0);
}
private void sendTouchEvent(int action, View anchor, int offsetX, int offsetY) {
mInstrumentation.sendPointerSync(obtainTouchEvent(action, anchor, offsetX, offsetY));
}
private static MotionEvent obtainMouseEvent(
int action, int buttonState, View anchor, int offsetX, int offsetY) {
final long eventTime = SystemClock.uptimeMillis();
final int[] xy = getViewCenter(anchor);
MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[] {
new MotionEvent.PointerProperties()
};
props[0].id = 0;
props[0].toolType = MotionEvent.TOOL_TYPE_MOUSE;
MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[] {
new MotionEvent.PointerCoords()
};
coords[0].x = xy[0] + offsetX;
coords[0].y = xy[1] + offsetY;
return MotionEvent.obtain(eventTime, eventTime, action, 1, props, coords, 0,
buttonState, 0, 0, -1, 0, InputDevice.SOURCE_MOUSE, 0);
}
private void sendMouseEvent(
int action, int buttonState, View anchor, int offsetX, int offsetY) {
mInstrumentation.sendPointerSync(obtainMouseEvent(
action, buttonState, anchor, offsetX, offsetY));
}
static class TouchPositionMatcher implements ArgumentMatcher<Point> {
private final Point mExpectedPosition;
TouchPositionMatcher(int x, int y) {
mExpectedPosition = new Point(x, y);
}
TouchPositionMatcher(View anchor, int x, int y) {
this(anchor.getWidth() / 2 + x, anchor.getHeight() / 2 + y);
}
@Override
public boolean matches(Point actual) {
return mExpectedPosition.equals(actual);
}
@Override
public String toString() {
return "TouchPositionMatcher: " + mExpectedPosition;
}
}
private void waitForLongPress() {
SystemClock.sleep(ViewConfiguration.getLongPressTimeout() * 2);
}
@Before
public void setUp() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mDragSource = mActivityRule.getActivity().findViewById(R.id.drag_source);
}
@SmallTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Test
public void mouseClick() throws Throwable {
final DragStartListener listener = createListener(true);
final DragStartHelper helper = createDragStartHelper(listener);
helper.attach();
sendMouseEvent(MotionEvent.ACTION_DOWN, MotionEvent.BUTTON_PRIMARY, mDragSource, 0, 0);
sendMouseEvent(MotionEvent.ACTION_UP, MotionEvent.BUTTON_PRIMARY, mDragSource, 0, 0);
// A simple mouse click does not trigger OnDragStart.
verifyNoMoreInteractions(listener);
}
@SmallTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Test
public void mousePressWithSecondaryButton() throws Throwable {
final DragStartListener listener = createListener(true);
final DragStartHelper helper = createDragStartHelper(listener);
helper.attach();
sendMouseEvent(MotionEvent.ACTION_DOWN, MotionEvent.BUTTON_PRIMARY, mDragSource, 0, 0);
sendMouseEvent(MotionEvent.ACTION_MOVE,
MotionEvent.BUTTON_PRIMARY | MotionEvent.BUTTON_SECONDARY, mDragSource, 0, 0);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 0, 0);
// ACTION_MOVE with the same position does not trigger OnDragStart.
verifyNoMoreInteractions(listener);
}
@SmallTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Test
public void mouseDrag() throws Throwable {
final DragStartListener listener = createListener(true);
final DragStartHelper helper = createDragStartHelper(listener);
helper.attach();
sendMouseEvent(MotionEvent.ACTION_DOWN, MotionEvent.BUTTON_PRIMARY, mDragSource, 0, 0);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 1, 2);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 3, 4);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 5, 6);
// Returning true from the callback prevents further callbacks.
verify(listener, times(1)).onDragStart(
eq(mDragSource), eq(helper), argThat(new TouchPositionMatcher(mDragSource, 1, 2)));
verifyNoMoreInteractions(listener);
}
@SmallTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Test
public void mouseDragWithNonprimaryButton() throws Throwable {
final DragStartListener listener = createListener(true);
final DragStartHelper helper = createDragStartHelper(listener);
helper.attach();
sendMouseEvent(MotionEvent.ACTION_DOWN, MotionEvent.BUTTON_SECONDARY, mDragSource, 0, 0);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_SECONDARY, mDragSource, 1, 2);
sendMouseEvent(MotionEvent.ACTION_UP, MotionEvent.BUTTON_SECONDARY, mDragSource, 3, 4);
sendMouseEvent(MotionEvent.ACTION_DOWN, MotionEvent.BUTTON_TERTIARY, mDragSource, 0, 0);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_TERTIARY, mDragSource, 1, 2);
sendMouseEvent(MotionEvent.ACTION_UP, MotionEvent.BUTTON_TERTIARY, mDragSource, 3, 4);
// Dragging mouse with a non-primary button down does not trigger OnDragStart.
verifyNoMoreInteractions(listener);
}
@SmallTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Test
public void mouseDragUsingTouchListener() throws Throwable {
final DragStartListener listener = createListener(true);
final DragStartHelper helper = createDragStartHelper(listener);
mDragSource.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
helper.onTouch(view, motionEvent);
return true;
}
});
sendMouseEvent(MotionEvent.ACTION_DOWN, MotionEvent.BUTTON_PRIMARY, mDragSource, 0, 0);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 1, 2);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 3, 4);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 5, 6);
// Returning true from the callback prevents further callbacks.
verify(listener, times(1)).onDragStart(
eq(mDragSource), eq(helper), argThat(new TouchPositionMatcher(mDragSource, 1, 2)));
verifyNoMoreInteractions(listener);
}
@SmallTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Test
public void mouseDragWhenListenerReturnsFalse() throws Throwable {
final DragStartListener listener = createListener(false);
final DragStartHelper helper = createDragStartHelper(listener);
helper.attach();
sendMouseEvent(MotionEvent.ACTION_DOWN, MotionEvent.BUTTON_PRIMARY, mDragSource, 0, 0);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 1, 2);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 3, 4);
sendMouseEvent(MotionEvent.ACTION_MOVE, MotionEvent.BUTTON_PRIMARY, mDragSource, 5, 6);
// When the listener returns false every ACTION_MOVE triggers OnDragStart.
InOrder inOrder = inOrder(listener);
inOrder.verify(listener, times(1)).onDragStart(
eq(mDragSource), eq(helper), argThat(new TouchPositionMatcher(mDragSource, 1, 2)));
inOrder.verify(listener, times(1)).onDragStart(
eq(mDragSource), eq(helper), argThat(new TouchPositionMatcher(mDragSource, 3, 4)));
inOrder.verify(listener, times(1)).onDragStart(
eq(mDragSource), eq(helper), argThat(new TouchPositionMatcher(mDragSource, 5, 6)));
inOrder.verifyNoMoreInteractions();
}
@LargeTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Test
public void mouseLongPress() throws Throwable {
final DragStartListener listener = createListener(true);
final DragStartHelper helper = createDragStartHelper(listener);
helper.attach();
sendMouseEvent(MotionEvent.ACTION_DOWN, MotionEvent.BUTTON_PRIMARY, mDragSource, 1, 2);
waitForLongPress();
// Long press triggers OnDragStart.
verify(listener, times(1)).onDragStart(
eq(mDragSource), eq(helper), argThat(new TouchPositionMatcher(mDragSource, 1, 2)));
verifyNoMoreInteractions(listener);
}
@SmallTest
@Test
public void touchDrag() throws Throwable {
final DragStartListener listener = createListener(false);
final DragStartHelper helper = createDragStartHelper(listener);
helper.attach();
sendTouchEvent(MotionEvent.ACTION_DOWN, mDragSource, 0, 0);
sendTouchEvent(MotionEvent.ACTION_MOVE, mDragSource, 1, 2);
sendTouchEvent(MotionEvent.ACTION_MOVE, mDragSource, 3, 4);
sendTouchEvent(MotionEvent.ACTION_MOVE, mDragSource, 5, 6);
// Touch and drag (without delay) does not trigger OnDragStart.
verifyNoMoreInteractions(listener);
}
@SmallTest
@Test
public void touchTap() throws Throwable {
final DragStartListener listener = createListener(false);
final DragStartHelper helper = createDragStartHelper(listener);
helper.attach();
sendTouchEvent(MotionEvent.ACTION_DOWN, mDragSource, 0, 0);
sendTouchEvent(MotionEvent.ACTION_UP, mDragSource, 0, 0);
// A simple tap does not trigger OnDragStart.
verifyNoMoreInteractions(listener);
}
@LargeTest
@Test
public void touchLongPress() throws Throwable {
final DragStartListener listener = createListener(true);
final DragStartHelper helper = createDragStartHelper(listener);
helper.attach();
sendTouchEvent(MotionEvent.ACTION_DOWN, mDragSource, 1, 2);
waitForLongPress();
// Long press triggers OnDragStart.
verify(listener, times(1)).onDragStart(
eq(mDragSource), eq(helper), argThat(new TouchPositionMatcher(mDragSource, 1, 2)));
verifyNoMoreInteractions(listener);
}
@LargeTest
@Test
public void touchLongPressUsingLongClickListener() throws Throwable {
final DragStartListener listener = createListener(true);
final DragStartHelper helper = createDragStartHelper(listener);
mDragSource.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return helper.onLongClick(view);
}
});
sendTouchEvent(MotionEvent.ACTION_DOWN, mDragSource, 1, 2);
waitForLongPress();
// Long press triggers OnDragStart.
// Since ACTION_DOWN is not handled, the touch offset is not available.
verify(listener, times(1)).onDragStart(
eq(mDragSource), eq(helper), argThat(new TouchPositionMatcher(0, 0)));
verifyNoMoreInteractions(listener);
}
}