Loading api/current.txt +4 −4 Original line number Diff line number Diff line Loading @@ -52563,19 +52563,19 @@ package android.view.textclassifier { method public int describeContents(); method public android.app.Person getAuthor(); method public android.os.Bundle getExtras(); method public java.time.ZonedDateTime getReferenceTime(); method public java.lang.CharSequence getText(); method public java.time.ZonedDateTime getTime(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR; field public static final android.app.Person PERSON_USER_LOCAL; field public static final android.app.Person PERSON_USER_REMOTE; } public static final class ConversationActions.Message.Builder { ctor public ConversationActions.Message.Builder(); ctor public ConversationActions.Message.Builder(android.app.Person); method public android.view.textclassifier.ConversationActions.Message build(); method public android.view.textclassifier.ConversationActions.Message.Builder setAuthor(android.app.Person); method public android.view.textclassifier.ConversationActions.Message.Builder setComposeTime(java.time.ZonedDateTime); method public android.view.textclassifier.ConversationActions.Message.Builder setExtras(android.os.Bundle); method public android.view.textclassifier.ConversationActions.Message.Builder setReferenceTime(java.time.ZonedDateTime); method public android.view.textclassifier.ConversationActions.Message.Builder setText(java.lang.CharSequence); } core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +9 −22 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package android.view.textclassifier; import android.annotation.NonNull; import android.app.Person; import android.text.TextUtils; import android.util.ArrayMap; Loading @@ -30,6 +29,7 @@ import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** Loading Loading @@ -57,9 +57,9 @@ public final class ActionsSuggestionsHelper { * </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> messages, Function<CharSequence, String> languageDetector) { List<ConversationActions.Message> messagesWithText = messages.stream() .filter(message -> !TextUtils.isEmpty(message.getText())) Loading @@ -67,31 +67,18 @@ public final class ActionsSuggestionsHelper { 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(), 0, null)}; } // Encode the messages in the reverse order, stop whenever the Person object is missing. Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>(); PersonEncoder personEncoder = new PersonEncoder(); int size = messagesWithText.size(); for (int i = size - 1; i >= 0; i--) { ConversationActions.Message message = messagesWithText.get(i); if (message.getAuthor() == null) { break; } long referenceTime = message.getReferenceTime() == null ? 0 : message.getReferenceTime().toInstant().toEpochMilli(); nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage( personEncoder.encode(message.getAuthor()), message.getText().toString(), 0, null)); message.getText().toString(), referenceTime, languageDetector.apply(message.getText()))); } return nativeMessages.toArray( new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]); Loading core/java/android/view/textclassifier/ConversationActions.java +46 −25 Original line number Diff line number Diff line Loading @@ -349,17 +349,31 @@ public final class ConversationActions implements Parcelable { /** * Represents the local user. * * @see Builder#setAuthor(Person) * @see Builder#Builder(Person) */ public static final Person PERSON_USER_LOCAL = new Person.Builder() .setKey("text-classifier-conversation-actions-local-user") .build(); /** * Represents the remote user. * <p> * If possible, you are suggested to create a {@link Person} object that can identify * the remote user better, so that the underlying model could differentiate between * different remote users. * * @see Builder#Builder(Person) */ public static final Person PERSON_USER_REMOTE = new Person.Builder() .setKey("text-classifier-conversation-actions-remote-user") .build(); @Nullable private final Person mAuthor; @Nullable private final ZonedDateTime mComposeTime; private final ZonedDateTime mReferenceTime; @Nullable private final CharSequence mText; @NonNull Loading @@ -367,18 +381,18 @@ public final class ConversationActions implements Parcelable { private Message( @Nullable Person author, @Nullable ZonedDateTime composeTime, @Nullable ZonedDateTime referenceTime, @Nullable CharSequence text, @NonNull Bundle bundle) { mAuthor = author; mComposeTime = composeTime; mReferenceTime = referenceTime; mText = text; mExtras = Preconditions.checkNotNull(bundle); } private Message(Parcel in) { mAuthor = in.readParcelable(null); mComposeTime = mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse( Loading @@ -390,9 +404,9 @@ public final class ConversationActions implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mAuthor, flags); parcel.writeInt(mComposeTime != null ? 1 : 0); if (mComposeTime != null) { parcel.writeString(mComposeTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); parcel.writeInt(mReferenceTime != null ? 1 : 0); if (mReferenceTime != null) { parcel.writeString(mReferenceTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); } parcel.writeCharSequence(mText); parcel.writeBundle(mExtras); Loading @@ -417,15 +431,18 @@ public final class ConversationActions implements Parcelable { }; /** Returns the person that composed the message. */ @Nullable @NonNull public Person getAuthor() { return mAuthor; } /** Returns the compose time of the message. */ /** * Returns the reference time of the message, for example it could be the compose or send * time of this message. */ @Nullable public ZonedDateTime getTime() { return mComposeTime; public ZonedDateTime getReferenceTime() { return mReferenceTime; } /** Returns the text of the message. */ Loading @@ -451,34 +468,38 @@ public final class ConversationActions implements Parcelable { @Nullable private Person mAuthor; @Nullable private ZonedDateTime mComposeTime; private ZonedDateTime mReferenceTime; @Nullable private CharSequence mText; @Nullable private Bundle mExtras; /** * Sets the person who composed this message. * <p> * Use {@link #PERSON_USER_LOCAL} to represent the local user. * Constructs a builder. * * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL} * to represent the local user. If it is not possible to identify the * remote user that the local user is conversing with, use * {@link #PERSON_USER_REMOTE} to represent a remote user. */ @NonNull public Builder setAuthor(@Nullable Person author) { mAuthor = author; return this; public Builder(@NonNull Person author) { mAuthor = Preconditions.checkNotNull(author); } /** Sets the text of this message */ /** Sets the text of this message. */ @NonNull public Builder setText(@Nullable CharSequence text) { mText = text; return this; } /** Sets the compose time of this message */ /** * Sets the reference time of this message, for example it could be the compose or send * time of this message. */ @NonNull public Builder setComposeTime(@Nullable ZonedDateTime composeTime) { mComposeTime = composeTime; public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) { mReferenceTime = referenceTime; return this; } Loading @@ -494,7 +515,7 @@ public final class ConversationActions implements Parcelable { public Message build() { return new Message( mAuthor, mComposeTime, mReferenceTime, mText == null ? null : new SpannedString(mText), mExtras == null ? new Bundle() : mExtras.deepCopy()); } Loading core/java/android/view/textclassifier/TextClassifierImpl.java +22 −1 Original line number Diff line number Diff line Loading @@ -374,7 +374,8 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); } ActionsSuggestionsModel.ConversationMessage[] nativeMessages = ActionsSuggestionsHelper.toNativeMessages(request.getConversation()); ActionsSuggestionsHelper.toNativeMessages(request.getConversation(), this::detectLanguageTagsFromText); if (nativeMessages.length == 0) { return mFallback.suggestConversationActions(request); } Loading Loading @@ -407,6 +408,26 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); } @Nullable private String detectLanguageTagsFromText(CharSequence text) { TextLanguage.Request request = new TextLanguage.Request.Builder(text).build(); TextLanguage textLanguage = detectLanguage(request); int localeHypothesisCount = textLanguage.getLocaleHypothesisCount(); List<String> languageTags = new ArrayList<>(); // TODO: Reconsider this and probably make the score threshold configurable. for (int i = 0; i < localeHypothesisCount; i++) { ULocale locale = textLanguage.getLocale(i); if (textLanguage.getConfidenceScore(locale) < 0.5) { break; } languageTags.add(locale.toLanguageTag()); } if (languageTags.isEmpty()) { return LocaleList.getDefault().toLanguageTags(); } return String.join(",", languageTags); } private Collection<String> resolveActionTypesFromRequest(ConversationActions.Request request) { List<String> defaultActionTypes = request.getHints().contains(ConversationActions.HINT_FOR_NOTIFICATION) Loading core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +50 −62 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.view.textclassifier; import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL; import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE; import static com.google.common.truth.Truth.assertThat; import android.app.Person; Loading @@ -27,16 +30,26 @@ import com.google.android.textclassifier.ActionsSuggestionsModel; import org.junit.Test; import org.junit.runner.RunWith; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; import java.util.Locale; import java.util.function.Function; @SmallTest @RunWith(AndroidJUnit4.class) public class ActionsSuggestionsHelperTest { private static final String LOCALE_TAG = Locale.US.toLanguageTag(); private static final Function<CharSequence, String> LANGUAGE_DETECTOR = charSequence -> LOCALE_TAG; @Test public void testToNativeMessages_emptyInput() { ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages(Collections.emptyList()); ActionsSuggestionsHelper.toNativeMessages( Collections.emptyList(), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } Loading @@ -44,114 +57,89 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_noTextMessages() { ConversationActions.Message messageWithoutText = new ConversationActions.Message.Builder().build(); new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Collections.singletonList(messageWithoutText)); Collections.singletonList(messageWithoutText), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } @Test public void testToNativeMessages_missingPersonInFirstMessage() { ConversationActions.Message firstMessage = new ConversationActions.Message.Builder() .setText("first") .build(); ConversationActions.Message secondMessage = new ConversationActions.Message.Builder() .setText("second") .setAuthor(new Person.Builder().build()) .build(); ConversationActions.Message thirdMessage = new ConversationActions.Message.Builder() .setText("third") .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Arrays.asList(firstMessage, secondMessage, thirdMessage)); assertThat(conversationMessages).hasLength(2); assertNativeMessage(conversationMessages[0], secondMessage.getText(), 1); assertNativeMessage(conversationMessages[1], thirdMessage.getText(), 0); } public void testToNativeMessages_userIdEncoding() { Person userA = new Person.Builder().setName("userA").build(); Person userB = new Person.Builder().setName("userB").build(); @Test public void testToNativeMessages_missingPersonInMiddleOfConversation() { ConversationActions.Message firstMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(userB) .setText("first") .setAuthor(new Person.Builder().setName("first").build()) .build(); ConversationActions.Message secondMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(userA) .setText("second") .build(); ConversationActions.Message thirdMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(PERSON_USER_LOCAL) .setText("third") .setAuthor(new Person.Builder().setName("third").build()) .build(); ConversationActions.Message fourthMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(userA) .setText("fourth") .setAuthor(new Person.Builder().setName("fourth").build()) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage)); Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage), LANGUAGE_DETECTOR); assertThat(conversationMessages).hasLength(2); assertNativeMessage(conversationMessages[0], thirdMessage.getText(), 2); assertNativeMessage(conversationMessages[1], fourthMessage.getText(), 1); assertThat(conversationMessages).hasLength(4); assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0); assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0); assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0); } @Test public void testToNativeMessages_userIdEncoding() { Person userA = new Person.Builder().setName("userA").build(); Person userB = new Person.Builder().setName("userB").build(); public void testToNativeMessages_referenceTime() { ConversationActions.Message firstMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("first") .setAuthor(userB) .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) .build(); ConversationActions.Message secondMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("second") .setAuthor(userA) .build(); ConversationActions.Message thirdMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("third") .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL) .build(); ConversationActions.Message fourthMessage = new ConversationActions.Message.Builder() .setText("fourth") .setAuthor(userA) .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage)); Arrays.asList(firstMessage, secondMessage, thirdMessage), LANGUAGE_DETECTOR); assertThat(conversationMessages).hasLength(4); assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2); assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1); assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0); assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1); assertThat(conversationMessages).hasLength(3); assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000); assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000); } private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC")); } private static void assertNativeMessage( ActionsSuggestionsModel.ConversationMessage nativeMessage, CharSequence text, int userId) { int userId, long referenceTimeInMsUtc) { assertThat(nativeMessage.getText()).isEqualTo(text.toString()); assertThat(nativeMessage.getUserId()).isEqualTo(userId); assertThat(nativeMessage.getLocales()).isEqualTo(LOCALE_TAG); assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc); } } Loading
api/current.txt +4 −4 Original line number Diff line number Diff line Loading @@ -52563,19 +52563,19 @@ package android.view.textclassifier { method public int describeContents(); method public android.app.Person getAuthor(); method public android.os.Bundle getExtras(); method public java.time.ZonedDateTime getReferenceTime(); method public java.lang.CharSequence getText(); method public java.time.ZonedDateTime getTime(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR; field public static final android.app.Person PERSON_USER_LOCAL; field public static final android.app.Person PERSON_USER_REMOTE; } public static final class ConversationActions.Message.Builder { ctor public ConversationActions.Message.Builder(); ctor public ConversationActions.Message.Builder(android.app.Person); method public android.view.textclassifier.ConversationActions.Message build(); method public android.view.textclassifier.ConversationActions.Message.Builder setAuthor(android.app.Person); method public android.view.textclassifier.ConversationActions.Message.Builder setComposeTime(java.time.ZonedDateTime); method public android.view.textclassifier.ConversationActions.Message.Builder setExtras(android.os.Bundle); method public android.view.textclassifier.ConversationActions.Message.Builder setReferenceTime(java.time.ZonedDateTime); method public android.view.textclassifier.ConversationActions.Message.Builder setText(java.lang.CharSequence); }
core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +9 −22 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package android.view.textclassifier; import android.annotation.NonNull; import android.app.Person; import android.text.TextUtils; import android.util.ArrayMap; Loading @@ -30,6 +29,7 @@ import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** Loading Loading @@ -57,9 +57,9 @@ public final class ActionsSuggestionsHelper { * </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> messages, Function<CharSequence, String> languageDetector) { List<ConversationActions.Message> messagesWithText = messages.stream() .filter(message -> !TextUtils.isEmpty(message.getText())) Loading @@ -67,31 +67,18 @@ public final class ActionsSuggestionsHelper { 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(), 0, null)}; } // Encode the messages in the reverse order, stop whenever the Person object is missing. Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>(); PersonEncoder personEncoder = new PersonEncoder(); int size = messagesWithText.size(); for (int i = size - 1; i >= 0; i--) { ConversationActions.Message message = messagesWithText.get(i); if (message.getAuthor() == null) { break; } long referenceTime = message.getReferenceTime() == null ? 0 : message.getReferenceTime().toInstant().toEpochMilli(); nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage( personEncoder.encode(message.getAuthor()), message.getText().toString(), 0, null)); message.getText().toString(), referenceTime, languageDetector.apply(message.getText()))); } return nativeMessages.toArray( new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]); Loading
core/java/android/view/textclassifier/ConversationActions.java +46 −25 Original line number Diff line number Diff line Loading @@ -349,17 +349,31 @@ public final class ConversationActions implements Parcelable { /** * Represents the local user. * * @see Builder#setAuthor(Person) * @see Builder#Builder(Person) */ public static final Person PERSON_USER_LOCAL = new Person.Builder() .setKey("text-classifier-conversation-actions-local-user") .build(); /** * Represents the remote user. * <p> * If possible, you are suggested to create a {@link Person} object that can identify * the remote user better, so that the underlying model could differentiate between * different remote users. * * @see Builder#Builder(Person) */ public static final Person PERSON_USER_REMOTE = new Person.Builder() .setKey("text-classifier-conversation-actions-remote-user") .build(); @Nullable private final Person mAuthor; @Nullable private final ZonedDateTime mComposeTime; private final ZonedDateTime mReferenceTime; @Nullable private final CharSequence mText; @NonNull Loading @@ -367,18 +381,18 @@ public final class ConversationActions implements Parcelable { private Message( @Nullable Person author, @Nullable ZonedDateTime composeTime, @Nullable ZonedDateTime referenceTime, @Nullable CharSequence text, @NonNull Bundle bundle) { mAuthor = author; mComposeTime = composeTime; mReferenceTime = referenceTime; mText = text; mExtras = Preconditions.checkNotNull(bundle); } private Message(Parcel in) { mAuthor = in.readParcelable(null); mComposeTime = mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse( Loading @@ -390,9 +404,9 @@ public final class ConversationActions implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mAuthor, flags); parcel.writeInt(mComposeTime != null ? 1 : 0); if (mComposeTime != null) { parcel.writeString(mComposeTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); parcel.writeInt(mReferenceTime != null ? 1 : 0); if (mReferenceTime != null) { parcel.writeString(mReferenceTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); } parcel.writeCharSequence(mText); parcel.writeBundle(mExtras); Loading @@ -417,15 +431,18 @@ public final class ConversationActions implements Parcelable { }; /** Returns the person that composed the message. */ @Nullable @NonNull public Person getAuthor() { return mAuthor; } /** Returns the compose time of the message. */ /** * Returns the reference time of the message, for example it could be the compose or send * time of this message. */ @Nullable public ZonedDateTime getTime() { return mComposeTime; public ZonedDateTime getReferenceTime() { return mReferenceTime; } /** Returns the text of the message. */ Loading @@ -451,34 +468,38 @@ public final class ConversationActions implements Parcelable { @Nullable private Person mAuthor; @Nullable private ZonedDateTime mComposeTime; private ZonedDateTime mReferenceTime; @Nullable private CharSequence mText; @Nullable private Bundle mExtras; /** * Sets the person who composed this message. * <p> * Use {@link #PERSON_USER_LOCAL} to represent the local user. * Constructs a builder. * * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL} * to represent the local user. If it is not possible to identify the * remote user that the local user is conversing with, use * {@link #PERSON_USER_REMOTE} to represent a remote user. */ @NonNull public Builder setAuthor(@Nullable Person author) { mAuthor = author; return this; public Builder(@NonNull Person author) { mAuthor = Preconditions.checkNotNull(author); } /** Sets the text of this message */ /** Sets the text of this message. */ @NonNull public Builder setText(@Nullable CharSequence text) { mText = text; return this; } /** Sets the compose time of this message */ /** * Sets the reference time of this message, for example it could be the compose or send * time of this message. */ @NonNull public Builder setComposeTime(@Nullable ZonedDateTime composeTime) { mComposeTime = composeTime; public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) { mReferenceTime = referenceTime; return this; } Loading @@ -494,7 +515,7 @@ public final class ConversationActions implements Parcelable { public Message build() { return new Message( mAuthor, mComposeTime, mReferenceTime, mText == null ? null : new SpannedString(mText), mExtras == null ? new Bundle() : mExtras.deepCopy()); } Loading
core/java/android/view/textclassifier/TextClassifierImpl.java +22 −1 Original line number Diff line number Diff line Loading @@ -374,7 +374,8 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); } ActionsSuggestionsModel.ConversationMessage[] nativeMessages = ActionsSuggestionsHelper.toNativeMessages(request.getConversation()); ActionsSuggestionsHelper.toNativeMessages(request.getConversation(), this::detectLanguageTagsFromText); if (nativeMessages.length == 0) { return mFallback.suggestConversationActions(request); } Loading Loading @@ -407,6 +408,26 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); } @Nullable private String detectLanguageTagsFromText(CharSequence text) { TextLanguage.Request request = new TextLanguage.Request.Builder(text).build(); TextLanguage textLanguage = detectLanguage(request); int localeHypothesisCount = textLanguage.getLocaleHypothesisCount(); List<String> languageTags = new ArrayList<>(); // TODO: Reconsider this and probably make the score threshold configurable. for (int i = 0; i < localeHypothesisCount; i++) { ULocale locale = textLanguage.getLocale(i); if (textLanguage.getConfidenceScore(locale) < 0.5) { break; } languageTags.add(locale.toLanguageTag()); } if (languageTags.isEmpty()) { return LocaleList.getDefault().toLanguageTags(); } return String.join(",", languageTags); } private Collection<String> resolveActionTypesFromRequest(ConversationActions.Request request) { List<String> defaultActionTypes = request.getHints().contains(ConversationActions.HINT_FOR_NOTIFICATION) Loading
core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +50 −62 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.view.textclassifier; import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL; import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE; import static com.google.common.truth.Truth.assertThat; import android.app.Person; Loading @@ -27,16 +30,26 @@ import com.google.android.textclassifier.ActionsSuggestionsModel; import org.junit.Test; import org.junit.runner.RunWith; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; import java.util.Locale; import java.util.function.Function; @SmallTest @RunWith(AndroidJUnit4.class) public class ActionsSuggestionsHelperTest { private static final String LOCALE_TAG = Locale.US.toLanguageTag(); private static final Function<CharSequence, String> LANGUAGE_DETECTOR = charSequence -> LOCALE_TAG; @Test public void testToNativeMessages_emptyInput() { ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages(Collections.emptyList()); ActionsSuggestionsHelper.toNativeMessages( Collections.emptyList(), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } Loading @@ -44,114 +57,89 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_noTextMessages() { ConversationActions.Message messageWithoutText = new ConversationActions.Message.Builder().build(); new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Collections.singletonList(messageWithoutText)); Collections.singletonList(messageWithoutText), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } @Test public void testToNativeMessages_missingPersonInFirstMessage() { ConversationActions.Message firstMessage = new ConversationActions.Message.Builder() .setText("first") .build(); ConversationActions.Message secondMessage = new ConversationActions.Message.Builder() .setText("second") .setAuthor(new Person.Builder().build()) .build(); ConversationActions.Message thirdMessage = new ConversationActions.Message.Builder() .setText("third") .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Arrays.asList(firstMessage, secondMessage, thirdMessage)); assertThat(conversationMessages).hasLength(2); assertNativeMessage(conversationMessages[0], secondMessage.getText(), 1); assertNativeMessage(conversationMessages[1], thirdMessage.getText(), 0); } public void testToNativeMessages_userIdEncoding() { Person userA = new Person.Builder().setName("userA").build(); Person userB = new Person.Builder().setName("userB").build(); @Test public void testToNativeMessages_missingPersonInMiddleOfConversation() { ConversationActions.Message firstMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(userB) .setText("first") .setAuthor(new Person.Builder().setName("first").build()) .build(); ConversationActions.Message secondMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(userA) .setText("second") .build(); ConversationActions.Message thirdMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(PERSON_USER_LOCAL) .setText("third") .setAuthor(new Person.Builder().setName("third").build()) .build(); ConversationActions.Message fourthMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(userA) .setText("fourth") .setAuthor(new Person.Builder().setName("fourth").build()) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage)); Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage), LANGUAGE_DETECTOR); assertThat(conversationMessages).hasLength(2); assertNativeMessage(conversationMessages[0], thirdMessage.getText(), 2); assertNativeMessage(conversationMessages[1], fourthMessage.getText(), 1); assertThat(conversationMessages).hasLength(4); assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0); assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0); assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0); } @Test public void testToNativeMessages_userIdEncoding() { Person userA = new Person.Builder().setName("userA").build(); Person userB = new Person.Builder().setName("userB").build(); public void testToNativeMessages_referenceTime() { ConversationActions.Message firstMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("first") .setAuthor(userB) .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) .build(); ConversationActions.Message secondMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("second") .setAuthor(userA) .build(); ConversationActions.Message thirdMessage = new ConversationActions.Message.Builder() new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("third") .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL) .build(); ConversationActions.Message fourthMessage = new ConversationActions.Message.Builder() .setText("fourth") .setAuthor(userA) .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage)); Arrays.asList(firstMessage, secondMessage, thirdMessage), LANGUAGE_DETECTOR); assertThat(conversationMessages).hasLength(4); assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2); assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1); assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0); assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1); assertThat(conversationMessages).hasLength(3); assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000); assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000); } private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC")); } private static void assertNativeMessage( ActionsSuggestionsModel.ConversationMessage nativeMessage, CharSequence text, int userId) { int userId, long referenceTimeInMsUtc) { assertThat(nativeMessage.getText()).isEqualTo(text.toString()); assertThat(nativeMessage.getUserId()).isEqualTo(userId); assertThat(nativeMessage.getLocales()).isEqualTo(LOCALE_TAG); assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc); } }