Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 64f5117b authored by Sara Kato's avatar Sara Kato
Browse files

Create test for factor value of ScaleGesture.

A ScaleGestureDectector is attached to an activity.

A pinch and zoom action has been created, and mimics the action by first
sending a down motion on the two points,
which then moves to the supplied end coordinates in the number of steps
(PINCH_STEP_COUNT).

Bug: 30751698
Change-Id: Id654025f47dfc10d852e9f2eb428a969fc7de95c
parent b103b294
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -479,6 +479,13 @@
            </intent-filter>
        </activity>

        <activity android:name="android.view.ScaleGesture" android:label="ScaleGesture">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
            </intent-filter>
        </activity>

        <activity android:name="android.view.StubbedView" android:label="ViewStub">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
+31 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->

<!-- Demonstrates use of scale gesture detector to perform pinch to zoom.
     See corresponding Java code. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:id="@+id/article"
        android:textSize="12sp"/>

</RelativeLayout>
+250 −0
Original line number Diff line number Diff line
/*
 * 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.view;

import static android.support.test.espresso.core.deps.guava.base.Preconditions.checkNotNull;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static org.hamcrest.Matchers.allOf;

import android.os.SystemClock;
import android.support.test.espresso.InjectEventSecurityException;
import android.support.test.espresso.PerformException;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.MotionEvents;
import android.support.test.espresso.action.Swiper;
import android.support.test.espresso.UiController;
import android.support.test.espresso.util.HumanReadables;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import javax.annotation.Nullable;
import org.hamcrest.Matcher;

/**
 * Pinch and zooms on a View using touch events.
 * <br>
 * View constraints:
 * <ul>
 * <li>must be displayed on screen
 * <ul>
 */
