blob: efdc968909ee3f9c15957dc51c5332b13523a998 [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 Makac9b4d82019-02-15 13:57:38 +000019import android.annotation.Nullable;
Tony Makf99ee172018-11-23 12:14:39 +000020import android.app.Person;
Tony Make94e0782018-12-14 11:57:54 +080021import android.content.Context;
Tony Makf99ee172018-11-23 12:14:39 +000022import android.text.TextUtils;
23import android.util.ArrayMap;
24
25import com.android.internal.annotations.VisibleForTesting;
26
27import com.google.android.textclassifier.ActionsSuggestionsModel;
28
29import java.util.ArrayDeque;
30import java.util.ArrayList;
31import java.util.Deque;
32import java.util.List;
Tony Make94e0782018-12-14 11:57:54 +080033import java.util.Locale;
Tony Makf99ee172018-11-23 12:14:39 +000034import java.util.Map;
Tony Make94e0782018-12-14 11:57:54 +080035import java.util.Objects;
36import java.util.StringJoiner;
Tony Mak82fa8d92018-12-07 17:37:43 +000037import java.util.function.Function;
Tony Makf99ee172018-11-23 12:14:39 +000038import java.util.stream.Collectors;
39
40/**
41 * Helper class for action suggestions.
42 *
43 * @hide
44 */
45@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
46public final class ActionsSuggestionsHelper {
47 private static final int USER_LOCAL = 0;
48 private static final int FIRST_NON_LOCAL_USER = 1;
49
50 private ActionsSuggestionsHelper() {}
51
52 /**
53 * Converts the messages to a list of native messages object that the model can understand.
54 * <p>
55 * User id encoding - local user is represented as 0, Other users are numbered according to
56 * how far before they spoke last time in the conversation. For example, considering this
57 * conversation:
58 * <ul>
59 * <li> User A: xxx
60 * <li> Local user: yyy
61 * <li> User B: zzz
62 * </ul>
63 * User A will be encoded as 2, user B will be encoded as 1 and local user will be encoded as 0.
64 */
Tony Makf99ee172018-11-23 12:14:39 +000065 public static ActionsSuggestionsModel.ConversationMessage[] toNativeMessages(
Tony Mak82fa8d92018-12-07 17:37:43 +000066 List<ConversationActions.Message> messages,
67 Function<CharSequence, String> languageDetector) {
Tony Makf99ee172018-11-23 12:14:39 +000068 List<ConversationActions.Message> messagesWithText =
69 messages.stream()
70 .filter(message -> !TextUtils.isEmpty(message.getText()))
71 .collect(Collectors.toCollection(ArrayList::new));
72 if (messagesWithText.isEmpty()) {
73 return new ActionsSuggestionsModel.ConversationMessage[0];
74 }
Tony Makf99ee172018-11-23 12:14:39 +000075 Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>();
76 PersonEncoder personEncoder = new PersonEncoder();
Tony Mak82fa8d92018-12-07 17:37:43 +000077 int size = messagesWithText.size();
Tony Makf99ee172018-11-23 12:14:39 +000078 for (int i = size - 1; i >= 0; i--) {
79 ConversationActions.Message message = messagesWithText.get(i);
Tony Mak82fa8d92018-12-07 17:37:43 +000080 long referenceTime = message.getReferenceTime() == null
81 ? 0
82 : message.getReferenceTime().toInstant().toEpochMilli();
Tony Makf99ee172018-11-23 12:14:39 +000083 nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage(
84 personEncoder.encode(message.getAuthor()),
Tony Mak82fa8d92018-12-07 17:37:43 +000085 message.getText().toString(), referenceTime,
86 languageDetector.apply(message.getText())));
Tony Makf99ee172018-11-23 12:14:39 +000087 }
88 return nativeMessages.toArray(
89 new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]);
90 }
91
Tony Make94e0782018-12-14 11:57:54 +080092 /**
93 * Returns the result id for logging.
94 */
95 public static String createResultId(
96 Context context,
97 List<ConversationActions.Message> messages,
98 int modelVersion,
99 List<Locale> modelLocales) {
100 final StringJoiner localesJoiner = new StringJoiner(",");
101 for (Locale locale : modelLocales) {
102 localesJoiner.add(locale.toLanguageTag());
103 }
104 final String modelName = String.format(
105 Locale.US, "%s_v%d", localesJoiner.toString(), modelVersion);
106 final int hash = Objects.hash(
Tony Mak03a1d032019-01-24 15:12:00 +0000107 messages.stream().mapToInt(ActionsSuggestionsHelper::hashMessage),
108 context.getPackageName(),
109 System.currentTimeMillis());
Tony Make94e0782018-12-14 11:57:54 +0800110 return SelectionSessionLogger.SignatureParser.createSignature(
111 SelectionSessionLogger.CLASSIFIER_ID, modelName, hash);
112 }
113
Tony Makac9b4d82019-02-15 13:57:38 +0000114 /**
115 * Returns a {@link android.view.textclassifier.LabeledIntent.TitleChooser} for
116 * conversation actions use case.
117 */
118 @Nullable
119 public static LabeledIntent.TitleChooser createTitleChooser(String actionType) {
120 if (ConversationAction.TYPE_OPEN_URL.equals(actionType)) {
121 return (labeledIntent, resolveInfo) -> resolveInfo.handleAllWebDataURI
122 ? labeledIntent.titleWithEntity : labeledIntent.titleWithoutEntity;
123 }
124 return null;
125 }
126
Tony Makf99ee172018-11-23 12:14:39 +0000127 private static final class PersonEncoder {
128 private final Map<Person, Integer> mMapping = new ArrayMap<>();
129 private int mNextUserId = FIRST_NON_LOCAL_USER;
130
131 private int encode(Person person) {
Tony Mak91daa152019-01-24 16:00:28 +0000132 if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) {
Tony Makf99ee172018-11-23 12:14:39 +0000133 return USER_LOCAL;
134 }
135 Integer result = mMapping.get(person);
136 if (result == null) {
137 mMapping.put(person, mNextUserId);
138 result = mNextUserId;
139 mNextUserId++;
140 }
141 return result;
142 }
143 }
Tony Mak03a1d032019-01-24 15:12:00 +0000144
145 private static int hashMessage(ConversationActions.Message message) {
146 return Objects.hash(message.getAuthor(), message.getText(), message.getReferenceTime());
147 }
Tony Makf99ee172018-11-23 12:14:39 +0000148}