Add g/setScore and g/setEntityTypes to TextClassifierEvent

1. Add get/setScore, so we can see how do scores affect the CTR and
eventually tune the threshold based on it.

2. Convert get/setEntity to get/setEntityTypes.
   Some events like TYPE_ACTIONS_GENERATED and TYPE_ACTIONS_SHOWN
   will need to have a list of entity types. Also, as TRON does not
   support a field of list type. And thus workaround this by having
   3 separate fields in TRON.

4. The result id of action is too easy to clash now as only messages
   are taking into account. Use timestamp and the person object as
   the input to the hash now.

5. Changes in TRON:
   a) We will put conversation_action / language detection, etc to
      category. And we will put the views to subtype.
   b) As suggested by the TRON team, we should not reuse existing fields
   in the clients directly. But we should rather introduce new fields,
   and map them to existing fields in the server. So, TRON team could
   suppress just the new logging if it goes wrong and not affect
   existing loggings.

Test: atest TextClassifierEventTest.java
Tests: atest TextClassifierEventTronLoggerTest.java

BUG: 120803809

Change-Id: I2e71436b323a200beec57ea7c1a8bcc96c9c552f
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
index 77cb4cd..fdc34b3 100644
--- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -103,10 +103,9 @@
         final String modelName = String.format(
                 Locale.US, "%s_v%d", localesJoiner.toString(), modelVersion);
         final int hash = Objects.hash(
-                messages.stream()
-                        .map(ConversationActions.Message::getText)
-                        .collect(Collectors.toList()),
-                context.getPackageName());
+                messages.stream().mapToInt(ActionsSuggestionsHelper::hashMessage),
+                context.getPackageName(),
+                System.currentTimeMillis());
         return SelectionSessionLogger.SignatureParser.createSignature(
                 SelectionSessionLogger.CLASSIFIER_ID, modelName, hash);
     }
@@ -128,4 +127,8 @@
             return result;
         }
     }
+
+    private static int hashMessage(ConversationActions.Message message) {
+        return Objects.hash(message.getAuthor(), message.getText(), message.getReferenceTime());
+    }
 }
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index b84f6f0..cd13cc0 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -72,7 +72,7 @@
              TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION,
              TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION,
              TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL,
-             TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY})
+             TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED})
     public @interface Type {
         // For custom event types, use range 1,000,000+.
     }
@@ -121,7 +121,7 @@
 
     @Category private final int mEventCategory;
     @Type private final int mEventType;
-    @Nullable private final String mEntityType;
+    @Nullable private final String[] mEntityTypes;
     @Nullable private final TextClassificationContext mEventContext;
     @Nullable private final String mResultId;
     private final int mEventIndex;
@@ -139,11 +139,12 @@
 
     // Language detection.
     @Nullable private final String mLanguage;
+    private final float mScore;
 
     private TextClassifierEvent(
             int eventCategory,
             int eventType,
-            String entityType,
+            String[] entityTypes,
             TextClassificationContext eventContext,
             String resultId,
             int eventIndex,
@@ -154,10 +155,11 @@
             int relativeSuggestedWordStartIndex,
             int relativeSuggestedWordEndIndex,
             int[] actionIndex,
-            String language) {
+            String language,
+            float score) {
         mEventCategory = eventCategory;
         mEventType = eventType;
-        mEntityType = entityType;
+        mEntityTypes = entityTypes;
         mEventContext = eventContext;
         mResultId = resultId;
         mEventIndex = eventIndex;
@@ -169,6 +171,7 @@
         mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex;
         mActionIndices = actionIndex;
         mLanguage = language;
+        mScore = score;
     }
 
     @Override
@@ -180,7 +183,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mEventCategory);
         dest.writeInt(mEventType);
-        dest.writeString(mEntityType);
+        dest.writeStringArray(mEntityTypes);
         dest.writeParcelable(mEventContext, flags);
         dest.writeString(mResultId);
         dest.writeInt(mEventIndex);
@@ -192,13 +195,14 @@
         dest.writeInt(mRelativeSuggestedWordEndIndex);
         dest.writeIntArray(mActionIndices);
         dest.writeString(mLanguage);