public class PinchZoomAction implements ViewAction {
    public static Swiper.Status sendPinchZoomAction(UiController uiController,
                                                    float[] firstFingerStartCoords,
                                                    float[] firstFingerEndCoords,
                                                    float[] secondFingerStartCoords,
                                                    float[] secondFingerEndCoords,
                                                    float[] precision) {
        checkNotNull(uiController);
        checkNotNull(firstFingerStartCoords);
        checkNotNull(firstFingerEndCoords);
        checkNotNull(secondFingerStartCoords);
        checkNotNull(secondFingerEndCoords);
        checkNotNull(precision);

        // Specify the touch properties for the finger events.
        final MotionEvent.PointerProperties pp1 = new MotionEvent.PointerProperties();
        pp1.id = 0;
        pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
        final MotionEvent.PointerProperties pp2 = new MotionEvent.PointerProperties();
        pp2.id = 1;
        pp2.toolType = MotionEvent.TOOL_TYPE_FINGER;
        MotionEvent.PointerProperties[] pointerProperties =
                new MotionEvent.PointerProperties[]{pp1, pp2};

        // Specify the motion properties of the two touch points.
        final MotionEvent.PointerCoords pc1 = new MotionEvent.PointerCoords();
        pc1.x = firstFingerStartCoords[0];
        pc1.y = firstFingerStartCoords[1];
        pc1.pressure = 1;
        pc1.size = 1;
        final MotionEvent.PointerCoords pc2 = new MotionEvent.PointerCoords();
        pc2.x = secondFingerStartCoords[0];
        pc2.y = secondFingerEndCoords[1];
        pc2.pressure = 1;
        pc2.size = 1;

        final long startTime = SystemClock.uptimeMillis();
        long eventTime = startTime;
        final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[]{pc1, pc2};

        final MotionEvent firstFingerEvent = MotionEvent.obtain(startTime,
                eventTime, MotionEvent.ACTION_DOWN, 1, pointerProperties, pointerCoords,
                0, 0, 1, 1, 0, 0, 0, 0);

        eventTime = SystemClock.uptimeMillis();
        final MotionEvent secondFingerEvent = MotionEvent.obtain(startTime, eventTime,
                MotionEvent.ACTION_POINTER_DOWN +
                        (pp2.id << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
                2, pointerProperties, pointerCoords, 0, 0, 1, 1, 0, 0, 0, 0);

        try {
            uiController.injectMotionEvent(firstFingerEvent);
        } catch (InjectEventSecurityException e) {
            throw new PerformException.Builder()
                    .withActionDescription("First finger down event")
                    .withViewDescription("Scale gesture detector")
                    .withCause(e)
                    .build();
        }

        try {
            uiController.injectMotionEvent(secondFingerEvent);
        } catch (InjectEventSecurityException e) {
            throw new PerformException.Builder()
                    .withActionDescription("Second finger down event")
                    .withViewDescription("Scale gesture detector")
                    .withCause(e)
                    .build();
        }

        // Specify the coordinates of the two touch points.
        final float[][] stepsFirstFinger = interpolate(firstFingerStartCoords,
                firstFingerEndCoords);
        final float[][] stepsSecondFinger = interpolate(secondFingerStartCoords,
                secondFingerEndCoords);

        // Loop until the end points of the two fingers are reached.
        for (int i = 0; i < PINCH_STEP_COUNT; i++) {
            eventTime = SystemClock.uptimeMillis();

            pc1.x = stepsFirstFinger[i][0];
            pc1.y = stepsFirstFinger[i][1];
            pc2.x = stepsSecondFinger[i][0];
            pc2.y = stepsSecondFinger[i][1];

            final MotionEvent event = MotionEvent.obtain(startTime, eventTime,
                    MotionEvent.ACTION_MOVE, 2, pointerProperties, pointerCoords,
                    0, 0, 1, 1, 0, 0, 0, 0);

            try {
                uiController.injectMotionEvent(event);
            } catch (InjectEventSecurityException e) {
                throw new PerformException.Builder()
                        .withActionDescription("Move event")
                        .withViewDescription("Scale gesture event")
                        .withCause(e)
                        .build();
            }

           uiController.loopMainThreadForAtLeast(800);
        }

        eventTime = SystemClock.uptimeMillis();

        // Send the up event for the second finger.
        final MotionEvent secondFingerUpEvent = MotionEvent.obtain(startTime, eventTime,
                MotionEvent.ACTION_POINTER_UP, 2, pointerProperties, pointerCoords,
                0, 0, 1, 1, 0, 0, 0, 0);
        try {
            uiController.injectMotionEvent(secondFingerUpEvent);
        } catch (InjectEventSecurityException e) {
            throw new PerformException.Builder()
                    .withActionDescription("Second finger up event")
                    .withViewDescription("Scale gesture detector")
                    .withCause(e)
                    .build();
        }

        eventTime = SystemClock.uptimeMillis();
        // Send the up event for the first finger.
        final MotionEvent firstFingerUpEvent = MotionEvent.obtain(startTime, eventTime,
                MotionEvent.ACTION_POINTER_UP, 1, pointerProperties, pointerCoords,
                0, 0, 1, 1, 0, 0, 0, 0);
        try {
            uiController.injectMotionEvent(firstFingerUpEvent);
        } catch (InjectEventSecurityException e) {
            throw new PerformException.Builder()
                    .withActionDescription("First finger up event")
                    .withViewDescription("Scale gesture detector")
                    .withCause(e)
                    .build();
        }
        return Swiper.Status.SUCCESS;
    }

    private static float[][] interpolate(float[] start, float[] end) {
        float[][] res = new float[PINCH_STEP_COUNT][2];

        for (int i = 0; i < PINCH_STEP_COUNT; i++) {
            res[i][0] = start[0] + (end[0] - start[0]) * i / (PINCH_STEP_COUNT - 1f);
            res[i][1] = start[1] + (end[1] - start[1]) * i / (PINCH_STEP_COUNT - 1f);
        }

        return res;
    }

    /** The number of move events to send for each pinch. */
    private static final int PINCH_STEP_COUNT = 10;

    private final Class<? extends View> mViewClass;
    private final float[] mFirstFingerStartCoords;
    private final float[] mFirstFingerEndCoords;
    private final float[] mSecondFingerStartCoords;
    private final float[] mSecondFingerEndCoords;

    public PinchZoomAction(float[] firstFingerStartCoords,
                           float[] firstFingerEndCoords,
                           float[] secondFingerStartCoords,
                           float[] secondFingerEndCoords,
                           Class<? extends View> viewClass) {
        mFirstFingerStartCoords = firstFingerStartCoords;
        mFirstFingerEndCoords = firstFingerEndCoords;
        mSecondFingerStartCoords = secondFingerStartCoords;
        mSecondFingerEndCoords = secondFingerEndCoords;
        mViewClass = viewClass;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Matcher<View> getConstraints() {
        return allOf(isCompletelyDisplayed(), isAssignableFrom(mViewClass));
    }

    @Override
    public void perform(UiController uiController, View view) {
        checkNotNull(uiController);
        checkNotNull(view);
        Swiper.Status status;
        final float[] precision = {1.0f, 1.0f, 1.0f, 1.0f};

        try {
            status = sendPinchZoomAction(uiController, this.mFirstFingerStartCoords,
                this.mFirstFingerEndCoords, this.mSecondFingerStartCoords,
                this.mSecondFingerEndCoords, precision);
        } catch (RuntimeException re) {
            throw new PerformException.Builder()
                    .withActionDescription(getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(re)
                    .build();
        }
        if (status == Swiper.Status.FAILURE) {
            throw new PerformException.Builder()
                    .withActionDescription(getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new RuntimeException(getDescription() + " failed"))
                    .build();
        }
    }

    @Override
    public String getDescription() {
        return "Pinch Zoom Action";
    }
}
+58 −0
Original line number Diff line number Diff line
/*
* 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.view;

import com.android.frameworks.coretests.R;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
import android.widget.TextView;

public class ScaleGesture extends Activity {
    private ScaleGestureDetector mScaleGestureDetector;
    private float mFactor;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.scale_gesture);
        mScaleGestureDetector = new ScaleGestureDetector(this, new OnScaleGestureListener());
        mFactor = 1.0f;
    }

    public float getScaleFactor() {
        return mFactor;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mScaleGestureDetector.onTouchEvent(event);
        return true;
    }

    public class OnScaleGestureListener extends SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mFactor *= detector.getScaleFactor();
            return true;
        }
    }
}
+91 −0
Original line number Diff line number Diff line
/*
* 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.view;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
import android.util.DisplayMetrics;
import android.view.PinchZoomAction;
import android.view.ScaleGesture;
import android.view.WindowManager;
import android.widget.TextView;

import com.android.frameworks.coretests.R;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.Espresso.onView;

public class ScaleGestureDetectorTest extends ActivityInstrumentationTestCase2<ScaleGesture> {
    private ScaleGesture mScaleGestureActivity;

    public ScaleGestureDetectorTest() {
        super("com.android.frameworks.coretests", ScaleGesture.class);
    }

    @Before
    public void setUp() throws Exception {
        super.setUp();
        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
        mScaleGestureActivity = getActivity();
    }

    @After
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @Test
    public void testScaleGestureDetector() {
        // No scaling should have occurred prior to performing pinch zoom action.
        final float initialScaleFactor = 1.0f;
        assertEquals(initialScaleFactor, mScaleGestureActivity.getScaleFactor());

        // Specify start and end coordinates, irrespective of device display size.
        final DisplayMetrics dm = new DisplayMetrics();
        final WindowManager wm = (WindowManager) (mScaleGestureActivity.getApplicationContext())
                .getSystemService(Context.WINDOW_SERVICE);
        wm.getDefaultDisplay().getMetrics(dm);
        final int displayWidth = dm.widthPixels;
        final int displayHeight = dm.heightPixels;

        // Obtain coordinates to perform pinch and zoom from the center, to 75% of the display.
        final int centerX = displayWidth / 2;
        final int centerY = displayHeight / 2;

        // Offset center coordinates by one, so that the two starting points are different.
        final float[] firstFingerStartCoords = new float[] {centerX + 1.0f, centerY - 1.0f};
        final float[] firstFingerEndCoords =
        new float[] {0.75f * displayWidth, 0.25f * displayHeight};
        final float[] secondFingerStartCoords = new float[] {centerX - 1.0f, centerY + 1.0f};
        final float[] secondFingerEndCoords =
        new float[] {0.25f * displayWidth, 0.75f * displayHeight};

        onView(withId(R.id.article)).perform(new PinchZoomAction(firstFingerStartCoords,
                firstFingerEndCoords, secondFingerStartCoords, secondFingerEndCoords,
                TextView.class));

        // Text should have been 'zoomed', meaning scale factor increased.
        assertTrue(mScaleGestureActivity.getScaleFactor() > initialScaleFactor);
    }
}
 No newline at end of file