Merge "Convert TextClassifierEvent to SelectionEvent for logging in the default TC." into qt-dev

This commit is contained in:
Abodunrinwa Toki
2019-05-23 19:35:24 +00:00
committed by Android (Google) Code Review
5 changed files with 453 additions and 12 deletions

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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.
*

View File

@@ -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 {
mTextClassifierEventTronLogger.writeEvent(event);
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);
}

View File

@@ -0,0 +1,294 @@
/*
* 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());
}
}