Deduplicate actions by their look

1. Deduplicate actions by their look. Ideally, we should compare
   their labels and icons, but comparing icon is expensive and thus
    we are comparing title + component name instead.

2. Put intent to extras in ConversationAction object

3. Updated LabeledIntent.resolve, so we only support activity intent
   handler.

4. Fixed a minor issue in the browser title chooser.
   If it is resolves to sharesheet / chooser, we will still show the URL.

BUG: 121200744
Test: atest frameworks/base/core/tests/coretests/src/android/view/textclassifier/

Change-Id: Ic7ea31eb0ac5e9386e8e4b428686a0b66726c96b
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
index efdc968..ddbff7b 100644
--- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -18,9 +18,11 @@
 
 import android.annotation.Nullable;
 import android.app.Person;
+import android.app.RemoteAction;
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -118,12 +120,60 @@
     @Nullable
     public static LabeledIntent.TitleChooser createTitleChooser(String actionType) {
         if (ConversationAction.TYPE_OPEN_URL.equals(actionType)) {
-            return (labeledIntent, resolveInfo) -> resolveInfo.handleAllWebDataURI
-                    ? labeledIntent.titleWithEntity : labeledIntent.titleWithoutEntity;
+            return (labeledIntent, resolveInfo) -> {
+                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 new Pair<>(
+                conversationAction.getAction().getTitle().toString(),
+                ExtrasUtils.getActionIntent(
+                        conversationAction.getExtras()).getComponent().getPackageName());
+    }
+
     private static final class PersonEncoder {
         private final Map<Person, Integer> mMapping = new ArrayMap<>();
         private int mNextUserId = FIRST_NON_LOCAL_USER;