diff --git a/api/current.txt b/api/current.txt index 92526d4f8bc45..0564de3f89cbc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -52593,9 +52593,10 @@ package android.view.inspector { package android.view.textclassifier { public final class ConversationActions implements android.os.Parcelable { - ctor public ConversationActions(java.util.List); + ctor public ConversationActions(java.util.List, java.lang.String); method public int describeContents(); method public java.util.List getConversationActions(); + method public java.lang.String getId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final java.lang.String HINT_FOR_IN_APP = "in_app"; @@ -52656,6 +52657,7 @@ package android.view.textclassifier { method public int describeContents(); method public java.lang.String getCallingPackageName(); method public java.util.List getConversation(); + method public java.lang.String getConversationId(); method public java.util.List getHints(); method public int getMaxSuggestions(); method public android.view.textclassifier.ConversationActions.TypeConfig getTypeConfig(); @@ -52666,6 +52668,7 @@ package android.view.textclassifier { public static final class ConversationActions.Request.Builder { ctor public ConversationActions.Request.Builder(java.util.List); method public android.view.textclassifier.ConversationActions.Request build(); + method public android.view.textclassifier.ConversationActions.Request.Builder setConversationId(java.lang.String); method public android.view.textclassifier.ConversationActions.Request.Builder setHints(java.util.List); method public android.view.textclassifier.ConversationActions.Request.Builder setMaxSuggestions(int); method public android.view.textclassifier.ConversationActions.Request.Builder setTypeConfig(android.view.textclassifier.ConversationActions.TypeConfig); diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 04924c9a59c43..3f690f70d2751 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -139,17 +139,21 @@ public final class ConversationActions implements Parcelable { */ public static final String HINT_FOR_NOTIFICATION = "notification"; - private List mConversationActions; + private final List mConversationActions; + private final String mId; /** Constructs a {@link ConversationActions} object. */ - public ConversationActions(@NonNull List conversationActions) { + public ConversationActions( + @NonNull List conversationActions, @Nullable String id) { mConversationActions = Collections.unmodifiableList(Preconditions.checkNotNull(conversationActions)); + mId = id; } private ConversationActions(Parcel in) { mConversationActions = Collections.unmodifiableList(in.createTypedArrayList(ConversationAction.CREATOR)); + mId = in.readString(); } @Override @@ -160,14 +164,26 @@ public final class ConversationActions implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeTypedList(mConversationActions); + parcel.writeString(mId); } - /** Returns an immutable list of {@link ConversationAction} objects. */ + /** + * Returns an immutable list of {@link ConversationAction} objects, which are ordered from high + * confidence to low confidence. + */ @NonNull public List getConversationActions() { return mConversationActions; } + /** + * Returns the id, if one exists, for this object. + */ + @Nullable + public String getId() { + return mId; + } + /** Represents the action suggested by a {@link TextClassifier} on a given conversation. */ public static final class ConversationAction implements Parcelable { @@ -678,35 +694,37 @@ public final class ConversationActions implements Parcelable { private final List mHints; @Nullable private String mCallingPackageName; + @Nullable + private final String mConversationId; private Request( @NonNull List conversation, @NonNull TypeConfig typeConfig, int maxSuggestions, + String conversationId, @Nullable @Hint List hints) { mConversation = Preconditions.checkNotNull(conversation); mTypeConfig = Preconditions.checkNotNull(typeConfig); mMaxSuggestions = maxSuggestions; + mConversationId = conversationId; mHints = hints; } private static Request readFromParcel(Parcel in) { List conversation = new ArrayList<>(); in.readParcelableList(conversation, null); - TypeConfig typeConfig = in.readParcelable(null); - int maxSuggestions = in.readInt(); - + String conversationId = in.readString(); List hints = new ArrayList<>(); in.readStringList(hints); - String callingPackageName = in.readString(); Request request = new Request( conversation, typeConfig, maxSuggestions, + conversationId, hints); request.setCallingPackageName(callingPackageName); return request; @@ -717,6 +735,7 @@ public final class ConversationActions implements Parcelable { parcel.writeParcelableList(mConversation, flags); parcel.writeParcelable(mTypeConfig, flags); parcel.writeInt(mMaxSuggestions); + parcel.writeString(mConversationId); parcel.writeStringList(mHints); parcel.writeString(mCallingPackageName); } @@ -759,6 +778,16 @@ public final class ConversationActions implements Parcelable { return mMaxSuggestions; } + /** + * Return an unique identifier of the conversation that is generating actions for. This + * identifier is unique within the calling package only, so use it with + * {@link #getCallingPackageName()}. + */ + @Nullable + public String getConversationId() { + return mConversationId; + } + /** Returns an immutable list of hints */ @Nullable @Hint @@ -794,6 +823,8 @@ public final class ConversationActions implements Parcelable { private TypeConfig mTypeConfig; private int mMaxSuggestions; @Nullable + private String mConversationId; + @Nullable @Hint private List mHints; @@ -823,7 +854,8 @@ public final class ConversationActions implements Parcelable { return this; } - /** Sets the maximum number of suggestions you want. + /** + * Sets the maximum number of suggestions you want. *

* Value 0 means no restriction. */ @@ -833,6 +865,15 @@ public final class ConversationActions implements Parcelable { return this; } + /** + * Sets an unique identifier of the conversation that is generating actions for. + */ + @NonNull + public Builder setConversationId(@Nullable String conversationId) { + mConversationId = conversationId; + return this; + } + /** Builds the {@link Request} object. */ @NonNull public Request build() { @@ -840,6 +881,7 @@ public final class ConversationActions implements Parcelable { Collections.unmodifiableList(mConversation), mTypeConfig == null ? new TypeConfig.Builder().build() : mTypeConfig, mMaxSuggestions, + mConversationId, mHints == null ? Collections.emptyList() : Collections.unmodifiableList(mHints)); diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index ea82bf3f38467..e894ab44b88cd 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -349,7 +349,7 @@ public interface TextClassifier { @NonNull ConversationActions.Request request) { Preconditions.checkNotNull(request); Utils.checkMainThread(); - return new ConversationActions(Collections.emptyList()); + return new ConversationActions(Collections.emptyList(), null); } /** diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index deda92646e712..df38e2ee79118 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -387,7 +387,10 @@ public final class TextClassifierImpl implements TextClassifier { Collection expectedTypes = resolveActionTypesFromRequest(request); List conversationActions = new ArrayList<>(); - int maxSuggestions = Math.min(request.getMaxSuggestions(), nativeSuggestions.length); + int maxSuggestions = nativeSuggestions.length; + if (request.getMaxSuggestions() > 0) { + maxSuggestions = Math.min(request.getMaxSuggestions(), nativeSuggestions.length); + } for (int i = 0; i < maxSuggestions; i++) { ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = nativeSuggestions[i]; String actionType = nativeSuggestion.getActionType(); @@ -400,7 +403,7 @@ public final class TextClassifierImpl implements TextClassifier { .setConfidenceScore(nativeSuggestion.getScore()) .build()); } - return new ConversationActions(conversationActions); + return new ConversationActions(conversationActions, /*id*/ null); } catch (Throwable t) { // Avoid throwing from this method. Log the error. Log.e(LOG_TAG, "Error suggesting conversation actions.", t); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 9b5c0347bdb69..81ec85eb2ea55 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -18,7 +18,6 @@ package android.view.textclassifier; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -375,7 +374,7 @@ public class TextClassifierTest { ConversationActions.Message message = new ConversationActions.Message.Builder( ConversationActions.Message.PERSON_USER_REMOTE) - .setText("Hello") + .setText("Where are you?") .build(); ConversationActions.TypeConfig typeConfig = new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false) @@ -384,19 +383,44 @@ public class TextClassifierTest { .build(); ConversationActions.Request request = new ConversationActions.Request.Builder(Collections.singletonList(message)) - .setMaxSuggestions(3) + .setMaxSuggestions(1) .setTypeConfig(typeConfig) .build(); ConversationActions conversationActions = mClassifier.suggestConversationActions(request); assertTrue(conversationActions.getConversationActions().size() > 0); - assertTrue(conversationActions.getConversationActions().size() <= 3); + assertTrue(conversationActions.getConversationActions().size() == 1); for (ConversationActions.ConversationAction conversationAction : conversationActions.getConversationActions()) { - assertEquals(conversationAction.getType(), ConversationActions.TYPE_TEXT_REPLY); - assertNotNull(conversationAction.getTextReply()); - assertTrue(conversationAction.getConfidenceScore() > 0); - assertTrue(conversationAction.getConfidenceScore() <= 1); + assertThat(conversationAction, + isConversationAction(ConversationActions.TYPE_TEXT_REPLY)); + } + } + + @Test + public void testSuggestConversationActions_textReplyOnly_noMax() { + if (isTextClassifierDisabled()) return; + ConversationActions.Message message = + new ConversationActions.Message.Builder( + ConversationActions.Message.PERSON_USER_REMOTE) + .setText("Where are you?") + .build(); + ConversationActions.TypeConfig typeConfig = + new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false) + .setIncludedTypes( + Collections.singletonList(ConversationActions.TYPE_TEXT_REPLY)) + .build(); + ConversationActions.Request request = + new ConversationActions.Request.Builder(Collections.singletonList(message)) + .setTypeConfig(typeConfig) + .build(); + + ConversationActions conversationActions = mClassifier.suggestConversationActions(request); + assertTrue(conversationActions.getConversationActions().size() > 1); + for (ConversationActions.ConversationAction conversationAction : + conversationActions.getConversationActions()) { + assertThat(conversationAction, + isConversationAction(ConversationActions.TYPE_TEXT_REPLY)); } } @@ -498,4 +522,36 @@ public class TextClassifierTest { } }; } + + private static Matcher isConversationAction( + String actionType) { + return new BaseMatcher() { + @Override + public boolean matches(Object o) { + if (!(o instanceof ConversationActions.ConversationAction)) { + return false; + } + ConversationActions.ConversationAction conversationAction = + (ConversationActions.ConversationAction) o; + if (!actionType.equals(conversationAction.getType())) { + return false; + } + if (ConversationActions.TYPE_TEXT_REPLY.equals(actionType)) { + if (conversationAction.getTextReply() == null) { + return false; + } + } + if (conversationAction.getConfidenceScore() < 0 + || conversationAction.getConfidenceScore() > 1) { + return false; + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("actionType=").appendValue(actionType); + } + }; + } } diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java index 60d31fca8ddbb..0352ebcec8b3f 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java @@ -75,7 +75,7 @@ public class SmartActionHelperTest { mContext.getSystemService(TextClassificationManager.class) .setTextClassifier(mTextClassifier); when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class))) - .thenReturn(new ConversationActions(Collections.emptyList())); + .thenReturn(new ConversationActions(Collections.emptyList(), null)); when(mNotificationEntry.getSbn()).thenReturn(mStatusBarNotification); // The notification is eligible to have smart suggestions.