Introduce Message.USER_LOCAL and added logic to map person to user id
1. Introduce Message.USER_LOCAL to allow caller to specify which
message is from the local user.
2. TextClassifierImpl will now encode the Person object to a user
id.
3. Fixed a bug in Person.equals check
BUG: 111437455
BUG: 111406942
Test: atest frameworks/base/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
Change-Id: I6629f42244a402fa210f87afa88a629c2ca4a510
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
new file mode 100644
index 0000000..8df83c0
--- /dev/null
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 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 android.annotation.NonNull;
+import android.app.Person;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.textclassifier.ActionsSuggestionsModel;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class for action suggestions.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class ActionsSuggestionsHelper {
+ private static final int USER_LOCAL = 0;
+ private static final int FIRST_NON_LOCAL_USER = 1;
+
+ private ActionsSuggestionsHelper() {}
+
+ /**
+ * Converts the messages to a list of native messages object that the model can understand.
+ * <p>
+ * User id encoding - local user is represented as 0, Other users are numbered according to
+ * how far before they spoke last time in the conversation. For example, considering this
+ * conversation:
+ * <ul>
+ * <li> User A: xxx
+ * <li> Local user: yyy
+ * <li> User B: zzz
+ * </ul>
+ * User A will be encoded as 2, user B will be encoded as 1 and local user will be encoded as 0.
+ */
+ @NonNull
+ public static ActionsSuggestionsModel.ConversationMessage[] toNativeMessages(
+ @NonNull List<ConversationActions.Message> messages) {
+ List<ConversationActions.Message> messagesWithText =
+ messages.stream()
+ .filter(message -> !TextUtils.isEmpty(message.getText()))
+ .collect(Collectors.toCollection(ArrayList::new));
+ if (messagesWithText.isEmpty()) {
+ return new ActionsSuggestionsModel.ConversationMessage[0];
+ }
+ int size = messagesWithText.size();
+ // If the last message (the most important one) does not have the Person object, we will
+ // just use the last message and consider this message is sent from a remote user.
+ ConversationActions.Message lastMessage = messages.get(size - 1);
+ boolean useLastMessageOnly = lastMessage.getAuthor() == null;
+ if (useLastMessageOnly) {
+ return new ActionsSuggestionsModel.ConversationMessage[]{
+ new ActionsSuggestionsModel.ConversationMessage(
+ FIRST_NON_LOCAL_USER,
+ lastMessage.getText().toString())};
+ }
+
+ // Encode the messages in the reverse order, stop whenever the Person object is missing.
+ Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>();
+ PersonEncoder personEncoder = new PersonEncoder();
+ for (int i = size - 1; i >= 0; i--) {
+ ConversationActions.Message message = messagesWithText.get(i);
+ if (message.getAuthor() == null) {
+ break;
+ }
+ nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage(
+ personEncoder.encode(message.getAuthor()),
+ message.getText().toString()));
+ }
+ return nativeMessages.toArray(
+ new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]);
+ }
+
+ private static final class PersonEncoder {
+ private final Map<Person, Integer> mMapping = new ArrayMap<>();
+ private int mNextUserId = FIRST_NON_LOCAL_USER;
+
+ private int encode(Person person) {
+ if (ConversationActions.Message.PERSON_USER_LOCAL.equals(person)) {
+ return USER_LOCAL;
+ }
+ Integer result = mMapping.get(person);
+ if (result == null) {
+ mMapping.put(person, mNextUserId);
+ result = mNextUserId;
+ mNextUserId++;
+ }
+ return result;
+ }
+ }
+}
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 5fcf227..1a7b911 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -345,6 +345,16 @@
/** Represents a message in the conversation. */
public static final class Message implements Parcelable {
+ /**
+ * Represents the local user.
+ *
+ * @see Builder#setAuthor(Person)
+ */
+ public static final Person PERSON_USER_LOCAL =
+ new Person.Builder()
+ .setKey("text-classifier-conversation-actions-local-user")
+ .build();
+
@Nullable
private final Person mAuthor;
@Nullable
@@ -446,7 +456,11 @@
@Nullable
private Bundle mExtras;
- /** Sets the person who composed this message. */
+ /**
+ * Sets the person who composed this message.
+ * <p>
+ * Use {@link #PERSON_USER_LOCAL} to represent the local user.
+ */
@NonNull
public Builder setAuthor(@Nullable Person author) {
mAuthor = author;
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 798a820..66da45d 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -40,7 +40,6 @@
import android.provider.Browser;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
-import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
@@ -269,17 +268,17 @@
final ZonedDateTime refTime = ZonedDateTime.now();
final Collection<String> entitiesToIdentify = request.getEntityConfig() != null
? request.getEntityConfig().resolveEntityListModifications(
- getEntitiesForHints(request.getEntityConfig().getHints()))
+ getEntitiesForHints(request.getEntityConfig().getHints()))
: mSettings.getEntityListDefault();
final AnnotatorModel annotatorImpl =
getAnnotatorImpl(request.getDefaultLocales());
final AnnotatorModel.AnnotatedSpan[] annotations =
annotatorImpl.annotate(
- textString,
- new AnnotatorModel.AnnotationOptions(
- refTime.toInstant().toEpochMilli(),
- refTime.getZone().getId(),
- concatenateLocales(request.getDefaultLocales())));
+ textString,
+ new AnnotatorModel.AnnotationOptions(
+ refTime.toInstant().toEpochMilli(),
+ refTime.getZone().getId(),
+ concatenateLocales(request.getDefaultLocales())));
for (AnnotatorModel.AnnotatedSpan span : annotations) {
final AnnotatorModel.ClassificationResult[] results =
span.getClassification();
@@ -373,20 +372,13 @@
// Actions model is optional, fallback if it is not available.
return mFallback.suggestConversationActions(request);
}
- List<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayList<>();
- for (ConversationActions.Message message : request.getConversation()) {
- if (TextUtils.isEmpty(message.getText())) {
- continue;
- }
- // TODO: We need to map the Person object to user id.
- int userId = 1;
- nativeMessages.add(
- new ActionsSuggestionsModel.ConversationMessage(
- userId, message.getText().toString()));
+ ActionsSuggestionsModel.ConversationMessage[] nativeMessages =
+ ActionsSuggestionsHelper.toNativeMessages(request.getConversation());
+ if (nativeMessages.length == 0) {
+ return mFallback.suggestConversationActions(request);
}
ActionsSuggestionsModel.Conversation nativeConversation =
- new ActionsSuggestionsModel.Conversation(nativeMessages.toArray(
- new ActionsSuggestionsModel.ConversationMessage[0]));
+ new ActionsSuggestionsModel.Conversation(nativeMessages);
ActionsSuggestionsModel.ActionSuggestion[] nativeSuggestions =
actionsImpl.suggestActions(nativeConversation, null);