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:
Tony Mak
2018-11-23 12:14:39 +00:00
parent c61363e7a2
commit f99ee17fc0
7 changed files with 303 additions and 24 deletions

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}