Loading core/java/android/view/textclassifier/SelectionEvent.java +29 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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(); Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading core/java/android/view/textclassifier/TextClassificationSession.java +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading core/java/android/view/textclassifier/TextClassifierEvent.java +123 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. * Loading core/java/android/view/textclassifier/TextClassifierImpl.java +6 −2 Original line number Diff line number Diff line Loading @@ -376,7 +376,6 @@ public final class TextClassifierImpl implements TextClassifier { /** @inheritDoc */ @Override public void onSelectionEvent(SelectionEvent event) { Preconditions.checkNotNull(event); mSessionLogger.writeEvent(event); } Loading @@ -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); } Loading core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java 0 → 100644 +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()); } } Loading
core/java/android/view/textclassifier/SelectionEvent.java +29 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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(); Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading
core/java/android/view/textclassifier/TextClassificationSession.java +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
core/java/android/view/textclassifier/TextClassifierEvent.java +123 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. * Loading
core/java/android/view/textclassifier/TextClassifierImpl.java +6 −2 Original line number Diff line number Diff line Loading @@ -376,7 +376,6 @@ public final class TextClassifierImpl implements TextClassifier { /** @inheritDoc */ @Override public void onSelectionEvent(SelectionEvent event) { Preconditions.checkNotNull(event); mSessionLogger.writeEvent(event); } Loading @@ -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); } Loading
core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java 0 → 100644 +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()); } }