+        dest.writeFloat(mScore);
     }
 
     private static TextClassifierEvent readFromParcel(Parcel in) {
         return new TextClassifierEvent(
                 /* eventCategory= */ in.readInt(),
                 /* eventType= */ in.readInt(),
-                /* entityType= */ in.readString(),
+                /* entityTypes=*/ in.readStringArray(),
                 /* eventContext= */ in.readParcelable(null),
                 /* resultId= */ in.readString(),
                 /* eventIndex= */ in.readInt(),
@@ -209,7 +213,8 @@
                 /* relativeSuggestedWordStartIndex= */ in.readInt(),
                 /* relativeSuggestedWordEndIndex= */ in.readInt(),
                 /* actionIndices= */ in.createIntArray(),
-                /* language= */ in.readString());
+                /* language= */ in.readString(),
+                /* score= */ in.readFloat());
     }
 
     /**
@@ -229,11 +234,11 @@
     }
 
     /**
-     * Returns the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+     * Returns an array of entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
      */
-    @Nullable
-    public String getEntityType() {
-        return mEntityType;
+    @NonNull
+    public String[] getEntityTypes() {
+        return mEntityTypes;
     }
 
     /**
@@ -327,13 +332,20 @@
     }
 
     /**
+     * Returns the score of the suggestion.
+     */
+    public float getScore() {
+        return mScore;
+    }
+
+    /**
      * Builder to build a text classifier event.
      */
     public static final class Builder {
 
         private final int mEventCategory;
         private final int mEventType;
-        @Nullable private String mEntityType;
+        private String[] mEntityTypes = new String[0];
         @Nullable private TextClassificationContext mEventContext;
         @Nullable private String mResultId;
         private int mEventIndex;
@@ -345,6 +357,7 @@
         private int mRelativeSuggestedWordEndIndex;
         private int[] mActionIndices = new int[0];
         @Nullable private String mLanguage;
+        private float mScore;
 
         /**
          * Creates a builder for building {@link TextClassifierEvent}s.
@@ -358,11 +371,12 @@
         }
 
         /**
-         * Sets the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+         * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
          */
         @NonNull
-        public Builder setEntityType(@Nullable String entityType) {
-            mEntityType = entityType;
+        public Builder setEntityTypes(@NonNull String... entityTypes) {
+            mEntityTypes = new String[entityTypes.length];
+            System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length);
             return this;
         }
 
@@ -478,6 +492,15 @@
         }
 
         /**
+         * Sets the score of the suggestion.
+         */
+        @NonNull
+        public Builder setScore(float score) {
+            mScore = score;
+            return this;
+        }
+
+        /**
          * Builds and returns a text classifier event.
          */
         @NonNull
@@ -486,7 +509,7 @@
             return new TextClassifierEvent(
                     mEventCategory,
                     mEventType,
-                    mEntityType,
+                    mEntityTypes,
                     mEventContext,
                     mResultId,
                     mEventIndex,
@@ -497,7 +520,8 @@
                     mRelativeSuggestedWordStartIndex,
                     mRelativeSuggestedWordEndIndex,
                     mActionIndices,
-                    mLanguage);
+                    mLanguage,
+                    mScore);
         }
         // TODO: Add build(boolean validate).
     }
@@ -507,7 +531,7 @@
         StringBuilder out = new StringBuilder(128);
         out.append("TextClassifierEvent{");
         out.append("mEventCategory=").append(mEventCategory);
-        out.append(", mEventType=").append(mEventType);
+        out.append(", mEventTypes=").append(Arrays.toString(mEntityTypes));
         out.append(", mEventContext=").append(mEventContext);
         out.append(", mResultId=").append(mResultId);
         out.append(", mEventIndex=").append(mEventIndex);
@@ -519,6 +543,7 @@
         out.append(", mRelativeSuggestedWordEndIndex=").append(mRelativeSuggestedWordEndIndex);
         out.append(", mActionIndices=").append(Arrays.toString(mActionIndices));
         out.append(", mLanguage=").append(mLanguage);
+        out.append(", mScore=").append(mScore);
         out.append("}");
         return out.toString();
     }
diff --git a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
index 439e594..5563dfc 100644
--- a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
+++ b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
@@ -15,12 +15,15 @@
  */
 package android.view.textclassifier;
 
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_SESSION_ID;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SESSION_ID;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_VERSION;
 
 import android.metrics.LogMaker;
 
@@ -60,16 +63,30 @@
             return;
         }
         final LogMaker log = new LogMaker(category)
-                .setType(getLogType(event))
-                .addTaggedData(FIELD_SELECTION_SESSION_ID, event.getResultId())
+                .setSubtype(getLogType(event))
+                .addTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID, event.getResultId())
                 .addTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME, event.getEventTime())
                 .addTaggedData(FIELD_TEXTCLASSIFIER_MODEL,
                         SelectionSessionLogger.SignatureParser.getModelName(event.getResultId()))
