chromium/content/public/android/junit/src/org/chromium/content/browser/input/StylusGestureConverterTest.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.content.browser.input;

import static android.view.inputmethod.HandwritingGesture.GRANULARITY_CHARACTER;
import static android.view.inputmethod.HandwritingGesture.GRANULARITY_WORD;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Build.VERSION_CODES;
import android.view.inputmethod.DeleteGesture;
import android.view.inputmethod.DeleteRangeGesture;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SelectRangeGesture;

import androidx.test.filters.SmallTest;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.blink.mojom.StylusWritingGestureAction;
import org.chromium.blink.mojom.StylusWritingGestureData;

/**
 * Tests for the StylusGestureConverter class which converts stylus gestures from their Android
 * representation to their Blink representation. These tests construct gesture objects and use the
 * converter to convert them into gesture data. The gesture data is then checked for accuracy.
 */
@RunWith(RobolectricTestRunner.class)
@Batch(Batch.PER_CLASS)
@CommandLineFlags.Add({"enable-features=StylusRichGestures"})
@Config(sdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
public class StylusGestureConverterTest {
    private static final String GESTURE_TYPE_HISTOGRAM = "InputMethod.StylusHandwriting.Gesture";
    private static final String FALLBACK_TEXT = "this gesture failed";

    @Test
    @SmallTest
    public void testSelectGesture() {
        var histogram =
                HistogramWatcher.newSingleRecordWatcher(
                        GESTURE_TYPE_HISTOGRAM, StylusGestureConverter.UmaGestureType.SELECT);
        SelectGesture gesture =
                new SelectGesture.Builder()
                        .setGranularity(GRANULARITY_CHARACTER)
                        .setSelectionArea(new RectF(0, 0, 10, 10))
                        .setFallbackText(FALLBACK_TEXT)
                        .build();
        StylusWritingGestureData gestureData = StylusGestureConverter.createGestureData(gesture);

        assertNotNull(gestureData);
        assertEquals(StylusWritingGestureAction.SELECT_TEXT, gestureData.action);
        assertEquals(
                org.chromium.blink.mojom.StylusWritingGestureGranularity.CHARACTER,
                gestureData.granularity);
        assertMojoRectsAreEqual(createMojoRect(0, 5, 0, 0), gestureData.startRect);
        assertMojoRectsAreEqual(createMojoRect(10, 5, 0, 0), gestureData.endRect);
        assertEquals(FALLBACK_TEXT, toJavaString(gestureData.textAlternative));
        assertNull(gestureData.textToInsert);
        histogram.assertExpected();
    }

    @Test
    @SmallTest
    public void testInsertGesture() {
        var histogram =
                HistogramWatcher.newSingleRecordWatcher(
                        GESTURE_TYPE_HISTOGRAM, StylusGestureConverter.UmaGestureType.INSERT);
        InsertGesture gesture =
                new InsertGesture.Builder()
                        .setTextToInsert("Foo")
                        .setInsertionPoint(new PointF(15, 31))
                        .setFallbackText(FALLBACK_TEXT)
                        .build();
        StylusWritingGestureData gestureData = StylusGestureConverter.createGestureData(gesture);

        assertNotNull(gestureData);
        assertEquals(StylusWritingGestureAction.ADD_SPACE_OR_TEXT, gestureData.action);
        assertEquals(
                org.chromium.blink.mojom.StylusWritingGestureGranularity.CHARACTER,
                gestureData.granularity);
        assertMojoRectsAreEqual(createMojoRect(15, 31, 0, 0), gestureData.startRect);
        assertNull(gestureData.endRect);
        assertEquals(FALLBACK_TEXT, toJavaString(gestureData.textAlternative));
        assertEquals("Foo", toJavaString(gestureData.textToInsert));
        histogram.assertExpected();
    }

    @Test
    @SmallTest
    public void testDeleteGesture() {
        var histogram =
                HistogramWatcher.newSingleRecordWatcher(
                        GESTURE_TYPE_HISTOGRAM, StylusGestureConverter.UmaGestureType.DELETE);
        DeleteGesture gesture =
                new DeleteGesture.Builder()
                        .setGranularity(GRANULARITY_WORD)
                        .setDeletionArea(new RectF(0, 0, 10, 10))
                        .setFallbackText(FALLBACK_TEXT)
                        .build();
        StylusWritingGestureData gestureData = StylusGestureConverter.createGestureData(gesture);

        assertNotNull(gestureData);
        assertEquals(StylusWritingGestureAction.DELETE_TEXT, gestureData.action);
        assertEquals(
                org.chromium.blink.mojom.StylusWritingGestureGranularity.WORD,
                gestureData.granularity);
        assertMojoRectsAreEqual(createMojoRect(0, 5, 0, 0), gestureData.startRect);
        assertMojoRectsAreEqual(createMojoRect(10, 5, 0, 0), gestureData.endRect);
        assertEquals(FALLBACK_TEXT, toJavaString(gestureData.textAlternative));
        assertNull(gestureData.textToInsert);
        histogram.assertExpected();
    }

    @Test
    @SmallTest
    public void testRemoveSpaceGesture() {
        var histogram =
                HistogramWatcher.newSingleRecordWatcher(
                        GESTURE_TYPE_HISTOGRAM, StylusGestureConverter.UmaGestureType.REMOVE_SPACE);
        RemoveSpaceGesture gesture =
                new RemoveSpaceGesture.Builder()
                        .setPoints(new PointF(51, 25), new PointF(105, 30))
                        .setFallbackText(FALLBACK_TEXT)
                        .build();
        StylusWritingGestureData gestureData = StylusGestureConverter.createGestureData(gesture);

        assertNotNull(gestureData);
        assertEquals(StylusWritingGestureAction.REMOVE_SPACES, gestureData.action);
        assertEquals(
                org.chromium.blink.mojom.StylusWritingGestureGranularity.CHARACTER,
                gestureData.granularity);
        assertMojoRectsAreEqual(createMojoRect(51, 25, 0, 0), gestureData.startRect);
        assertMojoRectsAreEqual(createMojoRect(105, 30, 0, 0), gestureData.endRect);
        assertEquals(FALLBACK_TEXT, toJavaString(gestureData.textAlternative));
        assertNull(gestureData.textToInsert);
        histogram.assertExpected();
    }

    @Test
    @SmallTest
    public void testJoinOrSplitGesture() {
        var histogram =
                HistogramWatcher.newSingleRecordWatcher(
                        GESTURE_TYPE_HISTOGRAM,
                        StylusGestureConverter.UmaGestureType.JOIN_OR_SPLIT);
        JoinOrSplitGesture gesture =
                new JoinOrSplitGesture.Builder()
                        .setJoinOrSplitPoint(new PointF(1, 19))
                        .setFallbackText(FALLBACK_TEXT)
                        .build();
        StylusWritingGestureData gestureData = StylusGestureConverter.createGestureData(gesture);

        assertNotNull(gestureData);
        assertEquals(StylusWritingGestureAction.SPLIT_OR_MERGE, gestureData.action);
        assertEquals(
                org.chromium.blink.mojom.StylusWritingGestureGranularity.CHARACTER,
                gestureData.granularity);
        assertMojoRectsAreEqual(createMojoRect(1, 19, 0, 0), gestureData.startRect);
        assertNull(gestureData.endRect);
        assertEquals(FALLBACK_TEXT, toJavaString(gestureData.textAlternative));
        assertNull(gestureData.textToInsert);
        histogram.assertExpected();
    }

    @Test
    @SmallTest
    public void testSelectRangeGesture() {
        var histogram =
                HistogramWatcher.newSingleRecordWatcher(
                        GESTURE_TYPE_HISTOGRAM, StylusGestureConverter.UmaGestureType.SELECT_RANGE);
        SelectRangeGesture gesture =
                new SelectRangeGesture.Builder()
                        .setGranularity(GRANULARITY_WORD)
                        .setSelectionStartArea(new RectF(10, 10, 45, 55))
                        .setSelectionEndArea(new RectF(0, 100, 70, 200))
                        .setFallbackText(FALLBACK_TEXT)
                        .build();
        StylusWritingGestureData gestureData = StylusGestureConverter.createGestureData(gesture);

        assertNotNull(gestureData);
        assertEquals(StylusWritingGestureAction.SELECT_TEXT, gestureData.action);
        assertEquals(
                org.chromium.blink.mojom.StylusWritingGestureGranularity.WORD,
                gestureData.granularity);
        assertMojoRectsAreEqual(createMojoRect(10, 10, 35, 45), gestureData.startRect);
        assertMojoRectsAreEqual(createMojoRect(0, 100, 70, 100), gestureData.endRect);
        assertEquals(FALLBACK_TEXT, toJavaString(gestureData.textAlternative));
        assertNull(gestureData.textToInsert);
        histogram.assertExpected();
    }

    @Test
    @SmallTest
    public void testDeleteRangeGesture() {
        var histogram =
                HistogramWatcher.newSingleRecordWatcher(
                        GESTURE_TYPE_HISTOGRAM, StylusGestureConverter.UmaGestureType.DELETE_RANGE);
        DeleteRangeGesture gesture =
                new DeleteRangeGesture.Builder()
                        .setGranularity(GRANULARITY_CHARACTER)
                        .setDeletionStartArea(new RectF(10, 10, 45, 55))
                        .setDeletionEndArea(new RectF(0, 100, 70, 200))
                        .setFallbackText(FALLBACK_TEXT)
                        .build();
        StylusWritingGestureData gestureData = StylusGestureConverter.createGestureData(gesture);

        assertNotNull(gestureData);
        assertEquals(StylusWritingGestureAction.DELETE_TEXT, gestureData.action);
        assertEquals(
                org.chromium.blink.mojom.StylusWritingGestureGranularity.CHARACTER,
                gestureData.granularity);
        assertMojoRectsAreEqual(createMojoRect(10, 10, 35, 45), gestureData.startRect);
        assertMojoRectsAreEqual(createMojoRect(0, 100, 70, 100), gestureData.endRect);
        assertEquals(FALLBACK_TEXT, toJavaString(gestureData.textAlternative));
        assertNull(gestureData.textToInsert);
        histogram.assertExpected();
    }

    @Test
    @SmallTest
    public void testNullFallbackText() {
        var histogram =
                HistogramWatcher.newSingleRecordWatcher(
                        GESTURE_TYPE_HISTOGRAM, StylusGestureConverter.UmaGestureType.SELECT);
        SelectGesture gesture =
                new SelectGesture.Builder()
                        .setGranularity(GRANULARITY_CHARACTER)
                        .setSelectionArea(new RectF(0, 0, 10, 10))
                        .build();
        StylusWritingGestureData gestureData = StylusGestureConverter.createGestureData(gesture);

        assertNotNull(gestureData);
        assertEquals(StylusWritingGestureAction.SELECT_TEXT, gestureData.action);
        assertEquals(
                org.chromium.blink.mojom.StylusWritingGestureGranularity.CHARACTER,
                gestureData.granularity);
        assertMojoRectsAreEqual(createMojoRect(0, 5, 0, 0), gestureData.startRect);
        assertMojoRectsAreEqual(createMojoRect(10, 5, 0, 0), gestureData.endRect);
        assertEquals("", toJavaString(gestureData.textAlternative));
        assertNull(gestureData.textToInsert);
        histogram.assertExpected();
    }

    private static void assertMojoRectsAreEqual(
            org.chromium.gfx.mojom.Rect expected, org.chromium.gfx.mojom.Rect actual) {
        assertEquals(expected.x, actual.x);
        assertEquals(expected.y, actual.y);
        assertEquals(expected.width, actual.width);
        assertEquals(expected.height, actual.height);
    }

    private static org.chromium.gfx.mojom.Rect createMojoRect(int x, int y, int width, int height) {
        org.chromium.gfx.mojom.Rect rect = new org.chromium.gfx.mojom.Rect();
        rect.x = x;
        rect.y = y;
        rect.width = width;
        rect.height = height;
        return rect;
    }

    private static String toJavaString(org.chromium.mojo_base.mojom.String16 buffer) {
        StringBuilder string = new StringBuilder();
        for (short c : buffer.data) {
            string.append((char) c);
        }
        return string.toString();
    }
}