summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt4
-rw-r--r--api/system-current.txt2
-rw-r--r--core/java/android/provider/DeviceConfig.java4
-rw-r--r--core/java/android/view/textclassifier/ConversationActions.java14
-rw-r--r--core/java/android/view/textclassifier/IntentFactory.java3
-rw-r--r--core/java/android/view/textclassifier/TemplateClassificationIntentFactory.java78
-rw-r--r--core/java/android/view/textclassifier/TemplateIntentFactory.java47
-rw-r--r--core/java/android/view/textclassifier/TextClassifierImpl.java101
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/TemplateClassificationIntentFactoryTest.java150
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/TemplateIntentFactoryTest.java81
-rw-r--r--packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java71
-rw-r--r--packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java179
-rw-r--r--packages/ExtServices/tests/Android.bp1
-rw-r--r--packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java150
-rw-r--r--packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java (renamed from packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java)235
15 files changed, 702 insertions, 418 deletions
diff --git a/api/current.txt b/api/current.txt
index 131237a77277..44e9e5e016a6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -53565,7 +53565,7 @@ package android.view.textclassifier {
method @NonNull public java.util.List<android.view.textclassifier.ConversationActions.Message> getConversation();
method @Nullable public String getConversationId();
method @Nullable public java.util.List<java.lang.String> getHints();
- method @IntRange(from=0) public int getMaxSuggestions();
+ method @IntRange(from=0xffffffff) public int getMaxSuggestions();
method @NonNull public android.view.textclassifier.TextClassifier.EntityConfig getTypeConfig();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Request> CREATOR;
@@ -53578,7 +53578,7 @@ package android.view.textclassifier {
method @NonNull public android.view.textclassifier.ConversationActions.Request build();
method @NonNull public android.view.textclassifier.ConversationActions.Request.Builder setConversationId(@Nullable String);
method public android.view.textclassifier.ConversationActions.Request.Builder setHints(@Nullable java.util.List<java.lang.String>);
- method @NonNull public android.view.textclassifier.ConversationActions.Request.Builder setMaxSuggestions(@IntRange(from=0) int);
+ method @NonNull public android.view.textclassifier.ConversationActions.Request.Builder setMaxSuggestions(@IntRange(from=0xffffffff) int);
method @NonNull public android.view.textclassifier.ConversationActions.Request.Builder setTypeConfig(@Nullable android.view.textclassifier.TextClassifier.EntityConfig);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index c3088c0cdeb7..139b198b31b2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5752,6 +5752,8 @@ package android.provider {
public static interface DeviceConfig.NotificationAssistant {
field public static final String GENERATE_ACTIONS = "generate_actions";
field public static final String GENERATE_REPLIES = "generate_replies";
+ field public static final String MAX_MESSAGES_TO_EXTRACT = "max_messages_to_extract";
+ field public static final String MAX_SUGGESTIONS = "max_suggestions";
field public static final String NAMESPACE = "notification_assistant";
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 1f587232ca05..daaa632a5cca 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -147,6 +147,10 @@ public final class DeviceConfig {
* Whether the Notification Assistant should generate contextual actions for notifications.
*/
String GENERATE_ACTIONS = "generate_actions";
+
+ String MAX_MESSAGES_TO_EXTRACT = "max_messages_to_extract";
+
+ String MAX_SUGGESTIONS = "max_suggestions";
}
/**
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 502181f633b6..cf3144558b76 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -393,9 +393,10 @@ public final class ConversationActions implements Parcelable {
}
/**
- * Return the maximal number of suggestions the caller wants, value 0 means no restriction.
+ * Return the maximal number of suggestions the caller wants, value -1 means no restriction
+ * and this is the default.
*/
- @IntRange(from = 0)
+ @IntRange(from = -1)
public int getMaxSuggestions() {
return mMaxSuggestions;
}
@@ -443,7 +444,7 @@ public final class ConversationActions implements Parcelable {
private List<Message> mConversation;
@Nullable
private TextClassifier.EntityConfig mTypeConfig;
- private int mMaxSuggestions;
+ private int mMaxSuggestions = -1;
@Nullable
private String mConversationId;
@Nullable
@@ -477,12 +478,11 @@ public final class ConversationActions implements Parcelable {
}
/**
- * Sets the maximum number of suggestions you want.
- * <p>
- * Value 0 means no restriction.
+ * Sets the maximum number of suggestions you want. Value -1 means no restriction and
+ * this is the default.
*/
@NonNull
- public Builder setMaxSuggestions(@IntRange(from = 0) int maxSuggestions) {
+ public Builder setMaxSuggestions(@IntRange(from = -1) int maxSuggestions) {
mMaxSuggestions = Preconditions.checkArgumentNonnegative(maxSuggestions);
return this;
}
diff --git a/core/java/android/view/textclassifier/IntentFactory.java b/core/java/android/view/textclassifier/IntentFactory.java
index d9c03c858f7a..9f3b97f8339f 100644
--- a/core/java/android/view/textclassifier/IntentFactory.java
+++ b/core/java/android/view/textclassifier/IntentFactory.java
@@ -50,7 +50,8 @@ public interface IntentFactory {
new Intent(Intent.ACTION_TRANSLATE)
// TODO: Probably better to introduce a "translate" scheme instead of
// using EXTRA_TEXT.
- .putExtra(Intent.EXTRA_TEXT, text),
+ .putExtra(Intent.EXTRA_TEXT, text)
+ .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true),
text.hashCode()));
}
}
diff --git a/core/java/android/view/textclassifier/TemplateClassificationIntentFactory.java b/core/java/android/view/textclassifier/TemplateClassificationIntentFactory.java
new file mode 100644
index 000000000000..2467802ec5a8
--- /dev/null
+++ b/core/java/android/view/textclassifier/TemplateClassificationIntentFactory.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 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.annotation.Nullable;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import com.google.android.textclassifier.AnnotatorModel;
+import com.google.android.textclassifier.RemoteActionTemplate;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Creates intents based on {@link RemoteActionTemplate} objects for a ClassificationResult.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class TemplateClassificationIntentFactory implements IntentFactory {
+ private static final String TAG = TextClassifier.DEFAULT_LOG_TAG;
+ private final TemplateIntentFactory mTemplateIntentFactory;
+ private final IntentFactory mFallback;
+
+ public TemplateClassificationIntentFactory(TemplateIntentFactory templateIntentFactory,
+ IntentFactory fallback) {
+ mTemplateIntentFactory = Preconditions.checkNotNull(templateIntentFactory);
+ mFallback = Preconditions.checkNotNull(fallback);
+ }
+
+ /**
+ * Returns a list of {@link android.view.textclassifier.TextClassifierImpl.LabeledIntent}
+ * that are constructed from the classification result.
+ */
+ @NonNull
+ @Override
+ public List<TextClassifierImpl.LabeledIntent> create(
+ Context context,
+ String text,
+ boolean foreignText,
+ @Nullable Instant referenceTime,
+ @Nullable AnnotatorModel.ClassificationResult classification) {
+ if (classification == null) {
+ return Collections.emptyList();
+ }
+ RemoteActionTemplate[] remoteActionTemplates = classification.getRemoteActionTemplates();
+ if (ArrayUtils.isEmpty(remoteActionTemplates)) {
+ // RemoteActionTemplate is missing, fallback.
+ Log.w(TAG, "RemoteActionTemplate is missing, fallback to LegacyIntentFactory.");
+ return mFallback.create(context, text, foreignText, referenceTime, classification);
+ }
+ final List<TextClassifierImpl.LabeledIntent> labeledIntents =
+ mTemplateIntentFactory.create(remoteActionTemplates);
+ if (foreignText) {
+ IntentFactory.insertTranslateAction(labeledIntents, context, text.trim());
+ }
+ return labeledIntents;
+ }
+}
diff --git a/core/java/android/view/textclassifier/TemplateIntentFactory.java b/core/java/android/view/textclassifier/TemplateIntentFactory.java
index 97e11bb702be..52788436b3ea 100644
--- a/core/java/android/view/textclassifier/TemplateIntentFactory.java
+++ b/core/java/android/view/textclassifier/TemplateIntentFactory.java
@@ -17,7 +17,6 @@ package android.view.textclassifier;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -25,64 +24,29 @@ import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
-import com.google.android.textclassifier.AnnotatorModel;
import com.google.android.textclassifier.NamedVariant;
import com.google.android.textclassifier.RemoteActionTemplate;
-import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Creates intents based on {@link RemoteActionTemplate} objects.
+ *
* @hide
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public final class TemplateIntentFactory implements IntentFactory {
+public final class TemplateIntentFactory {
private static final String TAG = TextClassifier.DEFAULT_LOG_TAG;
- private final IntentFactory mFallback;
-
- public TemplateIntentFactory(IntentFactory fallback) {
- mFallback = Preconditions.checkNotNull(fallback);
- }
- /**
- * Returns a list of {@link android.view.textclassifier.TextClassifierImpl.LabeledIntent}
- * that are constructed from the classification result.
- */
@NonNull
- @Override
public List<TextClassifierImpl.LabeledIntent> create(
- Context context,
- String text,
- boolean foreignText,
- @Nullable Instant referenceTime,
- @Nullable AnnotatorModel.ClassificationResult classification) {
- if (classification == null) {
- return Collections.emptyList();
- }
- RemoteActionTemplate[] remoteActionTemplates = classification.getRemoteActionTemplates();
+ @Nullable RemoteActionTemplate[] remoteActionTemplates) {
if (ArrayUtils.isEmpty(remoteActionTemplates)) {
- // RemoteActionTemplate is missing, fallback.
- Log.w(TAG, "RemoteActionTemplate is missing, fallback to LegacyIntentFactory.");
- return mFallback.create(context, text, foreignText, referenceTime, classification);
- }
- final List<TextClassifierImpl.LabeledIntent> labeledIntents =
- new ArrayList<>(createFromRemoteActionTemplates(remoteActionTemplates));
- if (foreignText) {
- IntentFactory.insertTranslateAction(labeledIntents, context, text.trim());
+ return Collections.emptyList();
}
- labeledIntents.forEach(
- action -> action.getIntent()
- .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true));
- return labeledIntents;
- }
-
- private static List<TextClassifierImpl.LabeledIntent> createFromRemoteActionTemplates(
- RemoteActionTemplate[] remoteActionTemplates) {
final List<TextClassifierImpl.LabeledIntent> labeledIntents = new ArrayList<>();
for (RemoteActionTemplate remoteActionTemplate : remoteActionTemplates) {
Intent intent = createIntent(remoteActionTemplate);
@@ -100,6 +64,9 @@ public final class TemplateIntentFactory implements IntentFactory {
);
labeledIntents.add(labeledIntent);
}
+ labeledIntents.forEach(
+ action -> action.getIntent()
+ .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true));
return labeledIntents;
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 14afa339f192..295c8b72b943 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -113,6 +113,7 @@ public final class TextClassifierImpl implements TextClassifier {
private final ModelFileManager mActionsModelFileManager;
private final IntentFactory mIntentFactory;
+ private final TemplateIntentFactory mTemplateIntentFactory;
public TextClassifierImpl(
Context context, TextClassificationConstants settings, TextClassifier fallback) {
@@ -142,8 +143,10 @@ public final class TextClassifierImpl implements TextClassifier {
ActionsSuggestionsModel::getVersion,
ActionsSuggestionsModel::getLocales));
+ mTemplateIntentFactory = new TemplateIntentFactory();
mIntentFactory = mSettings.isTemplateIntentFactoryEnabled()
- ? new TemplateIntentFactory(new LegacyIntentFactory())
+ ? new TemplateClassificationIntentFactory(
+ mTemplateIntentFactory, new LegacyIntentFactory())
: new LegacyIntentFactory();
}
@@ -189,7 +192,10 @@ public final class TextClassifierImpl implements TextClassifier {
refTime.toInstant().toEpochMilli(),
refTime.getZone().getId(),
localesString),
- mContext);
+ // Passing null here to suppress intent generation
+ // TODO: Use an explicit flag to suppress it.
+ /* appContext */ null,
+ /* deviceLocales */null);
final int size = results.length;
for (int i = 0; i < size; i++) {
tsBuilder.setEntityType(results[i].getCollection(), results[i].getScore());
@@ -233,7 +239,11 @@ public final class TextClassifierImpl implements TextClassifier {
refTime.toInstant().toEpochMilli(),
refTime.getZone().getId(),
localesString),
- mContext);
+ mContext,
+ // TODO: Pass the locale list once it is supported in
+ // native side.
+ LocaleList.getDefault().get(0).toLanguageTag()
+ );
if (results.length > 0) {
return createClassificationResult(
results, string,
@@ -389,32 +399,13 @@ public final class TextClassifierImpl implements TextClassifier {
new ActionsSuggestionsModel.Conversation(nativeMessages);
ActionsSuggestionsModel.ActionSuggestion[] nativeSuggestions =
- actionsImpl.suggestActions(nativeConversation, null);
-
- Collection<String> expectedTypes = resolveActionTypesFromRequest(request);
- List<ConversationAction> conversationActions = new ArrayList<>();
- 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();
- if (!expectedTypes.contains(actionType)) {
- continue;
- }
- conversationActions.add(
- new ConversationAction.Builder(actionType)
- .setTextReply(nativeSuggestion.getResponseText())
- .setConfidenceScore(nativeSuggestion.getScore())
- .build());
- }
- String resultId = ActionsSuggestionsHelper.createResultId(
- mContext,
- request.getConversation(),
- mActionModelInUse.getVersion(),
- mActionModelInUse.getSupportedLocales());
- return new ConversationActions(conversationActions, resultId);
+ actionsImpl.suggestActionsWithIntents(
+ nativeConversation,
+ null,
+ mContext,
+ // TODO: Pass the locale list once it is supported in native side.
+ LocaleList.getDefault().get(0).toLanguageTag());
+ return createConversationActionResult(request, nativeSuggestions);
} catch (Throwable t) {
// Avoid throwing from this method. Log the error.
Log.e(LOG_TAG, "Error suggesting conversation actions.", t);
@@ -422,6 +413,43 @@ public final class TextClassifierImpl implements TextClassifier {
return mFallback.suggestConversationActions(request);
}
+ private ConversationActions createConversationActionResult(
+ ConversationActions.Request request,
+ ActionsSuggestionsModel.ActionSuggestion[] nativeSuggestions) {
+ Collection<String> expectedTypes = resolveActionTypesFromRequest(request);
+ List<ConversationAction> conversationActions = new ArrayList<>();
+ for (ActionsSuggestionsModel.ActionSuggestion nativeSuggestion : nativeSuggestions) {
+ if (request.getMaxSuggestions() >= 0
+ && conversationActions.size() == request.getMaxSuggestions()) {
+ break;
+ }
+ String actionType = nativeSuggestion.getActionType();
+ if (!expectedTypes.contains(actionType)) {
+ continue;
+ }
+ List<LabeledIntent> labeledIntents =
+ mTemplateIntentFactory.create(nativeSuggestion.getRemoteActionTemplates());
+ RemoteAction remoteAction = null;
+ // Given that we only support implicit intent here, we should expect there is just one
+ // intent for each action type.
+ if (!labeledIntents.isEmpty()) {
+ remoteAction = labeledIntents.get(0).asRemoteAction(mContext);
+ }
+ conversationActions.add(
+ new ConversationAction.Builder(actionType)
+ .setConfidenceScore(nativeSuggestion.getScore())
+ .setTextReply(nativeSuggestion.getResponseText())
+ .setAction(remoteAction)
+ .build());
+ }
+ String resultId = ActionsSuggestionsHelper.createResultId(
+ mContext,
+ request.getConversation(),
+ mActionModelInUse.getVersion(),
+ mActionModelInUse.getSupportedLocales());
+ return new ConversationActions(conversationActions, resultId);
+ }
+
@Nullable
private String detectLanguageTagsFromText(CharSequence text) {
TextLanguage.Request request = new TextLanguage.Request.Builder(text).build();
@@ -462,11 +490,13 @@ public final class TextClassifierImpl implements TextClassifier {
}
if (mAnnotatorImpl == null || !Objects.equals(mAnnotatorModelInUse, bestModel)) {
Log.d(DEFAULT_LOG_TAG, "Loading " + bestModel);
- destroyAnnotatorImplIfExistsLocked();
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY);
try {
if (pfd != null) {
+ // The current annotator model may be still used by another thread / model.
+ // Do not call close() here, and let the GC to clean it up when no one else
+ // is using it.
mAnnotatorImpl = new AnnotatorModel(pfd.getFd());
mAnnotatorModelInUse = bestModel;
}
@@ -478,14 +508,6 @@ public final class TextClassifierImpl implements TextClassifier {
}
}
- @GuardedBy("mLock") // Do not call outside this lock.
- private void destroyAnnotatorImplIfExistsLocked() {
- if (mAnnotatorImpl != null) {
- mAnnotatorImpl.close();
- mAnnotatorImpl = null;
- }
- }
-
private LangIdModel getLangIdImpl() throws FileNotFoundException {
synchronized (mLock) {
if (mLangIdImpl == null) {
@@ -522,7 +544,8 @@ public final class TextClassifierImpl implements TextClassifier {
new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY);
try {
if (pfd != null) {
- mActionsImpl = new ActionsSuggestionsModel(pfd.getFd());
+ mActionsImpl = new ActionsSuggestionsModel(
+ pfd.getFd(), getAnnotatorImpl(LocaleList.getDefault()));
mActionModelInUse = bestModel;
}
} finally {
diff --git a/core/tests/coretests/src/android/view/textclassifier/TemplateClassificationIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/TemplateClassificationIntentFactoryTest.java
new file mode 100644
index 000000000000..08ad62ab843d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TemplateClassificationIntentFactoryTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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.content.Intent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.textclassifier.AnnotatorModel;
+import com.google.android.textclassifier.RemoteActionTemplate;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TemplateClassificationIntentFactoryTest {
+
+ private static final String TEXT = "text";
+ private static final String TITLE = "Map";
+ private static final String ACTION = Intent.ACTION_VIEW;
+
+ @Mock
+ private IntentFactory mFallback;
+ private TemplateClassificationIntentFactory mTemplateClassificationIntentFactory;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTemplateClassificationIntentFactory = new TemplateClassificationIntentFactory(
+ new TemplateIntentFactory(),
+ mFallback);
+ }
+
+ @Test
+ public void create_foreignText() {
+ RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate(
+ TITLE,
+ null,
+ ACTION,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ );
+
+ AnnotatorModel.ClassificationResult classificationResult =
+ new AnnotatorModel.ClassificationResult(
+ TextClassifier.TYPE_ADDRESS,
+ 1.0f,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ new RemoteActionTemplate[]{remoteActionTemplate});
+
+ List<TextClassifierImpl.LabeledIntent> intents =
+ mTemplateClassificationIntentFactory.create(
+ InstrumentationRegistry.getContext(),
+ TEXT,
+ /* foreignText */ true,
+ null,
+ classificationResult);
+
+ assertThat(intents).hasSize(2);
+ TextClassifierImpl.LabeledIntent labeledIntent = intents.get(0);
+ assertThat(labeledIntent.getTitle()).isEqualTo(TITLE);
+ Intent intent = labeledIntent.getIntent();
+ assertThat(intent.getAction()).isEqualTo(ACTION);
+ assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue();
+
+ labeledIntent = intents.get(1);
+ intent = labeledIntent.getIntent();
+ assertThat(intent.getAction()).isEqualTo(Intent.ACTION_TRANSLATE);
+ assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue();
+ }
+
+ @Test
+ public void create_notForeignText() {
+ RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate(
+ TITLE,
+ null,
+ ACTION,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ );
+
+ AnnotatorModel.ClassificationResult classificationResult =
+ new AnnotatorModel.ClassificationResult(
+ TextClassifier.TYPE_ADDRESS,
+ 1.0f,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ new RemoteActionTemplate[]{remoteActionTemplate});
+
+ List<TextClassifierImpl.LabeledIntent> intents =
+ mTemplateClassificationIntentFactory.create(
+ InstrumentationRegistry.getContext(),
+ TEXT,
+ /* foreignText */ false,
+ null,
+ classificationResult);
+
+ assertThat(intents).hasSize(1);
+ TextClassifierImpl.LabeledIntent labeledIntent = intents.get(0);
+ assertThat(labeledIntent.getTitle()).isEqualTo(TITLE);
+ Intent intent = labeledIntent.getIntent();
+ assertThat(intent.getAction()).isEqualTo(ACTION);
+ assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue();
+ }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TemplateIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/TemplateIntentFactoryTest.java
index 0fcf359708c1..0d364a329de4 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TemplateIntentFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TemplateIntentFactoryTest.java
@@ -13,7 +13,6 @@
* 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;
@@ -21,18 +20,15 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.Intent;
import android.net.Uri;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.google.android.textclassifier.AnnotatorModel;
import com.google.android.textclassifier.NamedVariant;
import com.google.android.textclassifier.RemoteActionTemplate;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
@@ -62,14 +58,12 @@ public class TemplateIntentFactoryTest {
};
private static final Integer REQUEST_CODE = 10;
- @Mock
- private IntentFactory mFallback;
private TemplateIntentFactory mTemplateIntentFactory;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mTemplateIntentFactory = new TemplateIntentFactory(mFallback);
+ mTemplateIntentFactory = new TemplateIntentFactory();
}
@Test
@@ -87,25 +81,9 @@ public class TemplateIntentFactoryTest {
REQUEST_CODE
);
- AnnotatorModel.ClassificationResult classificationResult =
- new AnnotatorModel.ClassificationResult(
- TextClassifier.TYPE_ADDRESS,
- 1.0f,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- new RemoteActionTemplate[]{remoteActionTemplate});
-
- List<TextClassifierImpl.LabeledIntent> intents = mTemplateIntentFactory.create(
- InstrumentationRegistry.getContext(),
- TEXT,
- false,
- null,
- classificationResult);
+
+ List<TextClassifierImpl.LabeledIntent> intents =
+ mTemplateIntentFactory.create(new RemoteActionTemplate[]{remoteActionTemplate});
assertThat(intents).hasSize(1);
TextClassifierImpl.LabeledIntent labeledIntent = intents.get(0);
@@ -122,12 +100,11 @@ public class TemplateIntentFactoryTest {
assertThat(
intent.getStringExtra(KEY_ONE)).isEqualTo(VALUE_ONE);
assertThat(intent.getIntExtra(KEY_TWO, 0)).isEqualTo(VALUE_TWO);
- assertThat(
- intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue();
+ assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue();
}
@Test
- public void create_pacakgeIsNotNull() {
+ public void create_packageIsNotNull() {
RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate(
TITLE,
DESCRIPTION,
@@ -141,25 +118,8 @@ public class TemplateIntentFactoryTest {
REQUEST_CODE
);
- AnnotatorModel.ClassificationResult classificationResult =
- new AnnotatorModel.ClassificationResult(
- TextClassifier.TYPE_ADDRESS,
- 1.0f,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- new RemoteActionTemplate[]{remoteActionTemplate});
-
- List<TextClassifierImpl.LabeledIntent> intents = mTemplateIntentFactory.create(
- InstrumentationRegistry.getContext(),
- TEXT,
- false,
- null,
- classificationResult);
+ List<TextClassifierImpl.LabeledIntent> intents =
+ mTemplateIntentFactory.create(new RemoteActionTemplate[] {remoteActionTemplate});
assertThat(intents).hasSize(0);
}
@@ -179,25 +139,9 @@ public class TemplateIntentFactoryTest {
null
);
- AnnotatorModel.ClassificationResult classificationResult =
- new AnnotatorModel.ClassificationResult(
- TextClassifier.TYPE_ADDRESS,
- 1.0f,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- new RemoteActionTemplate[]{remoteActionTemplate});
-
- List<TextClassifierImpl.LabeledIntent> intents = mTemplateIntentFactory.create(
- InstrumentationRegistry.getContext(),
- TEXT,
- false,
- null,
- classificationResult);
+ List<TextClassifierImpl.LabeledIntent> intents =
+ mTemplateIntentFactory.create(new RemoteActionTemplate[]{remoteActionTemplate});
+
assertThat(intents).hasSize(1);
TextClassifierImpl.LabeledIntent labeledIntent = intents.get(0);
@@ -212,7 +156,6 @@ public class TemplateIntentFactoryTest {
assertThat(intent.getFlags()).isEqualTo(0);
assertThat(intent.getCategories()).isNull();
assertThat(intent.getPackage()).isNull();
- assertThat(
- intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue();
+ assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue();
}
}
diff --git a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
index d99c356b8194..6cf72a45a06b 100644
--- a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
+++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
@@ -23,7 +23,6 @@ import android.os.Handler;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
-import android.util.KeyValueListParser;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -37,6 +36,9 @@ final class AssistantSettings extends ContentObserver {
private static final boolean DEFAULT_GENERATE_REPLIES = true;
private static final boolean DEFAULT_GENERATE_ACTIONS = true;
private static final int DEFAULT_NEW_INTERRUPTION_MODEL_INT = 1;
+ private static final int DEFAULT_MAX_MESSAGES_TO_EXTRACT = 5;
+ @VisibleForTesting
+ static final int DEFAULT_MAX_SUGGESTIONS = 3;
private static final Uri STREAK_LIMIT_URI =
Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT);
@@ -46,7 +48,6 @@ final class AssistantSettings extends ContentObserver {
private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI =
Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL);
- private final KeyValueListParser mParser = new KeyValueListParser(',');
private final ContentResolver mResolver;
private final int mUserId;
@@ -55,12 +56,14 @@ final class AssistantSettings extends ContentObserver {
@VisibleForTesting
protected final Runnable mOnUpdateRunnable;
- // Actuall configuration settings.
+ // Actual configuration settings.
float mDismissToViewRatioLimit;
int mStreakLimit;
boolean mGenerateReplies = DEFAULT_GENERATE_REPLIES;
boolean mGenerateActions = DEFAULT_GENERATE_ACTIONS;
boolean mNewInterruptionModel;
+ int mMaxMessagesToExtract = DEFAULT_MAX_MESSAGES_TO_EXTRACT;
+ int mMaxSuggestions = DEFAULT_MAX_SUGGESTIONS;
private AssistantSettings(Handler handler, ContentResolver resolver, int userId,
Runnable onUpdateRunnable) {
@@ -124,27 +127,18 @@ final class AssistantSettings extends ContentObserver {
}
private void updateFromDeviceConfigFlags() {
- String generateRepliesFlag = DeviceConfig.getProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_REPLIES);
- if (TextUtils.isEmpty(generateRepliesFlag)) {
- mGenerateReplies = DEFAULT_GENERATE_REPLIES;
- } else {
- // parseBoolean returns false for everything that isn't 'true' so there's no need to
- // sanitise the flag string here.
- mGenerateReplies = Boolean.parseBoolean(generateRepliesFlag);
- }
+ mGenerateReplies = DeviceConfigHelper.getBoolean(
+ DeviceConfig.NotificationAssistant.GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES);
- String generateActionsFlag = DeviceConfig.getProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_ACTIONS);
- if (TextUtils.isEmpty(generateActionsFlag)) {
- mGenerateActions = DEFAULT_GENERATE_ACTIONS;
- } else {
- // parseBoolean returns false for everything that isn't 'true' so there's no need to
- // sanitise the flag string here.
- mGenerateActions = Boolean.parseBoolean(generateActionsFlag);
- }
+ mGenerateActions = DeviceConfigHelper.getBoolean(
+ DeviceConfig.NotificationAssistant.GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS);
+
+ mMaxMessagesToExtract = DeviceConfigHelper.getInteger(
+ DeviceConfig.NotificationAssistant.MAX_MESSAGES_TO_EXTRACT,
+ DEFAULT_MAX_MESSAGES_TO_EXTRACT);
+
+ mMaxSuggestions = DeviceConfigHelper.getInteger(
+ DeviceConfig.NotificationAssistant.MAX_SUGGESTIONS, DEFAULT_MAX_SUGGESTIONS);
mOnUpdateRunnable.run();
}
@@ -175,8 +169,37 @@ final class AssistantSettings extends ContentObserver {
mOnUpdateRunnable.run();
}
+ static class DeviceConfigHelper {
+
+ static int getInteger(String key, int defaultValue) {
+ String value = getValue(key);
+ if (TextUtils.isEmpty(value)) {
+ return defaultValue;
+ }
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException ex) {
+ return defaultValue;
+ }
+ }
+
+ static boolean getBoolean(String key, boolean defaultValue) {
+ String value = getValue(key);
+ if (TextUtils.isEmpty(value)) {
+ return defaultValue;
+ }
+ return Boolean.parseBoolean(value);
+ }
+
+ private static String getValue(String key) {
+ return DeviceConfig.getProperty(
+ DeviceConfig.NotificationAssistant.NAMESPACE,
+ key);
+ }
+ }
+
public interface Factory {
AssistantSettings createAndRegister(Handler handler, ContentResolver resolver, int userId,
Runnable onUpdateRunnable);
}
-}
+} \ No newline at end of file
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 48a3974e913b..08cc39fc4935 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -29,28 +29,22 @@ import android.text.TextUtils;
import android.util.LruCache;
import android.view.textclassifier.ConversationAction;
import android.view.textclassifier.ConversationActions;
-import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextClassifierEvent;
-import android.view.textclassifier.TextLinks;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.stream.Collectors;
public class SmartActionsHelper {
- private static final ArrayList<Notification.Action> EMPTY_ACTION_LIST = new ArrayList<>();
- private static final ArrayList<CharSequence> EMPTY_REPLY_LIST = new ArrayList<>();
-
private static final String KEY_ACTION_TYPE = "action_type";
// If a notification has any of these flags set, it's inelgibile for actions being added.
private static final int FLAG_MASK_INELGIBILE_FOR_ACTIONS =
@@ -58,19 +52,8 @@ public class SmartActionsHelper {
| Notification.FLAG_FOREGROUND_SERVICE
| Notification.FLAG_GROUP_SUMMARY
| Notification.FLAG_NO_CLEAR;
- private static final int MAX_ACTION_EXTRACTION_TEXT_LENGTH = 400;
- private static final int MAX_ACTIONS_PER_LINK = 1;
- private static final int MAX_SMART_ACTIONS = 3;
- private static final int MAX_SUGGESTED_REPLIES = 3;
- // TODO: Make this configurable.
- private static final int MAX_MESSAGES_TO_EXTRACT = 5;
private static final int MAX_RESULT_ID_TO_CACHE = 20;
- private static final TextClassifier.EntityConfig TYPE_CONFIG =
- new TextClassifier.EntityConfig.Builder().setIncludedTypes(
- Collections.singletonList(ConversationAction.TYPE_TEXT_REPLY))
- .includeTypesFromTextClassifier(false)
- .build();
private static final List<String> HINTS =
Collections.singletonList(ConversationActions.Request.HINT_FOR_NOTIFICATION);
@@ -92,20 +75,30 @@ public class SmartActionsHelper {
mSettings = settings;
}
- @NonNull
SmartSuggestions suggest(@NonNull NotificationEntry entry) {
// Whenever suggest() is called on a notification, its previous session is ended.
mNotificationKeyToResultIdCache.remove(entry.getSbn().getKey());
- ArrayList<Notification.Action> actions = suggestActions(entry);
- ArrayList<CharSequence> replies = suggestReplies(entry);
+ boolean eligibleForReplyAdjustment =
+ mSettings.mGenerateReplies && isEligibleForReplyAdjustment(entry);
+ boolean eligibleForActionAdjustment =
+ mSettings.mGenerateActions && isEligibleForActionAdjustment(entry);
- // Not logging subsequent events of this notification if we didn't generate any suggestion
- // for it.
- if (replies.isEmpty() && actions.isEmpty()) {
- mNotificationKeyToResultIdCache.remove(entry.getSbn().getKey());
- }
+ List<ConversationAction> conversationActions =
+ suggestConversationActions(
+ entry,
+ eligibleForReplyAdjustment,
+ eligibleForActionAdjustment);
+ ArrayList<CharSequence> replies = conversationActions.stream()
+ .map(ConversationAction::getTextReply)
+ .filter(textReply -> !TextUtils.isEmpty(textReply))
+ .collect(Collectors.toCollection(ArrayList::new));
+
+ ArrayList<Notification.Action> actions = conversationActions.stream()
+ .filter(conversationAction -> conversationAction.getAction() != null)
+ .map(action -> createNotificationAction(action.getAction(), action.getType()))
+ .collect(Collectors.toCollection(ArrayList::new));
return new SmartSuggestions(replies, actions);
}
@@ -113,61 +106,48 @@ public class SmartActionsHelper {
* Adds action adjustments based on the notification contents.
*/
@NonNull
- ArrayList<Notification.Action> suggestActions(@NonNull NotificationEntry entry) {
- if (!mSettings.mGenerateActions) {
- return EMPTY_ACTION_LIST;
- }
- if (!isEligibleForActionAdjustment(entry)) {
- return EMPTY_ACTION_LIST;
+ private List<ConversationAction> suggestConversationActions(
+ @NonNull NotificationEntry entry,
+ boolean includeReplies,
+ boolean includeActions) {
+ if (!includeReplies && !includeActions) {
+ return Collections.emptyList();
}
if (mTextClassifier == null) {
- return EMPTY_ACTION_LIST;
+ return Collections.emptyList();
}
List<ConversationActions.Message> messages = extractMessages(entry.getNotification());
if (messages.isEmpty()) {
- return EMPTY_ACTION_LIST;
+ return Collections.emptyList();
}
- // TODO: Move to TextClassifier.suggestConversationActions once it is ready.
- return suggestActionsFromText(
- messages.get(messages.size() - 1).getText(), MAX_SMART_ACTIONS);
- }
- @NonNull
- ArrayList<CharSequence> suggestReplies(@NonNull NotificationEntry entry) {
- if (!mSettings.mGenerateReplies) {
- return EMPTY_REPLY_LIST;
- }
- if (!isEligibleForReplyAdjustment(entry)) {
- return EMPTY_REPLY_LIST;
- }
- if (mTextClassifier == null) {
- return EMPTY_REPLY_LIST;
- }
- List<ConversationActions.Message> messages = extractMessages(entry.getNotification());
- if (messages.isEmpty()) {
- return EMPTY_REPLY_LIST;
+ TextClassifier.EntityConfig.Builder typeConfigBuilder =
+ new TextClassifier.EntityConfig.Builder();
+ if (!includeReplies) {
+ typeConfigBuilder.setExcludedTypes(
+ Collections.singletonList(ConversationAction.TYPE_TEXT_REPLY));
+ } else if (!includeActions) {
+ typeConfigBuilder
+ .setIncludedTypes(
+ Collections.singletonList(ConversationAction.TYPE_TEXT_REPLY))
+ .includeTypesFromTextClassifier(false);
}
ConversationActions.Request request =
new ConversationActions.Request.Builder(messages)
- .setMaxSuggestions(MAX_SUGGESTED_REPLIES)
+ .setMaxSuggestions(mSettings.mMaxSuggestions)
.setHints(HINTS)
- .setTypeConfig(TYPE_CONFIG)
+ .setTypeConfig(typeConfigBuilder.build())
.build();
ConversationActions conversationActionsResult =
mTextClassifier.suggestConversationActions(request);
- List<ConversationAction> conversationActions =
- conversationActionsResult.getConversationActions();
- ArrayList<CharSequence> replies = conversationActions.stream()
- .map(conversationAction -> conversationAction.getTextReply())
- .filter(textReply -> !TextUtils.isEmpty(textReply))
- .collect(Collectors.toCollection(ArrayList::new));
String resultId = conversationActionsResult.getId();
- if (resultId != null) {
+ if (!TextUtils.isEmpty(resultId)
+ && !conversationActionsResult.getConversationActions().isEmpty()) {
mNotificationKeyToResultIdCache.put(entry.getSbn().getKey(), resultId);
}
- return replies;
+ return conversationActionsResult.getConversationActions();
}
void onNotificationExpansionChanged(@NonNull NotificationEntry entry, boolean isUserAction,
@@ -248,6 +228,17 @@ public class SmartActionsHelper {
mTextClassifier.onTextClassifierEvent(textClassifierEvent);
}
+ private Notification.Action createNotificationAction(
+ RemoteAction remoteAction, String actionType) {
+ return new Notification.Action.Builder(
+ remoteAction.getIcon(),
+ remoteAction.getTitle(),
+ remoteAction.getActionIntent())
+ .setContextual(true)
+ .addExtras(Bundle.forPair(KEY_ACTION_TYPE, actionType))
+ .build();
+ }
+
private TextClassifierEvent.Builder createTextClassifierEventBuilder(
int eventType, @NonNull String resultId) {
return new TextClassifierEvent.Builder(
@@ -308,7 +299,7 @@ public class SmartActionsHelper {
private List<ConversationActions.Message> extractMessages(@NonNull Notification notification) {
Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
if (messages == null || messages.length == 0) {
- return Arrays.asList(new ConversationActions.Message.Builder(
+ return Collections.singletonList(new ConversationActions.Message.Builder(
ConversationActions.Message.PERSON_USER_OTHERS)
.setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT))
.build());
@@ -335,75 +326,13 @@ public class SmartActionsHelper {
ZonedDateTime.ofInstant(Instant.ofEpochMilli(message.getTimestamp()),
ZoneOffset.systemDefault()))
.build());
- if (extractMessages.size() >= MAX_MESSAGES_TO_EXTRACT) {
+ if (extractMessages.size() >= mSettings.mMaxMessagesToExtract) {
break;
}
}
return new ArrayList<>(extractMessages);
}
- /** Returns a list of actions to act on entities in a given piece of text. */
- @NonNull
- private ArrayList<Notification.Action> suggestActionsFromText(
- @Nullable CharSequence text, int maxSmartActions) {
- if (TextUtils.isEmpty(text)) {
- return EMPTY_ACTION_LIST;
- }
- // We want to process only text visible to the user to avoid confusing suggestions, so we
- // truncate the text to a reasonable length. This is particularly important for e.g.
- // email apps that sometimes include the text for the entire thread.
- text = text.subSequence(0, Math.min(text.length(), MAX_ACTION_EXTRACTION_TEXT_LENGTH));
-
- // Extract all entities.
- TextLinks.Request textLinksRequest = new TextLinks.Request.Builder(text)
- .setEntityConfig(
- TextClassifier.EntityConfig.createWithHints(
- Collections.singletonList(
- TextClassifier.HINT_TEXT_IS_NOT_EDITABLE)))
- .build();
- TextLinks links = mTextClassifier.generateLinks(textLinksRequest);
- EntityTypeCounter entityTypeCounter = EntityTypeCounter.fromTextLinks(links);
-
- ArrayList<Notification.Action> actions = new ArrayList<>();
- for (TextLinks.TextLink link : links.getLinks()) {
- // Ignore any entity type for which we have too many entities. This is to handle the
- // case where a notification contains e.g. a list of phone numbers. In such cases, the
- // user likely wants to act on the whole list rather than an individual entity.
- if (link.getEntityCount() == 0
- || entityTypeCounter.getCount(link.getEntity(0)) != 1) {
- continue;
- }
-
- // Generate the actions, and add the most prominent ones to the action bar.
- TextClassification classification =
- mTextClassifier.classifyText(
- new TextClassification.Request.Builder(
- text, link.getStart(), link.getEnd()).build());
- if (classification.getEntityCount() == 0) {
- continue;
- }
- int numOfActions = Math.min(
- MAX_ACTIONS_PER_LINK, classification.getActions().size());
- for (int i = 0; i < numOfActions; ++i) {
- RemoteAction remoteAction = classification.getActions().get(i);
- Notification.Action action = new Notification.Action.Builder(
- remoteAction.getIcon(),
- remoteAction.getTitle(),
- remoteAction.getActionIntent())
- .setContextual(true)
- .addExtras(Bundle.forPair(KEY_ACTION_TYPE, classification.getEntity(0)))
- .build();
- actions.add(action);
-
- // We have enough smart actions.
- if (actions.size() >= maxSmartActions) {
- return actions;
- }
- }
- }
- return actions;
- }
-
static class SmartSuggestions {
public final ArrayList<CharSequence> replies;
public final ArrayList<Notification.Action> actions;
diff --git a/packages/ExtServices/tests/Android.bp b/packages/ExtServices/tests/Android.bp
index 5de454836c2a..930b783aaa98 100644
--- a/packages/ExtServices/tests/Android.bp
+++ b/packages/ExtServices/tests/Android.bp
@@ -15,6 +15,7 @@ android_test {
static_libs: [
"ExtServices-core",
"android-support-test",
+ "compatibility-device-util",
"mockito-target-minus-junit4",
"espresso-core",
"truth-prebuilt",
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
index 597051a8b744..d890c1ae81fb 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
@@ -16,6 +16,10 @@
package android.ext.services.notification;
+import static android.ext.services.notification.AssistantSettings.DEFAULT_MAX_SUGGESTIONS;
+import static android.provider.DeviceConfig.NotificationAssistant;
+import static android.provider.DeviceConfig.setProperty;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -26,12 +30,13 @@ import static org.mockito.Mockito.verify;
import android.content.ContentResolver;
import android.os.Handler;
import android.os.Looper;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
import android.testing.TestableContext;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -39,8 +44,13 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.IOException;
+
@RunWith(AndroidJUnit4.class)
public class AssistantSettingsTest {
+ private static final String CLEAR_DEVICE_CONFIG_KEY_CMD =
+ "device_config delete " + NotificationAssistant.NAMESPACE;
+
private static final int USER_ID = 5;
@Rule
@@ -69,16 +79,21 @@ public class AssistantSettingsTest {
handler, mResolver, USER_ID, mOnUpdateRunnable);
}
+ @After
+ public void tearDown() throws IOException {
+ clearDeviceConfig();
+ }
+
@Test
public void testGenerateRepliesDisabled() {
- DeviceConfig.setProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_REPLIES,
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_REPLIES,
"false",
false /* makeDefault */);
mAssistantSettings.onDeviceConfigPropertyChanged(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_REPLIES,
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_REPLIES,
"false");
assertFalse(mAssistantSettings.mGenerateReplies);
@@ -86,14 +101,14 @@ public class AssistantSettingsTest {
@Test
public void testGenerateRepliesEnabled() {
- DeviceConfig.setProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_REPLIES,
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_REPLIES,
"true",
false /* makeDefault */);
mAssistantSettings.onDeviceConfigPropertyChanged(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_REPLIES,
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_REPLIES,
"true");
assertTrue(mAssistantSettings.mGenerateReplies);
@@ -101,26 +116,26 @@ public class AssistantSettingsTest {
@Test
public void testGenerateRepliesEmptyFlag() {
- DeviceConfig.setProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_REPLIES,
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_REPLIES,
"false",
false /* makeDefault */);
mAssistantSettings.onDeviceConfigPropertyChanged(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_REPLIES,
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_REPLIES,
"false");
assertFalse(mAssistantSettings.mGenerateReplies);
- DeviceConfig.setProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_REPLIES,
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_REPLIES,
"",
false /* makeDefault */);
mAssistantSettings.onDeviceConfigPropertyChanged(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_REPLIES,
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_REPLIES,
"");
// Go back to the default value.
@@ -129,14 +144,14 @@ public class AssistantSettingsTest {
@Test
public void testGenerateActionsDisabled() {
- DeviceConfig.setProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_ACTIONS,
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_ACTIONS,
"false",
false /* makeDefault */);
mAssistantSettings.onDeviceConfigPropertyChanged(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_ACTIONS,
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_ACTIONS,
"false");
assertFalse(mAssistantSettings.mGenerateActions);
@@ -144,14 +159,14 @@ public class AssistantSettingsTest {
@Test
public void testGenerateActionsEnabled() {
- DeviceConfig.setProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_ACTIONS,
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_ACTIONS,
"true",
false /* makeDefault */);
mAssistantSettings.onDeviceConfigPropertyChanged(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_ACTIONS,
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_ACTIONS,
"true");
assertTrue(mAssistantSettings.mGenerateActions);
@@ -159,26 +174,26 @@ public class AssistantSettingsTest {
@Test
public void testGenerateActionsEmptyFlag() {
- DeviceConfig.setProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_ACTIONS,
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_ACTIONS,
"false",
false /* makeDefault */);
mAssistantSettings.onDeviceConfigPropertyChanged(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_ACTIONS,
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_ACTIONS,
"false");
assertFalse(mAssistantSettings.mGenerateActions);
- DeviceConfig.setProperty(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_ACTIONS,
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_ACTIONS,
"",
false /* makeDefault */);
mAssistantSettings.onDeviceConfigPropertyChanged(
- DeviceConfig.NotificationAssistant.NAMESPACE,
- DeviceConfig.NotificationAssistant.GENERATE_ACTIONS,
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.GENERATE_ACTIONS,
"");
// Go back to the default value.
@@ -186,6 +201,46 @@ public class AssistantSettingsTest {
}
@Test
+ public void testMaxMessagesToExtract() {
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.MAX_MESSAGES_TO_EXTRACT,
+ "10",
+ false /* makeDefault */);
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.MAX_MESSAGES_TO_EXTRACT,
+ "10");
+
+ assertEquals(10, mAssistantSettings.mMaxMessagesToExtract);
+ }
+
+ @Test
+ public void testMaxSuggestions() {
+ setProperty(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.MAX_SUGGESTIONS,
+ "5",
+ false /* makeDefault */);
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.MAX_SUGGESTIONS,
+ "5");
+
+ assertEquals(5, mAssistantSettings.mMaxSuggestions);
+ }
+
+ @Test
+ public void testMaxSuggestionsEmpty() {
+ mAssistantSettings.onDeviceConfigPropertyChanged(
+ NotificationAssistant.NAMESPACE,
+ NotificationAssistant.MAX_SUGGESTIONS,
+ "");
+
+ assertEquals(DEFAULT_MAX_SUGGESTIONS, mAssistantSettings.mMaxSuggestions);
+ }
+
+ @Test
public void testStreakLimit() {
verify(mOnUpdateRunnable, never()).run();
@@ -219,4 +274,17 @@ public class AssistantSettingsTest {
assertEquals(newDismissToViewRatioLimit, mAssistantSettings.mDismissToViewRatioLimit, 1e-6);
verify(mOnUpdateRunnable).run();
}
+
+ private static void clearDeviceConfig() throws IOException {
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ uiDevice.executeShellCommand(
+ CLEAR_DEVICE_CONFIG_KEY_CMD + " " + NotificationAssistant.GENERATE_ACTIONS);
+ uiDevice.executeShellCommand(
+ CLEAR_DEVICE_CONFIG_KEY_CMD + " " + NotificationAssistant.GENERATE_REPLIES);
+ uiDevice.executeShellCommand(
+ CLEAR_DEVICE_CONFIG_KEY_CMD + " " + NotificationAssistant.MAX_MESSAGES_TO_EXTRACT);
+ uiDevice.executeShellCommand(
+ CLEAR_DEVICE_CONFIG_KEY_CMD + " " + NotificationAssistant.MAX_SUGGESTIONS);
+ }
+
}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java
index 7f8127aa43a8..ebbd961b6f23 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java
@@ -25,8 +25,15 @@ import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.Person;
+import android.app.RemoteInput;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.graphics.drawable.Icon;
import android.os.Process;
import android.service.notification.NotificationAssistantService;
import android.service.notification.StatusBarNotification;
@@ -53,7 +60,6 @@ import org.mockito.MockitoAnnotations;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -62,20 +68,26 @@ import java.util.Objects;
import javax.annotation.Nullable;
@RunWith(AndroidJUnit4.class)
-public class SmartActionHelperTest {
+public class SmartActionsHelperTest {
private static final String NOTIFICATION_KEY = "key";
private static final String RESULT_ID = "id";
-
private static final ConversationAction REPLY_ACTION =
new ConversationAction.Builder(ConversationAction.TYPE_TEXT_REPLY)
- .setTextReply("Home")
- .build();
+ .setTextReply("Home")
+ .build();
+ private static final String MESSAGE = "Where are you?";
+
+ @Mock
+ IPackageManager mIPackageManager;
+ @Mock
+ private TextClassifier mTextClassifier;
+ @Mock
+ private StatusBarNotification mStatusBarNotification;
+ @Mock
+ private SmsHelper mSmsHelper;
private SmartActionsHelper mSmartActionsHelper;
private Context mContext;
- @Mock private TextClassifier mTextClassifier;
- @Mock private NotificationEntry mNotificationEntry;
- @Mock private StatusBarNotification mStatusBarNotification;
private Notification.Builder mNotificationBuilder;
private AssistantSettings mSettings;
@@ -89,10 +101,6 @@ public class SmartActionHelperTest {
when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class)))
.thenReturn(new ConversationActions(Arrays.asList(REPLY_ACTION), RESULT_ID));
- when(mNotificationEntry.getSbn()).thenReturn(mStatusBarNotification);
- // The notification is eligible to have smart suggestions.
- when(mNotificationEntry.hasInlineReply()).thenReturn(true);
- when(mNotificationEntry.isMessaging()).thenReturn(true);
when(mStatusBarNotification.getPackageName()).thenReturn("random.app");
when(mStatusBarNotification.getUser()).thenReturn(Process.myUserHandle());
when(mStatusBarNotification.getKey()).thenReturn(NOTIFICATION_KEY);
@@ -105,33 +113,96 @@ public class SmartActionHelperTest {
}
@Test
- public void testSuggestReplies_notMessagingApp() {
- when(mNotificationEntry.isMessaging()).thenReturn(false);
- ArrayList<CharSequence> textReplies =
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
- assertThat(textReplies).isEmpty();
+ public void testSuggest_notMessageNotification() {
+ Notification notification = mNotificationBuilder.setContentText(MESSAGE).build();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
+
+ mSmartActionsHelper.suggest(createNotificationEntry());
+
+ verify(mTextClassifier, never())
+ .suggestConversationActions(any(ConversationActions.Request.class));
+ }
+
+ @Test
+ public void testSuggest_noInlineReply() {
+ Notification notification =
+ mNotificationBuilder
+ .setContentText(MESSAGE)
+ .setCategory(Notification.CATEGORY_MESSAGE)
+ .build();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
+
+ ConversationActions.Request request = runSuggestAndCaptureRequest();
+
+ // actions are enabled, but replies are not.
+ assertThat(
+ request.getTypeConfig().resolveEntityListModifications(
+ Arrays.asList(ConversationAction.TYPE_TEXT_REPLY,
+ ConversationAction.TYPE_OPEN_URL)))
+ .containsExactly(ConversationAction.TYPE_OPEN_URL);
+ }
+
+ @Test
+ public void testSuggest_settingsOff() {
+ mSettings.mGenerateActions = false;
+ mSettings.mGenerateReplies = false;
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
+
+ mSmartActionsHelper.suggest(createNotificationEntry());
+
+ verify(mTextClassifier, never())
+ .suggestConversationActions(any(ConversationActions.Request.class));
+ }
+
+ @Test
+ public void testSuggest_settings_repliesOnActionsOff() {
+ mSettings.mGenerateReplies = true;
+ mSettings.mGenerateActions = false;
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
+
+ ConversationActions.Request request = runSuggestAndCaptureRequest();
+
+ // replies are enabled, but actions are not.
+ assertThat(
+ request.getTypeConfig().resolveEntityListModifications(
+ Arrays.asList(ConversationAction.TYPE_TEXT_REPLY,
+ ConversationAction.TYPE_OPEN_URL)))
+ .containsExactly(ConversationAction.TYPE_TEXT_REPLY);
}
@Test
- public void testSuggestReplies_noInlineReply() {
- when(mNotificationEntry.hasInlineReply()).thenReturn(false);
- ArrayList<CharSequence> textReplies =
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
- assertThat(textReplies).isEmpty();
+ public void testSuggest_settings_repliesOffActionsOn() {
+ mSettings.mGenerateReplies = false;
+ mSettings.mGenerateActions = true;
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
+
+ ConversationActions.Request request = runSuggestAndCaptureRequest();
+
+ // actions are enabled, but replies are not.
+ assertThat(
+ request.getTypeConfig().resolveEntityListModifications(
+ Arrays.asList(ConversationAction.TYPE_TEXT_REPLY,
+ ConversationAction.TYPE_OPEN_URL)))
+ .containsExactly(ConversationAction.TYPE_OPEN_URL);
}
+
@Test
- public void testSuggestReplies_nonMessageStyle() {
- Notification notification = mNotificationBuilder.setContentText("Where are you?").build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ public void testSuggest_nonMessageStyleMessageNotification() {
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- List<ConversationActions.Message> messages = getMessagesInRequest();
+ List<ConversationActions.Message> messages =
+ runSuggestAndCaptureRequest().getConversation();
assertThat(messages).hasSize(1);
- MessageSubject.assertThat(messages.get(0)).hasText("Where are you?");
+ MessageSubject.assertThat(messages.get(0)).hasText(MESSAGE);
}
@Test
- public void testSuggestReplies_messageStyle() {
+ public void testSuggest_messageStyle() {
Person me = new Person.Builder().setName("Me").build();
Person userA = new Person.Builder().setName("A").build();
Person userB = new Person.Builder().setName("B").build();
@@ -145,10 +216,12 @@ public class SmartActionHelperTest {
mNotificationBuilder
.setContentText("You have three new messages")
.setStyle(style)
+ .setActions(createReplyAction())
.build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- List<ConversationActions.Message> messages = getMessagesInRequest();
+ List<ConversationActions.Message> messages =
+ runSuggestAndCaptureRequest().getConversation();
assertThat(messages).hasSize(3);
ConversationActions.Message secondMessage = messages.get(0);
@@ -172,7 +245,7 @@ public class SmartActionHelperTest {
}
@Test
- public void testSuggestReplies_messageStyle_noPerson() {
+ public void testSuggest_messageStyle_noPerson() {
Person me = new Person.Builder().setName("Me").build();
Notification.MessagingStyle style =
new Notification.MessagingStyle(me).addMessage("message", 1000, (Person) null);
@@ -180,10 +253,11 @@ public class SmartActionHelperTest {
mNotificationBuilder
.setContentText("You have one new message")
.setStyle(style)
+ .setActions(createReplyAction())
.build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
+ mSmartActionsHelper.suggest(createNotificationEntry());
verify(mTextClassifier, never())
.suggestConversationActions(any(ConversationActions.Request.class));
@@ -191,13 +265,12 @@ public class SmartActionHelperTest {
@Test
public void testOnSuggestedReplySent() {
- final String message = "Where are you?";
- Notification notification = mNotificationBuilder.setContentText(message).build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
+ mSmartActionsHelper.suggest(createNotificationEntry());
mSmartActionsHelper.onSuggestedReplySent(
- NOTIFICATION_KEY, message, NotificationAssistantService.SOURCE_FROM_ASSISTANT);
+ NOTIFICATION_KEY, MESSAGE, NotificationAssistantService.SOURCE_FROM_ASSISTANT);
ArgumentCaptor<TextClassifierEvent> argumentCaptor =
ArgumentCaptor.forClass(TextClassifierEvent.class);
@@ -208,13 +281,12 @@ public class SmartActionHelperTest {
@Test
public void testOnSuggestedReplySent_anotherNotification() {
- final String message = "Where are you?";
- Notification notification = mNotificationBuilder.setContentText(message).build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
+ mSmartActionsHelper.suggest(createNotificationEntry());
mSmartActionsHelper.onSuggestedReplySent(
- "something_else", message, NotificationAssistantService.SOURCE_FROM_ASSISTANT);
+ "something_else", MESSAGE, NotificationAssistantService.SOURCE_FROM_ASSISTANT);
verify(mTextClassifier, never())
.onTextClassifierEvent(Mockito.any(TextClassifierEvent.class));
@@ -225,13 +297,12 @@ public class SmartActionHelperTest {
when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class)))
.thenReturn(new ConversationActions(Collections.emptyList(), null));
- final String message = "Where are you?";
- Notification notification = mNotificationBuilder.setContentText(message).build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
+ mSmartActionsHelper.suggest(createNotificationEntry());
mSmartActionsHelper.onSuggestedReplySent(
- "something_else", message, NotificationAssistantService.SOURCE_FROM_ASSISTANT);
+ "something_else", MESSAGE, NotificationAssistantService.SOURCE_FROM_ASSISTANT);
verify(mTextClassifier, never())
.onTextClassifierEvent(Mockito.any(TextClassifierEvent.class));
@@ -239,10 +310,10 @@ public class SmartActionHelperTest {
@Test
public void testOnNotificationDirectReply() {
- Notification notification = mNotificationBuilder.setContentText("Where are you?").build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
+ mSmartActionsHelper.suggest(createNotificationEntry());
mSmartActionsHelper.onNotificationDirectReplied(NOTIFICATION_KEY);
ArgumentCaptor<TextClassifierEvent> argumentCaptor =
@@ -254,12 +325,11 @@ public class SmartActionHelperTest {
@Test
public void testOnNotificationExpansionChanged() {
- final String message = "Where are you?";
- Notification notification = mNotificationBuilder.setContentText(message).build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
- mSmartActionsHelper.onNotificationExpansionChanged(mNotificationEntry, true, true);
+ mSmartActionsHelper.suggest(createNotificationEntry());
+ mSmartActionsHelper.onNotificationExpansionChanged(createNotificationEntry(), true, true);
ArgumentCaptor<TextClassifierEvent> argumentCaptor =
ArgumentCaptor.forClass(TextClassifierEvent.class);
@@ -270,12 +340,11 @@ public class SmartActionHelperTest {
@Test
public void testOnNotificationsSeen_notExpanded() {
- final String message = "Where are you?";
- Notification notification = mNotificationBuilder.setContentText(message).build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
- mSmartActionsHelper.onNotificationExpansionChanged(mNotificationEntry, false, false);
+ mSmartActionsHelper.suggest(createNotificationEntry());
+ mSmartActionsHelper.onNotificationExpansionChanged(createNotificationEntry(), false, false);
verify(mTextClassifier, never()).onTextClassifierEvent(
Mockito.any(TextClassifierEvent.class));
@@ -283,12 +352,11 @@ public class SmartActionHelperTest {
@Test
public void testOnNotifications_expanded() {
- final String message = "Where are you?";
- Notification notification = mNotificationBuilder.setContentText(message).build();
- when(mNotificationEntry.getNotification()).thenReturn(notification);
+ Notification notification = createMessageNotification();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
- mSmartActionsHelper.onNotificationExpansionChanged(mNotificationEntry, false, true);
+ mSmartActionsHelper.suggest(createNotificationEntry());
+ mSmartActionsHelper.onNotificationExpansionChanged(createNotificationEntry(), false, true);
ArgumentCaptor<TextClassifierEvent> argumentCaptor =
ArgumentCaptor.forClass(TextClassifierEvent.class);
@@ -301,14 +369,41 @@ public class SmartActionHelperTest {
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneOffset.systemDefault());
}
- private List<ConversationActions.Message> getMessagesInRequest() {
- mSmartActionsHelper.suggestReplies(mNotificationEntry);
+ private ConversationActions.Request runSuggestAndCaptureRequest() {
+ mSmartActionsHelper.suggest(createNotificationEntry());
ArgumentCaptor<ConversationActions.Request> argumentCaptor =
ArgumentCaptor.forClass(ConversationActions.Request.class);
verify(mTextClassifier).suggestConversationActions(argumentCaptor.capture());
- ConversationActions.Request request = argumentCaptor.getValue();
- return request.getConversation();
+ return argumentCaptor.getValue();
+ }
+
+ private Notification.Action createReplyAction() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(mContext, this.getClass()), 0);
+ RemoteInput remoteInput = new RemoteInput.Builder("result")
+ .setAllowFreeFormInput(true)
+ .build();
+ return new Notification.Action.Builder(
+ Icon.createWithResource(mContext.getResources(),
+ android.R.drawable.stat_sys_warning),
+ "Reply", pendingIntent)
+ .addRemoteInput(remoteInput)
+ .build();
+ }
+
+ private NotificationEntry createNotificationEntry() {
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
+ return new NotificationEntry(mIPackageManager, mStatusBarNotification, channel, mSmsHelper);
+ }
+
+ private Notification createMessageNotification() {
+ return mNotificationBuilder
+ .setContentText(MESSAGE)
+ .setCategory(Notification.CATEGORY_MESSAGE)
+ .setActions(createReplyAction())
+ .build();
}
private void assertTextClassifierEvent(