-                .addTaggedData(FIELD_SELECTION_ENTITY_TYPE, event.getEntityType());
+                .addTaggedData(FIELD_TEXT_CLASSIFIER_SCORE, event.getScore());
+
+        String[] entityTypes = event.getEntityTypes();
+        // TRON does not support a field of list type, and thus workaround by store them
+        // in three separate fields. This is no longer an issue once we have moved to Westworld.
+        if (entityTypes.length >= 1) {
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE, entityTypes[0]);
+        }
+        if (entityTypes.length >= 2) {
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE, entityTypes[1]);
+        }
+        if (entityTypes.length >= 3) {
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE, entityTypes[2]);
+        }
         TextClassificationContext eventContext = event.getEventContext();
         if (eventContext != null) {
-            log.addTaggedData(FIELD_SELECTION_WIDGET_TYPE, eventContext.getWidgetType());
-            log.addTaggedData(FIELD_SELECTION_WIDGET_VERSION, eventContext.getWidgetVersion());
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE, eventContext.getWidgetType());
+            log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION,
+                    eventContext.getWidgetVersion());
             log.setPackageName(eventContext.getPackageName());
         }
         mMetricsLogger.write(log);
@@ -94,6 +111,8 @@
                 return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN;
             case TextClassifierEvent.TYPE_MANUAL_REPLY:
                 return MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY;
+            case TextClassifierEvent.TYPE_ACTIONS_GENERATED:
+                return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED;
             default:
                 return MetricsEvent.VIEW_UNKNOWN;
         }
@@ -127,14 +146,22 @@
         if (!Log.ENABLE_FULL_LOGGING) {
             return;
         }
-        final String id = String.valueOf(log.getTaggedData(FIELD_SELECTION_SESSION_ID));
+        final String id = String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID));
         final String categoryName = toCategoryName(log.getCategory());
-        final String eventName = toEventName(log.getType());
-        final String widgetType = String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_TYPE));
+        final String eventName = toEventName(log.getSubtype());
+        final String widgetType =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE));
         final String widgetVersion =
-                String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_VERSION));
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION));
         final String model = String.valueOf(log.getTaggedData(FIELD_TEXTCLASSIFIER_MODEL));
-        final String entityType = String.valueOf(log.getTaggedData(FIELD_SELECTION_ENTITY_TYPE));
+        final String firstEntityType =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE));
+        final String secondEntityType =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE));
+        final String thirdEntityType =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE));
+        final String score =
+                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE));
 
         StringBuilder builder = new StringBuilder();
         builder.append("writeEvent: ");
@@ -144,7 +171,10 @@
         builder.append(", widgetType=").append(widgetType);
         builder.append(", widgetVersion=").append(widgetVersion);
         builder.append(", model=").append(model);
-        builder.append(", entityType=").append(entityType);
+        builder.append(", firstEntityType=").append(firstEntityType);
+        builder.append(", secondEntityType=").append(secondEntityType);
+        builder.append(", thirdEntityType=").append(thirdEntityType);
+        builder.append(", score=").append(score);
 
         Log.v(TAG, builder.toString());
     }
diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
index b1b7416..73af567 100644
--- a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
@@ -18,9 +18,10 @@
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.CONVERSATION_ACTIONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -71,7 +72,8 @@
                 new TextClassifierEvent.Builder(
                         TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS,
                         TextClassifierEvent.TYPE_SMART_ACTION)
-                        .setEntityType(ConversationAction.TYPE_CALL_PHONE)
+                        .setEntityTypes(ConversationAction.TYPE_CALL_PHONE)
+                        .setScore(0.5f)
                         .setEventTime(EVENT_TIME)
                         .setEventContext(textClassificationContext)
                         .build();
@@ -83,15 +85,18 @@
         LogMaker logMaker = captor.getValue();
         assertThat(logMaker.getCategory()).isEqualTo(
                 CONVERSATION_ACTIONS);
-        assertThat(logMaker.getType()).isEqualTo(
+        assertThat(logMaker.getSubtype()).isEqualTo(
                 ACTION_TEXT_SELECTION_SMART_SHARE);
-        assertThat(logMaker.getTaggedData(FIELD_SELECTION_ENTITY_TYPE))
+        assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE))
                 .isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+        assertThat((float) logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE))
+                .isWithin(0.00001f).of(0.5f);
         assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME))
                 .isEqualTo(EVENT_TIME);
         assertThat(logMaker.getPackageName()).isEqualTo(PACKAGE_NAME);
-        assertThat(logMaker.getTaggedData(FIELD_SELECTION_WIDGET_TYPE))
+        assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE))
                 .isEqualTo(WIDGET_TYPE);
+
     }
 
     @Test