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
This commit is contained in:
@@ -51896,6 +51896,7 @@ package android.view.textclassifier {
|
||||
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;
|
||||
}
|
||||
|
||||
public static final class ConversationActions.Message.Builder {
|
||||
|
||||
@@ -441,10 +441,10 @@ package android.app {
|
||||
}
|
||||
|
||||
public class KeyguardManager {
|
||||
method public void setPrivateNotificationsAllowed(boolean);
|
||||
method public boolean getPrivateNotificationsAllowed();
|
||||
method public android.content.Intent createConfirmFactoryResetCredentialIntent(java.lang.CharSequence, java.lang.CharSequence, java.lang.CharSequence);
|
||||
method public boolean getPrivateNotificationsAllowed();
|
||||
method public void requestDismissKeyguard(android.app.Activity, java.lang.CharSequence, android.app.KeyguardManager.KeyguardDismissCallback);
|
||||
method public void setPrivateNotificationsAllowed(boolean);
|
||||
}
|
||||
|
||||
public class Notification implements android.os.Parcelable {
|
||||
|
||||
@@ -127,8 +127,8 @@ public final class Person implements Parcelable {
|
||||
if (obj instanceof Person) {
|
||||
final Person other = (Person) obj;
|
||||
return Objects.equals(mName, other.mName)
|
||||
&& mIcon == null ? other.mIcon == null :
|
||||
(other.mIcon != null && mIcon.sameAs(other.mIcon))
|
||||
&& (mIcon == null ? other.mIcon == null :
|
||||
(other.mIcon != null && mIcon.sameAs(other.mIcon)))
|
||||
&& Objects.equals(mUri, other.mUri)
|
||||
&& Objects.equals(mKey, other.mKey)
|
||||
&& mIsBot == other.mIsBot
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,6 +345,16 @@ public final class ConversationActions implements Parcelable {
|
||||
|
||||
/** 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 @@ public final class ConversationActions implements Parcelable {
|
||||
@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;
|
||||
|
||||
@@ -40,7 +40,6 @@ import android.os.UserManager;
|
||||
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 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
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 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
// 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);
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.Person;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.google.android.textclassifier.ActionsSuggestionsModel;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ActionsSuggestionsHelperTest {
|
||||
@Test
|
||||
public void testToNativeMessages_emptyInput() {
|
||||
ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
|
||||
ActionsSuggestionsHelper.toNativeMessages(Collections.emptyList());
|
||||
|
||||
assertThat(conversationMessages).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToNativeMessages_noTextMessages() {
|
||||
ConversationActions.Message messageWithoutText =
|
||||
new ConversationActions.Message.Builder().build();
|
||||
|
||||
ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
|
||||
ActionsSuggestionsHelper.toNativeMessages(
|
||||
Collections.singletonList(messageWithoutText));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToNativeMessages_missingPersonInMiddleOfConversation() {
|
||||
ConversationActions.Message firstMessage =
|
||||
new ConversationActions.Message.Builder()
|
||||
.setText("first")
|
||||
.setAuthor(new Person.Builder().setName("first").build())
|
||||
.build();
|
||||
ConversationActions.Message secondMessage =
|
||||
new ConversationActions.Message.Builder()
|
||||
.setText("second")
|
||||
.build();
|
||||
ConversationActions.Message thirdMessage =
|
||||
new ConversationActions.Message.Builder()
|
||||
.setText("third")
|
||||
.setAuthor(new Person.Builder().setName("third").build())
|
||||
.build();
|
||||
ConversationActions.Message fourthMessage =
|
||||
new ConversationActions.Message.Builder()
|
||||
.setText("fourth")
|
||||
.setAuthor(new Person.Builder().setName("fourth").build())
|
||||
.build();
|
||||
|
||||
ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
|
||||
ActionsSuggestionsHelper.toNativeMessages(
|
||||
Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage));
|
||||
|
||||
assertThat(conversationMessages).hasLength(2);
|
||||
assertNativeMessage(conversationMessages[0], thirdMessage.getText(), 2);
|
||||
assertNativeMessage(conversationMessages[1], fourthMessage.getText(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToNativeMessages_userIdEncoding() {
|
||||
Person userA = new Person.Builder().setName("userA").build();
|
||||
Person userB = new Person.Builder().setName("userB").build();
|
||||
|
||||
ConversationActions.Message firstMessage =
|
||||
new ConversationActions.Message.Builder()
|
||||
.setText("first")
|
||||
.setAuthor(userB)
|
||||
.build();
|
||||
ConversationActions.Message secondMessage =
|
||||
new ConversationActions.Message.Builder()
|
||||
.setText("second")
|
||||
.setAuthor(userA)
|
||||
.build();
|
||||
ConversationActions.Message thirdMessage =
|
||||
new ConversationActions.Message.Builder()
|
||||
.setText("third")
|
||||
.setAuthor(ConversationActions.Message.PERSON_USER_LOCAL)
|
||||
.build();
|
||||
ConversationActions.Message fourthMessage =
|
||||
new ConversationActions.Message.Builder()
|
||||
.setText("fourth")
|
||||
.setAuthor(userA)
|
||||
.build();
|
||||
|
||||
ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
|
||||
ActionsSuggestionsHelper.toNativeMessages(
|
||||
Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static void assertNativeMessage(
|
||||
ActionsSuggestionsModel.ConversationMessage nativeMessage,
|
||||
CharSequence text,
|
||||
int userId) {
|
||||
assertThat(nativeMessage.getText()).isEqualTo(text.toString());
|
||||
assertThat(nativeMessage.getUserId()).isEqualTo(userId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user