Loading core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +52 −2 Original line number Original line Diff line number Diff line Loading @@ -18,9 +18,11 @@ package android.view.textclassifier; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.Person; import android.app.Person; import android.app.RemoteAction; import android.content.Context; import android.content.Context; import android.text.TextUtils; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -118,11 +120,59 @@ public final class ActionsSuggestionsHelper { @Nullable @Nullable public static LabeledIntent.TitleChooser createTitleChooser(String actionType) { public static LabeledIntent.TitleChooser createTitleChooser(String actionType) { if (ConversationAction.TYPE_OPEN_URL.equals(actionType)) { if (ConversationAction.TYPE_OPEN_URL.equals(actionType)) { return (labeledIntent, resolveInfo) -> resolveInfo.handleAllWebDataURI return (labeledIntent, resolveInfo) -> { ? labeledIntent.titleWithEntity : labeledIntent.titleWithoutEntity; if (resolveInfo.handleAllWebDataURI) { return labeledIntent.titleWithEntity; } } if ("android".equals(resolveInfo.activityInfo.packageName)) { return labeledIntent.titleWithEntity; } return labeledIntent.titleWithoutEntity; }; } return null; } /** * Returns a list of {@link ConversationAction}s that have 0 duplicates. Two actions are * duplicates if they may look the same to users. This function assumes every * ConversationActions with a non-null RemoteAction also have a non-null intent in the extras. */ public static List<ConversationAction> removeActionsWithDuplicates( List<ConversationAction> conversationActions) { // Ideally, we should compare title and icon here, but comparing icon is expensive and thus // we use the component name of the target handler as the heuristic. Map<Pair<String, String>, Integer> counter = new ArrayMap<>(); for (ConversationAction conversationAction : conversationActions) { Pair<String, String> representation = getRepresentation(conversationAction); if (representation == null) { continue; } Integer existingCount = counter.getOrDefault(representation, 0); counter.put(representation, existingCount + 1); } List<ConversationAction> result = new ArrayList<>(); for (ConversationAction conversationAction : conversationActions) { Pair<String, String> representation = getRepresentation(conversationAction); if (representation == null || counter.getOrDefault(representation, 0) == 1) { result.add(conversationAction); } } return result; } @Nullable private static Pair<String, String> getRepresentation( ConversationAction conversationAction) { RemoteAction remoteAction = conversationAction.getAction(); if (remoteAction == null) { return null; return null; } } return new Pair<>( conversationAction.getAction().getTitle().toString(), ExtrasUtils.getActionIntent( conversationAction.getExtras()).getComponent().getPackageName()); } private static final class PersonEncoder { private static final class PersonEncoder { private final Map<Person, Integer> mMapping = new ArrayMap<>(); private final Map<Person, Integer> mMapping = new ArrayMap<>(); Loading core/java/android/view/textclassifier/ExtrasUtils.java +17 −0 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,7 @@ import java.util.ArrayList; */ */ public final class ExtrasUtils { public final class ExtrasUtils { private static final String ACTION_INTENT = "action-intent"; private static final String ACTIONS_INTENTS = "actions-intents"; private static final String ACTIONS_INTENTS = "actions-intents"; private static final String FOREIGN_LANGUAGE = "foreign-language"; private static final String FOREIGN_LANGUAGE = "foreign-language"; private static final String ENTITY_TYPE = "entity-type"; private static final String ENTITY_TYPE = "entity-type"; Loading Loading @@ -76,6 +77,22 @@ public final class ExtrasUtils { container.putParcelableArrayList(ACTIONS_INTENTS, actionsIntents); container.putParcelableArrayList(ACTIONS_INTENTS, actionsIntents); } } /** * Stores {@code actionIntents} information in TextClassifier response object's extras * {@code container}. */ public static void putActionIntent(Bundle container, @Nullable Intent actionIntent) { container.putParcelable(ACTION_INTENT, actionIntent); } /** * Returns {@code actionIntent} information contained in a TextClassifier response object. */ @Nullable public static Intent getActionIntent(Bundle container) { return container.getParcelable(ACTION_INTENT); } /** /** * Returns {@code actionIntents} information contained in the TextClassification object. * Returns {@code actionIntents} information contained in the TextClassification object. */ */ Loading core/java/android/view/textclassifier/LabeledIntent.java +15 −10 Original line number Original line Diff line number Diff line Loading @@ -91,15 +91,22 @@ public final class LabeledIntent { Context context, @Nullable TitleChooser titleChooser) { Context context, @Nullable TitleChooser titleChooser) { final PackageManager pm = context.getPackageManager(); final PackageManager pm = context.getPackageManager(); final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); final String packageName = resolveInfo != null && resolveInfo.activityInfo != null ? resolveInfo.activityInfo.packageName : null; if (resolveInfo == null || resolveInfo.activityInfo == null) { Icon icon = null; Log.w(TAG, "resolveInfo or activityInfo is null"); return null; } final String packageName = resolveInfo.activityInfo.packageName; final String className = resolveInfo.activityInfo.name; if (packageName == null || className == null) { Log.w(TAG, "packageName or className is null"); return null; } Intent resolvedIntent = new Intent(intent); Intent resolvedIntent = new Intent(intent); resolvedIntent.setComponent(new ComponentName(packageName, className)); boolean shouldShowIcon = false; boolean shouldShowIcon = false; if (packageName != null && !"android".equals(packageName)) { Icon icon = null; // There is a default activity handling the intent. if (!"android".equals(packageName)) { resolvedIntent.setComponent( new ComponentName(packageName, resolveInfo.activityInfo.name)); if (resolveInfo.activityInfo.getIconResource() != 0) { if (resolveInfo.activityInfo.getIconResource() != 0) { icon = Icon.createWithResource( icon = Icon.createWithResource( packageName, resolveInfo.activityInfo.getIconResource()); packageName, resolveInfo.activityInfo.getIconResource()); Loading @@ -113,9 +120,6 @@ public final class LabeledIntent { } } final PendingIntent pendingIntent = final PendingIntent pendingIntent = TextClassification.createPendingIntent(context, resolvedIntent, requestCode); TextClassification.createPendingIntent(context, resolvedIntent, requestCode); if (pendingIntent == null) { return null; } if (titleChooser == null) { if (titleChooser == null) { titleChooser = DEFAULT_TITLE_CHOOSER; titleChooser = DEFAULT_TITLE_CHOOSER; } } Loading Loading @@ -150,6 +154,7 @@ public final class LabeledIntent { public interface TitleChooser { public interface TitleChooser { /** /** * Picks a title from a {@link LabeledIntent} by looking into resolved info. * Picks a title from a {@link LabeledIntent} by looking into resolved info. * {@code resolveInfo} is guaranteed to have a non-null {@code activityInfo}. */ */ @Nullable @Nullable CharSequence chooseTitle(LabeledIntent labeledIntent, ResolveInfo resolveInfo); CharSequence chooseTitle(LabeledIntent labeledIntent, ResolveInfo resolveInfo); Loading core/java/android/view/textclassifier/TextClassification.java +2 −47 Original line number Original line Diff line number Diff line Loading @@ -25,8 +25,6 @@ import android.app.PendingIntent; import android.app.RemoteAction; import android.app.RemoteAction; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.Resources; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.AdaptiveIconDrawable; Loading Loading @@ -304,53 +302,10 @@ public final class TextClassification implements Parcelable { * @throws IllegalArgumentException if context or intent is null * @throws IllegalArgumentException if context or intent is null * @hide * @hide */ */ @Nullable public static PendingIntent createPendingIntent( public static PendingIntent createPendingIntent( @NonNull final Context context, @NonNull final Intent intent, int requestCode) { @NonNull final Context context, @NonNull final Intent intent, int requestCode) { final int flags = PendingIntent.FLAG_UPDATE_CURRENT; return PendingIntent.getActivity( switch (getIntentType(intent, context)) { context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); case IntentType.ACTIVITY: return PendingIntent.getActivity(context, requestCode, intent, flags); case IntentType.SERVICE: return PendingIntent.getService(context, requestCode, intent, flags); default: return null; } } @IntentType private static int getIntentType(@NonNull Intent intent, @NonNull Context context) { Preconditions.checkArgument(context != null); Preconditions.checkArgument(intent != null); final ResolveInfo activityRI = context.getPackageManager().resolveActivity(intent, 0); if (activityRI != null) { if (context.getPackageName().equals(activityRI.activityInfo.packageName)) { return IntentType.ACTIVITY; } final boolean exported = activityRI.activityInfo.exported; if (exported && hasPermission(context, activityRI.activityInfo.permission)) { return IntentType.ACTIVITY; } } final ResolveInfo serviceRI = context.getPackageManager().resolveService(intent, 0); if (serviceRI != null) { if (context.getPackageName().equals(serviceRI.serviceInfo.packageName)) { return IntentType.SERVICE; } final boolean exported = serviceRI.serviceInfo.exported; if (exported && hasPermission(context, serviceRI.serviceInfo.permission)) { return IntentType.SERVICE; } } return IntentType.UNSUPPORTED; } private static boolean hasPermission(@NonNull Context context, @NonNull String permission) { return permission == null || context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; } } /** /** Loading core/java/android/view/textclassifier/TextClassifierImpl.java +11 −0 Original line number Original line Diff line number Diff line Loading @@ -403,6 +403,12 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); return mFallback.suggestConversationActions(request); } } /** * Returns the {@link ConversationAction} result, with a non-null extras. * <p> * Whenever the RemoteAction is non-null, you can expect its corresponding intent * with a non-null component name is in the extras. */ private ConversationActions createConversationActionResult( private ConversationActions createConversationActionResult( ConversationActions.Request request, ConversationActions.Request request, ActionsSuggestionsModel.ActionSuggestion[] nativeSuggestions) { ActionsSuggestionsModel.ActionSuggestion[] nativeSuggestions) { Loading @@ -419,6 +425,7 @@ public final class TextClassifierImpl implements TextClassifier { } } List<LabeledIntent> labeledIntents = List<LabeledIntent> labeledIntents = mTemplateIntentFactory.create(nativeSuggestion.getRemoteActionTemplates()); mTemplateIntentFactory.create(nativeSuggestion.getRemoteActionTemplates()); Bundle extras = new Bundle(); RemoteAction remoteAction = null; RemoteAction remoteAction = null; // Given that we only support implicit intent here, we should expect there is just one // Given that we only support implicit intent here, we should expect there is just one // intent for each action type. // intent for each action type. Loading @@ -428,6 +435,7 @@ public final class TextClassifierImpl implements TextClassifier { LabeledIntent.Result result = labeledIntents.get(0).resolve(mContext, titleChooser); LabeledIntent.Result result = labeledIntents.get(0).resolve(mContext, titleChooser); if (result != null) { if (result != null) { remoteAction = result.remoteAction; remoteAction = result.remoteAction; ExtrasUtils.putActionIntent(extras, result.resolvedIntent); } } } } conversationActions.add( conversationActions.add( Loading @@ -435,8 +443,11 @@ public final class TextClassifierImpl implements TextClassifier { .setConfidenceScore(nativeSuggestion.getScore()) .setConfidenceScore(nativeSuggestion.getScore()) .setTextReply(nativeSuggestion.getResponseText()) .setTextReply(nativeSuggestion.getResponseText()) .setAction(remoteAction) .setAction(remoteAction) .setExtras(extras) .build()); .build()); } } conversationActions = ActionsSuggestionsHelper.removeActionsWithDuplicates(conversationActions); String resultId = ActionsSuggestionsHelper.createResultId( String resultId = ActionsSuggestionsHelper.createResultId( mContext, mContext, request.getConversation(), request.getConversation(), Loading Loading
core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +52 −2 Original line number Original line Diff line number Diff line Loading @@ -18,9 +18,11 @@ package android.view.textclassifier; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.Person; import android.app.Person; import android.app.RemoteAction; import android.content.Context; import android.content.Context; import android.text.TextUtils; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -118,11 +120,59 @@ public final class ActionsSuggestionsHelper { @Nullable @Nullable public static LabeledIntent.TitleChooser createTitleChooser(String actionType) { public static LabeledIntent.TitleChooser createTitleChooser(String actionType) { if (ConversationAction.TYPE_OPEN_URL.equals(actionType)) { if (ConversationAction.TYPE_OPEN_URL.equals(actionType)) { return (labeledIntent, resolveInfo) -> resolveInfo.handleAllWebDataURI return (labeledIntent, resolveInfo) -> { ? labeledIntent.titleWithEntity : labeledIntent.titleWithoutEntity; if (resolveInfo.handleAllWebDataURI) { return labeledIntent.titleWithEntity; } } if ("android".equals(resolveInfo.activityInfo.packageName)) { return labeledIntent.titleWithEntity; } return labeledIntent.titleWithoutEntity; }; } return null; } /** * Returns a list of {@link ConversationAction}s that have 0 duplicates. Two actions are * duplicates if they may look the same to users. This function assumes every * ConversationActions with a non-null RemoteAction also have a non-null intent in the extras. */ public static List<ConversationAction> removeActionsWithDuplicates( List<ConversationAction> conversationActions) { // Ideally, we should compare title and icon here, but comparing icon is expensive and thus // we use the component name of the target handler as the heuristic. Map<Pair<String, String>, Integer> counter = new ArrayMap<>(); for (ConversationAction conversationAction : conversationActions) { Pair<String, String> representation = getRepresentation(conversationAction); if (representation == null) { continue; } Integer existingCount = counter.getOrDefault(representation, 0); counter.put(representation, existingCount + 1); } List<ConversationAction> result = new ArrayList<>(); for (ConversationAction conversationAction : conversationActions) { Pair<String, String> representation = getRepresentation(conversationAction); if (representation == null || counter.getOrDefault(representation, 0) == 1) { result.add(conversationAction); } } return result; } @Nullable private static Pair<String, String> getRepresentation( ConversationAction conversationAction) { RemoteAction remoteAction = conversationAction.getAction(); if (remoteAction == null) { return null; return null; } } return new Pair<>( conversationAction.getAction().getTitle().toString(), ExtrasUtils.getActionIntent( conversationAction.getExtras()).getComponent().getPackageName()); } private static final class PersonEncoder { private static final class PersonEncoder { private final Map<Person, Integer> mMapping = new ArrayMap<>(); private final Map<Person, Integer> mMapping = new ArrayMap<>(); Loading
core/java/android/view/textclassifier/ExtrasUtils.java +17 −0 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,7 @@ import java.util.ArrayList; */ */ public final class ExtrasUtils { public final class ExtrasUtils { private static final String ACTION_INTENT = "action-intent"; private static final String ACTIONS_INTENTS = "actions-intents"; private static final String ACTIONS_INTENTS = "actions-intents"; private static final String FOREIGN_LANGUAGE = "foreign-language"; private static final String FOREIGN_LANGUAGE = "foreign-language"; private static final String ENTITY_TYPE = "entity-type"; private static final String ENTITY_TYPE = "entity-type"; Loading Loading @@ -76,6 +77,22 @@ public final class ExtrasUtils { container.putParcelableArrayList(ACTIONS_INTENTS, actionsIntents); container.putParcelableArrayList(ACTIONS_INTENTS, actionsIntents); } } /** * Stores {@code actionIntents} information in TextClassifier response object's extras * {@code container}. */ public static void putActionIntent(Bundle container, @Nullable Intent actionIntent) { container.putParcelable(ACTION_INTENT, actionIntent); } /** * Returns {@code actionIntent} information contained in a TextClassifier response object. */ @Nullable public static Intent getActionIntent(Bundle container) { return container.getParcelable(ACTION_INTENT); } /** /** * Returns {@code actionIntents} information contained in the TextClassification object. * Returns {@code actionIntents} information contained in the TextClassification object. */ */ Loading
core/java/android/view/textclassifier/LabeledIntent.java +15 −10 Original line number Original line Diff line number Diff line Loading @@ -91,15 +91,22 @@ public final class LabeledIntent { Context context, @Nullable TitleChooser titleChooser) { Context context, @Nullable TitleChooser titleChooser) { final PackageManager pm = context.getPackageManager(); final PackageManager pm = context.getPackageManager(); final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); final String packageName = resolveInfo != null && resolveInfo.activityInfo != null ? resolveInfo.activityInfo.packageName : null; if (resolveInfo == null || resolveInfo.activityInfo == null) { Icon icon = null; Log.w(TAG, "resolveInfo or activityInfo is null"); return null; } final String packageName = resolveInfo.activityInfo.packageName; final String className = resolveInfo.activityInfo.name; if (packageName == null || className == null) { Log.w(TAG, "packageName or className is null"); return null; } Intent resolvedIntent = new Intent(intent); Intent resolvedIntent = new Intent(intent); resolvedIntent.setComponent(new ComponentName(packageName, className)); boolean shouldShowIcon = false; boolean shouldShowIcon = false; if (packageName != null && !"android".equals(packageName)) { Icon icon = null; // There is a default activity handling the intent. if (!"android".equals(packageName)) { resolvedIntent.setComponent( new ComponentName(packageName, resolveInfo.activityInfo.name)); if (resolveInfo.activityInfo.getIconResource() != 0) { if (resolveInfo.activityInfo.getIconResource() != 0) { icon = Icon.createWithResource( icon = Icon.createWithResource( packageName, resolveInfo.activityInfo.getIconResource()); packageName, resolveInfo.activityInfo.getIconResource()); Loading @@ -113,9 +120,6 @@ public final class LabeledIntent { } } final PendingIntent pendingIntent = final PendingIntent pendingIntent = TextClassification.createPendingIntent(context, resolvedIntent, requestCode); TextClassification.createPendingIntent(context, resolvedIntent, requestCode); if (pendingIntent == null) { return null; } if (titleChooser == null) { if (titleChooser == null) { titleChooser = DEFAULT_TITLE_CHOOSER; titleChooser = DEFAULT_TITLE_CHOOSER; } } Loading Loading @@ -150,6 +154,7 @@ public final class LabeledIntent { public interface TitleChooser { public interface TitleChooser { /** /** * Picks a title from a {@link LabeledIntent} by looking into resolved info. * Picks a title from a {@link LabeledIntent} by looking into resolved info. * {@code resolveInfo} is guaranteed to have a non-null {@code activityInfo}. */ */ @Nullable @Nullable CharSequence chooseTitle(LabeledIntent labeledIntent, ResolveInfo resolveInfo); CharSequence chooseTitle(LabeledIntent labeledIntent, ResolveInfo resolveInfo); Loading
core/java/android/view/textclassifier/TextClassification.java +2 −47 Original line number Original line Diff line number Diff line Loading @@ -25,8 +25,6 @@ import android.app.PendingIntent; import android.app.RemoteAction; import android.app.RemoteAction; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.Resources; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.AdaptiveIconDrawable; Loading Loading @@ -304,53 +302,10 @@ public final class TextClassification implements Parcelable { * @throws IllegalArgumentException if context or intent is null * @throws IllegalArgumentException if context or intent is null * @hide * @hide */ */ @Nullable public static PendingIntent createPendingIntent( public static PendingIntent createPendingIntent( @NonNull final Context context, @NonNull final Intent intent, int requestCode) { @NonNull final Context context, @NonNull final Intent intent, int requestCode) { final int flags = PendingIntent.FLAG_UPDATE_CURRENT; return PendingIntent.getActivity( switch (getIntentType(intent, context)) { context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); case IntentType.ACTIVITY: return PendingIntent.getActivity(context, requestCode, intent, flags); case IntentType.SERVICE: return PendingIntent.getService(context, requestCode, intent, flags); default: return null; } } @IntentType private static int getIntentType(@NonNull Intent intent, @NonNull Context context) { Preconditions.checkArgument(context != null); Preconditions.checkArgument(intent != null); final ResolveInfo activityRI = context.getPackageManager().resolveActivity(intent, 0); if (activityRI != null) { if (context.getPackageName().equals(activityRI.activityInfo.packageName)) { return IntentType.ACTIVITY; } final boolean exported = activityRI.activityInfo.exported; if (exported && hasPermission(context, activityRI.activityInfo.permission)) { return IntentType.ACTIVITY; } } final ResolveInfo serviceRI = context.getPackageManager().resolveService(intent, 0); if (serviceRI != null) { if (context.getPackageName().equals(serviceRI.serviceInfo.packageName)) { return IntentType.SERVICE; } final boolean exported = serviceRI.serviceInfo.exported; if (exported && hasPermission(context, serviceRI.serviceInfo.permission)) { return IntentType.SERVICE; } } return IntentType.UNSUPPORTED; } private static boolean hasPermission(@NonNull Context context, @NonNull String permission) { return permission == null || context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; } } /** /** Loading
core/java/android/view/textclassifier/TextClassifierImpl.java +11 −0 Original line number Original line Diff line number Diff line Loading @@ -403,6 +403,12 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); return mFallback.suggestConversationActions(request); } } /** * Returns the {@link ConversationAction} result, with a non-null extras. * <p> * Whenever the RemoteAction is non-null, you can expect its corresponding intent * with a non-null component name is in the extras. */ private ConversationActions createConversationActionResult( private ConversationActions createConversationActionResult( ConversationActions.Request request, ConversationActions.Request request, ActionsSuggestionsModel.ActionSuggestion[] nativeSuggestions) { ActionsSuggestionsModel.ActionSuggestion[] nativeSuggestions) { Loading @@ -419,6 +425,7 @@ public final class TextClassifierImpl implements TextClassifier { } } List<LabeledIntent> labeledIntents = List<LabeledIntent> labeledIntents = mTemplateIntentFactory.create(nativeSuggestion.getRemoteActionTemplates()); mTemplateIntentFactory.create(nativeSuggestion.getRemoteActionTemplates()); Bundle extras = new Bundle(); RemoteAction remoteAction = null; RemoteAction remoteAction = null; // Given that we only support implicit intent here, we should expect there is just one // Given that we only support implicit intent here, we should expect there is just one // intent for each action type. // intent for each action type. Loading @@ -428,6 +435,7 @@ public final class TextClassifierImpl implements TextClassifier { LabeledIntent.Result result = labeledIntents.get(0).resolve(mContext, titleChooser); LabeledIntent.Result result = labeledIntents.get(0).resolve(mContext, titleChooser); if (result != null) { if (result != null) { remoteAction = result.remoteAction; remoteAction = result.remoteAction; ExtrasUtils.putActionIntent(extras, result.resolvedIntent); } } } } conversationActions.add( conversationActions.add( Loading @@ -435,8 +443,11 @@ public final class TextClassifierImpl implements TextClassifier { .setConfidenceScore(nativeSuggestion.getScore()) .setConfidenceScore(nativeSuggestion.getScore()) .setTextReply(nativeSuggestion.getResponseText()) .setTextReply(nativeSuggestion.getResponseText()) .setAction(remoteAction) .setAction(remoteAction) .setExtras(extras) .build()); .build()); } } conversationActions = ActionsSuggestionsHelper.removeActionsWithDuplicates(conversationActions); String resultId = ActionsSuggestionsHelper.createResultId( String resultId = ActionsSuggestionsHelper.createResultId( mContext, mContext, request.getConversation(), request.getConversation(), Loading