blob: fdc34b3f68d04bee1873cbe6b91f80c353eae7e9 [file] [log] [blame]
Tony Makf99ee172018-11-23 12:14:39 +00001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view.textclassifier;
18
Tony Makf99ee172018-11-23 12:14:39 +000019import android.app.Person;
Tony Make94e0782018-12-14 11:57:54 +080020import android.content.Context;
Tony Makf99ee172018-11-23 12:14:39 +000021import android.text.TextUtils;
22import android.util.ArrayMap;
23
24import com.android.internal.annotations.VisibleForTesting;
25
26import com.google.android.textclassifier.ActionsSuggestionsModel;
27
28import java.util.ArrayDeque;
29import java.util.ArrayList;
30import java.util.Deque;
31import java.util.List;
Tony Make94e0782018-12-14 11:57:54 +080032import java.util.Locale;
Tony Makf99ee172018-11-23 12:14:39 +000033import java.util.Map;
Tony Make94e0782018-12-14 11:57:54 +080034import java.util.Objects;
35import java.util.StringJoiner;
Tony Mak82fa8d92018-12-07 17:37:43 +000036import java.util.function.Function;
Tony Makf99ee172018-11-23 12:14:39 +000037import java.util.stream.Collectors;
38
39/**
40 * Helper class for action suggestions.
41 *
42 * @hide
43 */
44@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
45public final class ActionsSuggestionsHelper {
46 private static final int USER_LOCAL = 0;
47 private static final int FIRST_NON_LOCAL_USER = 1;
48
49 private ActionsSuggestionsHelper() {}
50
51 /**
52 * Converts the messages to a list of native messages object that the model can understand.
53 * <p>
54 * User id encoding - local user is represented as 0, Other users are numbered according to
55 * how far before they spoke last time in the conversation. For example, considering this
56 * conversation:
57 * <ul>
58 * <li> User A: xxx
59 * <li> Local user: yyy
60 * <li> User B: zzz
61 * </ul>
62 * User A will be encoded as 2, user B will be encoded as 1 and local user will be encoded as 0.
63 */
Tony Makf99ee172018-11-23 12:14:39 +000064 public static ActionsSuggestionsModel.ConversationMessage[] toNativeMessages(
Tony Mak82fa8d92018-12-07 17:37:43 +000065 List<ConversationActions.Message> messages,
66 Function<CharSequence, String> languageDetector) {
Tony Makf99ee172018-11-23 12:14:39 +000067 List<ConversationActions.Message> messagesWithText =
68 messages.stream()
69 .filter(message -> !TextUtils.isEmpty(message.getText()))
70 .collect(Collectors.toCollection(ArrayList::new));
71 if (messagesWithText.isEmpty()) {
72 return new ActionsSuggestionsModel.ConversationMessage[0];
73 }
Tony Makf99ee172018-11-23 12:14:39 +000074 Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>();
75 PersonEncoder personEncoder = new PersonEncoder();
Tony Mak82fa8d92018-12-07 17:37:43 +000076 int size = messagesWithText.size();
Tony Makf99ee172018-11-23 12:14:39 +000077 for (int i = size - 1; i >= 0; i--) {
78 ConversationActions.Message message = messagesWithText.get(i);
Tony Mak82fa8d92018-12-07 17:37:43 +000079 long referenceTime = message.getReferenceTime() == null
80 ? 0
81 : message.getReferenceTime().toInstant().toEpochMilli();
Tony Makf99ee172018-11-23 12:14:39 +000082 nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage(
83 personEncoder.encode(message.getAuthor()),
Tony Mak82fa8d92018-12-07 17:37:43 +000084 message.getText().toString(), referenceTime,
85 languageDetector.apply(message.getText())));
Tony Makf99ee172018-11-23 12:14:39 +000086 }
87 return nativeMessages.toArray(
88 new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]);
89 }
90
Tony Make94e0782018-12-14 11:57:54 +080091 /**
92 * Returns the result id for logging.
93 */
94 public static String createResultId(
95 Context context,
96 List<ConversationActions.Message> messages,
97 int modelVersion,
98 List<Locale> modelLocales) {
99 final StringJoiner localesJoiner = new StringJoiner(",");
100 for (Locale locale : modelLocales) {
101 localesJoiner.add(locale.toLanguageTag());
102 }
103 final String modelName = String.format(
104 Locale.US, "%s_v%d", localesJoiner.toString(), modelVersion);
105 final int hash = Objects.hash(
Tony Mak03a1d032019-01-24 15:12:00 +0000106 messages.stream().mapToInt(ActionsSuggestionsHelper::hashMessage),
107 context.getPackageName(),
108 System.currentTimeMillis());
Tony Make94e0782018-12-14 11:57:54 +0800109 return SelectionSessionLogger.SignatureParser.createSignature(
110 SelectionSessionLogger.CLASSIFIER_ID, modelName, hash);
111 }
112
Tony Makf99ee172018-11-23 12:14:39 +0000113 private static final class PersonEncoder {
114 private final Map<Person, Integer> mMapping = new ArrayMap<>();
115 private int mNextUserId = FIRST_NON_LOCAL_USER;
116
117 private int encode(Person person) {
118 if (ConversationActions.Message.PERSON_USER_LOCAL.equals(person)) {
119 return USER_LOCAL;
120 }
121 Integer result = mMapping.get(person);
122 if (result == null) {
123 mMapping.put(person, mNextUserId);
124 result = mNextUserId;
125 mNextUserId++;
126 }
127 return result;
128 }
129 }
Tony Mak03a1d032019-01-24 15:12:00 +0000130
131 private static int hashMessage(ConversationActions.Message message) {
132 return Objects.hash(message.getAuthor(), message.getText(), message.getReferenceTime());
133 }
Tony Makf99ee172018-11-23 12:14:39 +0000134}