Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a44e66a5 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topic "reference_time_tc"

* changes:
  Populate person and reference time, uses more than the last message in NAS
  Pass reference time / locales of messages to the model
parents f5918914 43a899fc
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -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);
  }
+9 −22
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package android.view.textclassifier;

import android.annotation.NonNull;
import android.app.Person;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -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;

/**
@@ -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()))
@@ -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()]);
+46 −25
Original line number Diff line number Diff line
@@ -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
@@ -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(
@@ -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);
@@ -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. */
@@ -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;
            }

@@ -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());
            }
+22 −1
Original line number Diff line number Diff line
@@ -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);
            }
@@ -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)
+50 −62
Original line number Diff line number Diff line
@@ -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;
@@ -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();
    }
@@ -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