Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0887b3d7 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Deduplicate actions by their look"

parents 5e91c8f1 c12035eb
Loading
Loading
Loading
Loading
+52 −2
Original line number Original line Diff line number Diff line
@@ -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;


@@ -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<>();
+17 −0
Original line number Original line Diff line number Diff line
@@ -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";
@@ -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.
     */
     */
+15 −10
Original line number Original line Diff line number Diff line
@@ -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());
@@ -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;
        }
        }
@@ -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);
+2 −47
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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;
    }
    }


    /**
    /**
+11 −0
Original line number Original line Diff line number Diff line
@@ -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) {
@@ -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.
@@ -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(
@@ -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