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

Commit 5258633e authored by Abodunrinwa Toki's avatar Abodunrinwa Toki
Browse files

Convert TextClassifierEvent to SelectionEvent

for logging in the default TC.

TCEvents for selection and links are not currently being written to
default TC logs. This changelist writes these events as SelEvents.

Bug: 131228248
Test: atest android.view.textclassifier.TextClassifierEventTest
Change-Id: I191f2f9281eab1b8a427ef21717fff283a304a22
parent 2cb08912
Loading
Loading
Loading
Loading
+29 −10
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.os.Parcelable;
import android.view.textclassifier.TextClassifier.EntityType;
import android.view.textclassifier.TextClassifier.WidgetType;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
@@ -115,7 +116,7 @@ public final class SelectionEvent implements Parcelable {
    /** Unknown invocation method */
    public static final int INVOCATION_UNKNOWN = 0;

    private static final String NO_SIGNATURE = "";
    static final String NO_SIGNATURE = "";

    private final int mAbsoluteStart;
    private final int mAbsoluteEnd;
@@ -374,8 +375,10 @@ public final class SelectionEvent implements Parcelable {

    /**
     * Sets the event type.
     * @hide
     */
    void setEventType(@EventType int eventType) {
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void setEventType(@EventType int eventType) {
        mEventType = eventType;
    }

@@ -416,8 +419,10 @@ public final class SelectionEvent implements Parcelable {

    /**
     * Sets the {@link TextClassificationContext} for this event.
     * @hide
     */
    void setTextClassificationSessionContext(TextClassificationContext context) {
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void setTextClassificationSessionContext(TextClassificationContext context) {
        mPackageName = context.getPackageName();
        mWidgetType = context.getWidgetType();
        mWidgetVersion = context.getWidgetVersion();
@@ -432,8 +437,10 @@ public final class SelectionEvent implements Parcelable {

    /**
     * Sets the invocationMethod for this event.
     * @hide
     */
    void setInvocationMethod(@InvocationMethod int invocationMethod) {
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void setInvocationMethod(@InvocationMethod int invocationMethod) {
        mInvocationMethod = invocationMethod;
    }

@@ -495,7 +502,9 @@ public final class SelectionEvent implements Parcelable {
        return mEventIndex;
    }

    SelectionEvent setEventIndex(int index) {
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public SelectionEvent setEventIndex(int index) {
        mEventIndex = index;
        return this;
    }
@@ -508,7 +517,9 @@ public final class SelectionEvent implements Parcelable {
        return mSessionId;
    }

    SelectionEvent setSessionId(TextClassificationSessionId id) {
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public SelectionEvent setSessionId(@Nullable TextClassificationSessionId id) {
        mSessionId = id;
        return this;
    }
@@ -521,7 +532,9 @@ public final class SelectionEvent implements Parcelable {
        return mStart;
    }

    SelectionEvent setStart(int start) {
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public SelectionEvent setStart(int start) {
        mStart = start;
        return this;
    }
@@ -534,7 +547,9 @@ public final class SelectionEvent implements Parcelable {
        return mEnd;
    }

    SelectionEvent setEnd(int end) {
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public SelectionEvent setEnd(int end) {
        mEnd = end;
        return this;
    }
@@ -547,7 +562,9 @@ public final class SelectionEvent implements Parcelable {
        return mSmartStart;
    }

    SelectionEvent setSmartStart(int start) {
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public SelectionEvent setSmartStart(int start) {
        this.mSmartStart = start;
        return this;
    }
@@ -560,7 +577,9 @@ public final class SelectionEvent implements Parcelable {
        return mSmartEnd;
    }

    SelectionEvent setSmartEnd(int end) {
    /** @hide */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public SelectionEvent setSmartEnd(int end) {
        mSmartEnd = end;
        return this;
    }
+1 −0
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ final class TextClassificationSession implements TextClassifier {
    @Override
    public void onTextClassifierEvent(TextClassifierEvent event) {
        try {
            event.mHiddenTempSessionId = mSessionId;
            mDelegate.onTextClassifierEvent(event);
        } catch (Exception e) {
            // Avoid crashing for event reporting.
+123 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
@@ -150,6 +151,14 @@ public abstract class TextClassifierEvent implements Parcelable {
    private final ULocale mLocale;
    private final Bundle mExtras;

    /**
     * Session id holder to help with converting this event to the legacy SelectionEvent.
     * @hide
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    @Nullable
    public TextClassificationSessionId mHiddenTempSessionId;

    private TextClassifierEvent(Builder builder) {
        mEventCategory = builder.mEventCategory;
        mEventType = builder.mEventType;
@@ -359,6 +368,120 @@ public abstract class TextClassifierEvent implements Parcelable {
        return out.toString();
    }

    /**
     * Returns a {@link SelectionEvent} equivalent of this event; or {@code null} if it can not be
     * converted to a {@link SelectionEvent}.
     * @hide
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    @Nullable
    public final SelectionEvent toSelectionEvent() {
        final int invocationMethod;
        switch (getEventCategory()) {
            case TextClassifierEvent.CATEGORY_SELECTION:
                invocationMethod = SelectionEvent.INVOCATION_MANUAL;
                break;
            case TextClassifierEvent.CATEGORY_LINKIFY:
                invocationMethod = SelectionEvent.INVOCATION_LINK;
                break;
            default:
                // Cannot be converted to a SelectionEvent.
                return null;
        }

        final String entityType = getEntityTypes().length > 0
                ? getEntityTypes()[0] : TextClassifier.TYPE_UNKNOWN;
        final SelectionEvent out = new SelectionEvent(
                /* absoluteStart= */ 0,
                /* absoluteEnd= */ 0,
                /* eventType= */0,
                entityType,
                SelectionEvent.INVOCATION_UNKNOWN,
                SelectionEvent.NO_SIGNATURE);
        out.setInvocationMethod(invocationMethod);

        final TextClassificationContext eventContext = getEventContext();
        if (eventContext != null) {
            out.setTextClassificationSessionContext(getEventContext());
        }
        out.setSessionId(mHiddenTempSessionId);
        final String resultId = getResultId();
        out.setResultId(resultId == null ? SelectionEvent.NO_SIGNATURE : resultId);
        out.setEventIndex(getEventIndex());


        final int eventType;
        switch (getEventType()) {
            case TextClassifierEvent.TYPE_SELECTION_STARTED:
                eventType = SelectionEvent.EVENT_SELECTION_STARTED;
                break;
            case TextClassifierEvent.TYPE_SELECTION_MODIFIED:
                eventType = SelectionEvent.EVENT_SELECTION_MODIFIED;
                break;
            case TextClassifierEvent.TYPE_SMART_SELECTION_SINGLE:
                eventType = SelectionEvent.EVENT_SMART_SELECTION_SINGLE;
                break;
            case TextClassifierEvent.TYPE_SMART_SELECTION_MULTI:
                eventType = SelectionEvent.EVENT_SMART_SELECTION_MULTI;
                break;
            case TextClassifierEvent.TYPE_AUTO_SELECTION:
                eventType = SelectionEvent.EVENT_AUTO_SELECTION;
                break;
            case TextClassifierEvent.TYPE_OVERTYPE:
                eventType = SelectionEvent.ACTION_OVERTYPE;
                break;
            case TextClassifierEvent.TYPE_COPY_ACTION:
                eventType = SelectionEvent.ACTION_COPY;
                break;
            case TextClassifierEvent.TYPE_PASTE_ACTION:
                eventType = SelectionEvent.ACTION_PASTE;
                break;
            case TextClassifierEvent.TYPE_CUT_ACTION:
                eventType = SelectionEvent.ACTION_CUT;
                break;
            case TextClassifierEvent.TYPE_SHARE_ACTION:
                eventType = SelectionEvent.ACTION_SHARE;
                break;
            case TextClassifierEvent.TYPE_SMART_ACTION:
                eventType = SelectionEvent.ACTION_SMART_SHARE;
                break;
            case TextClassifierEvent.TYPE_SELECTION_DRAG:
                eventType = SelectionEvent.ACTION_DRAG;
                break;
            case TextClassifierEvent.TYPE_SELECTION_DESTROYED:
                eventType = SelectionEvent.ACTION_ABANDON;
                break;
            case TextClassifierEvent.TYPE_OTHER_ACTION:
                eventType = SelectionEvent.ACTION_OTHER;
                break;
            case TextClassifierEvent.TYPE_SELECT_ALL:
                eventType = SelectionEvent.ACTION_SELECT_ALL;
                break;
            case TextClassifierEvent.TYPE_SELECTION_RESET:
                eventType = SelectionEvent.ACTION_RESET;
                break;
            default:
                eventType = 0;
                break;
        }
        out.setEventType(eventType);

        if (this instanceof TextClassifierEvent.TextSelectionEvent) {
            final TextClassifierEvent.TextSelectionEvent selEvent =
                    (TextClassifierEvent.TextSelectionEvent) this;
            // TODO: Ideally, we should have these fields in events of type
            // TextClassifierEvent.TextLinkifyEvent events too but we're now past the API deadline
            // and will have to do with these fields being set only in TextSelectionEvent events.
            // Fix this at the next API bump.
            out.setStart(selEvent.getRelativeWordStartIndex());
            out.setEnd(selEvent.getRelativeWordEndIndex());
            out.setSmartStart(selEvent.getRelativeSuggestedWordStartIndex());
            out.setSmartEnd(selEvent.getRelativeSuggestedWordEndIndex());
        }

        return out;
    }

    /**
     * Builder to build a text classifier event.
     *
+6 −2
Original line number Diff line number Diff line
@@ -376,7 +376,6 @@ public final class TextClassifierImpl implements TextClassifier {
    /** @inheritDoc */
    @Override
    public void onSelectionEvent(SelectionEvent event) {
        Preconditions.checkNotNull(event);
        mSessionLogger.writeEvent(event);
    }

@@ -386,7 +385,12 @@ public final class TextClassifierImpl implements TextClassifier {
            Log.d(DEFAULT_LOG_TAG, "onTextClassifierEvent() called with: event = [" + event + "]");
        }
        try {
            final SelectionEvent selEvent = event.toSelectionEvent();
            if (selEvent != null) {
                mSessionLogger.writeEvent(selEvent);
            } else {
                mTextClassifierEventTronLogger.writeEvent(event);
            }
        } catch (Exception e) {
            Log.e(LOG_TAG, "Error writing event", e);
        }
+294 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.textclassifier;

import static com.google.common.truth.Truth.assertWithMessage;

import android.annotation.Nullable;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

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

@SmallTest
@RunWith(AndroidJUnit4.class)
public final class TextClassifierEventTest {

    private static final TextClassificationContext TC_CONTEXT =
            new TextClassificationContext.Builder("pkg", TextClassifier.WIDGET_TYPE_TEXTVIEW)
                    .build();

    private static final TextSelection TEXT_SELECTION = new TextSelection.Builder(10, 20)
            .setEntityType(TextClassifier.TYPE_ADDRESS, 1)
            .setId("id1")
            .build();

    private static final TextClassification TEXT_CLASSIFICATION = new TextClassification.Builder()
            .setEntityType(TextClassifier.TYPE_DATE, 1)
            .setId("id2")
            .build();

    @Test
    public void toSelectionEvent_selectionStarted() {
        final TextClassificationSessionId sessionId = new TextClassificationSessionId();
        final SelectionEvent expected = SelectionEvent.createSelectionStartedEvent(
                SelectionEvent.INVOCATION_MANUAL, 0);
        expected.setTextClassificationSessionContext(TC_CONTEXT);
        expected.setSessionId(sessionId);

        final TextClassifierEvent event = new TextClassifierEvent.TextSelectionEvent.Builder(
                TextClassifierEvent.TYPE_SELECTION_STARTED)
                .setEventContext(TC_CONTEXT)
                .build();
        event.mHiddenTempSessionId = sessionId;

        assertEquals(expected, event.toSelectionEvent());
    }

    @Test
    public void toSelectionEvent_smartSelectionMulti() {
        final int start = -1;
        final int end = 2;
        final int eventIndex = 1;
        final SelectionEvent expected = SelectionEvent.createSelectionModifiedEvent(
                0, 3, TEXT_SELECTION);
        expected.setInvocationMethod(SelectionEvent.INVOCATION_MANUAL);
        expected.setEventType(SelectionEvent.EVENT_SMART_SELECTION_MULTI);
        expected.setStart(start);
        expected.setEnd(end);
        expected.setEventIndex(eventIndex);
        expected.setTextClassificationSessionContext(TC_CONTEXT);

        final String entityType = TEXT_SELECTION.getEntity(0);
        final TextClassifierEvent event = new TextClassifierEvent.TextSelectionEvent.Builder(
                TextClassifierEvent.TYPE_SMART_SELECTION_MULTI)
                .setEventContext(TC_CONTEXT)
                .setResultId(TEXT_SELECTION.getId())
                .setRelativeWordStartIndex(start)
                .setRelativeWordEndIndex(end)
                .setEntityTypes(entityType)
                .setScores(TEXT_SELECTION.getConfidenceScore(entityType))
                .setEventIndex(eventIndex)
                .build();

        assertEquals(expected, event.toSelectionEvent());
    }

    @Test
    public void toSelectionEvent_smartSelectionSingle() {
        final int start = 0;
        final int end = 1;
        final int eventIndex = 2;
        final SelectionEvent expected = SelectionEvent.createSelectionModifiedEvent(
                0, 1, TEXT_SELECTION);
        expected.setInvocationMethod(SelectionEvent.INVOCATION_MANUAL);
        expected.setEventType(SelectionEvent.EVENT_SMART_SELECTION_SINGLE);
        expected.setStart(start);
        expected.setEnd(end);
        expected.setEventIndex(eventIndex);
        expected.setTextClassificationSessionContext(TC_CONTEXT);

        final String entityType = TEXT_SELECTION.getEntity(0);
        final TextClassifierEvent event = new TextClassifierEvent.TextSelectionEvent.Builder(
                TextClassifierEvent.TYPE_SMART_SELECTION_SINGLE)
                .setEventContext(TC_CONTEXT)
                .setResultId(TEXT_SELECTION.getId())
                .setRelativeWordStartIndex(start)
                .setRelativeWordEndIndex(end)
                .setEntityTypes(entityType)
                .setScores(TEXT_SELECTION.getConfidenceScore(entityType))
                .setEventIndex(eventIndex)
                .build();

        assertEquals(expected, event.toSelectionEvent());
    }

    @Test
    public void toSelectionEvent_resetSelection() {
        final int start = 0;
        final int end = 1;
        final int smartStart = -1;
        final int smartEnd = 2;
        final int eventIndex = 3;
        final SelectionEvent expected = SelectionEvent.createSelectionActionEvent(
                0, 1, SelectionEvent.ACTION_RESET, TEXT_CLASSIFICATION);
        expected.setInvocationMethod(SelectionEvent.INVOCATION_MANUAL);
        expected.setStart(start);
        expected.setEnd(end);
        expected.setSmartStart(smartStart);
        expected.setSmartEnd(smartEnd);
        expected.setEventIndex(eventIndex);
        expected.setTextClassificationSessionContext(TC_CONTEXT);

        final String entityType = TEXT_CLASSIFICATION.getEntity(0);
        final TextClassifierEvent event = new TextClassifierEvent.TextSelectionEvent.Builder(
                TextClassifierEvent.TYPE_SELECTION_RESET)
                .setEventContext(TC_CONTEXT)
                .setResultId(TEXT_CLASSIFICATION.getId())
                .setRelativeSuggestedWordStartIndex(smartStart)
                .setRelativeSuggestedWordEndIndex(smartEnd)
                .setRelativeWordStartIndex(start)
                .setRelativeWordEndIndex(end)
                .setEntityTypes(TEXT_CLASSIFICATION.getEntity(0))
                .setScores(TEXT_CLASSIFICATION.getConfidenceScore(entityType))
                .setEventIndex(eventIndex)
                .build();

        assertEquals(expected, event.toSelectionEvent());
    }

    @Test
    public void toSelectionEvent_modifySelection() {
        final int start = -1;
        final int end = 5;
        final int eventIndex = 4;
        final SelectionEvent expected = SelectionEvent.createSelectionModifiedEvent(0, 1);
        expected.setInvocationMethod(SelectionEvent.INVOCATION_MANUAL);
        expected.setStart(start);
        expected.setEnd(end);
        expected.setEventIndex(eventIndex);
        expected.setTextClassificationSessionContext(TC_CONTEXT);

        final TextClassifierEvent event = new TextClassifierEvent.TextSelectionEvent.Builder(
                TextClassifierEvent.TYPE_SELECTION_MODIFIED)
                .setEventContext(TC_CONTEXT)
                .setRelativeWordStartIndex(start)
                .setRelativeWordEndIndex(end)
                .setEventIndex(eventIndex)
                .build();

        assertEquals(expected, event.toSelectionEvent());
    }

    @Test
    public void toSelectionEvent_copyAction() {
        final int start = 3;
        final int end = 4;
        final int eventIndex = 5;
        final SelectionEvent expected = SelectionEvent.createSelectionActionEvent(
                5, 6, SelectionEvent.ACTION_COPY);
        expected.setInvocationMethod(SelectionEvent.INVOCATION_MANUAL);
        expected.setStart(start);
        expected.setEnd(end);
        expected.setEventIndex(eventIndex);
        expected.setTextClassificationSessionContext(TC_CONTEXT);

        final TextClassifierEvent event = new TextClassifierEvent.TextSelectionEvent.Builder(
                TextClassifierEvent.TYPE_COPY_ACTION)
                .setEventContext(TC_CONTEXT)
                .setRelativeWordStartIndex(start)
                .setRelativeWordEndIndex(end)
                .setEventIndex(eventIndex)
                .build();

        assertEquals(expected, event.toSelectionEvent());
    }

    @Test
    public void toSelectionEvent_selectionDismissed() {
        final int eventIndex = 6;
        final SelectionEvent expected = SelectionEvent.createSelectionActionEvent(
                0, 1, SelectionEvent.ACTION_ABANDON);
        expected.setInvocationMethod(SelectionEvent.INVOCATION_MANUAL);
        expected.setEventIndex(eventIndex);

        final TextClassifierEvent event = new TextClassifierEvent.TextSelectionEvent.Builder(
                TextClassifierEvent.TYPE_SELECTION_DESTROYED)
                .setEventIndex(eventIndex)
                .build();

        assertEquals(expected, event.toSelectionEvent());
    }

    @Test
    public void toSelectionEvent_link_smartAction() {
        final int eventIndex = 2;
        final SelectionEvent expected = SelectionEvent.createSelectionActionEvent(
                1, 9, SelectionEvent.ACTION_SMART_SHARE, TEXT_CLASSIFICATION);
        expected.setInvocationMethod(SelectionEvent.INVOCATION_LINK);
        // TODO: TextLinkifyEvent API is missing APIs to set text indices. See related comment in
        // TextClassifierEvent.
        expected.setEventIndex(eventIndex);
        expected.setTextClassificationSessionContext(TC_CONTEXT);

        final String entityType = TEXT_CLASSIFICATION.getEntity(0);
        final TextClassifierEvent event = new TextClassifierEvent.TextLinkifyEvent.Builder(
                TextClassifierEvent.TYPE_SMART_ACTION)
                .setEventContext(TC_CONTEXT)
                .setResultId(TEXT_CLASSIFICATION.getId())
                .setEntityTypes(entityType)
                .setScores(TEXT_CLASSIFICATION.getConfidenceScore(entityType))
                .setActionIndices(0)
                .setEventIndex(eventIndex)
                .build();

        assertEquals(expected, event.toSelectionEvent());
    }

    @Test
    public void toSelectionEvent_nonSelectionOrLinkifyEvent() {
        final TextClassifierEvent convActionEvent =
                new TextClassifierEvent.ConversationActionsEvent.Builder(
                        TextClassifierEvent.TYPE_ACTIONS_GENERATED)
                        .build();
        assertWithMessage("conversation action event")
                .that(convActionEvent.toSelectionEvent()).isNull();

        final TextClassifierEvent langDetEvent =
                new TextClassifierEvent.ConversationActionsEvent.Builder(
                        TextClassifierEvent.TYPE_ACTIONS_GENERATED)
                        .setEventContext(TC_CONTEXT)
                        .build();
        assertWithMessage("language detection event")
                .that(langDetEvent.toSelectionEvent()).isNull();
    }

    private static void assertEquals(
            @Nullable SelectionEvent expected, @Nullable SelectionEvent actual) {
        if (expected == null && actual == null) return;
        if (expected == actual) return;
        assertWithMessage("actual").that(actual).isNotNull();
        assertWithMessage("expected").that(expected).isNotNull();
        assertWithMessage("eventType")
                .that(actual.getEventType()).isEqualTo(expected.getEventType());
        assertWithMessage("packageName")
                .that(actual.getPackageName()).isEqualTo(expected.getPackageName());
        assertWithMessage("widgetType")
                .that(actual.getWidgetType()).isEqualTo(expected.getWidgetType());
        assertWithMessage("widgetVersion")
                .that(actual.getWidgetVersion()).isEqualTo(expected.getWidgetVersion());
        assertWithMessage("invocationMethod")
                .that(actual.getInvocationMethod()).isEqualTo(expected.getInvocationMethod());
        assertWithMessage("resultId")
                .that(actual.getResultId()).isEqualTo(expected.getResultId());
        assertWithMessage("sessionId")
                .that(actual.getSessionId()).isEqualTo(expected.getSessionId());
        assertWithMessage("entityType")
                .that(actual.getEntityType()).isEqualTo(expected.getEntityType());
        assertWithMessage("eventIndex")
                .that(actual.getEventIndex()).isEqualTo(expected.getEventIndex());
        assertWithMessage("start")
                .that(actual.getStart()).isEqualTo(expected.getStart());
        assertWithMessage("end")
                .that(actual.getEnd()).isEqualTo(expected.getEnd());
        assertWithMessage("smartStart")
                .that(actual.getSmartStart()).isEqualTo(expected.getSmartStart());
        assertWithMessage("smartEnd")
                .that(actual.getSmartEnd()).isEqualTo(expected.getSmartEnd());
    }
}