TextClassifier: Support service intents.

Previously, the TextClassifier only supported Activity intents.

Test: bit FrameworksCoreTests:android.view.textclassifier.TextClassificationManagerTest
Test: bit FrameworksCoreTests:android.widget.TextViewActivityTest
Change-Id: Ic488e2f6241eb91a6cd6e16d9f84a49a679164dc
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 8fe1d8f..c91116a 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -17,11 +17,14 @@
 package android.view.textclassifier;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -36,6 +39,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.List;
@@ -81,7 +86,7 @@
  *           // Add the "secondary" actions.
  *           for (int i = 0; i < classification.getSecondaryActionsCount(); i++) {
  *               if (thisAppHasPermissionToInvokeIntent(classification.getSecondaryIntent(i))) {
- *                   menu.add(Menu.NONE, i + 1, 20, classification.getSecondaryLabel(i))
+ *                  menu.add(Menu.NONE, i + 1, 20, classification.getSecondaryLabel(i))
  *                      .setIcon(classification.getSecondaryIcon(i))
  *                      .setIntent(classification.getSecondaryIntent(i));
  *               }
@@ -109,6 +114,14 @@
     private static final int MAX_PRIMARY_ICON_SIZE = 192;
     private static final int MAX_SECONDARY_ICON_SIZE = 144;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {IntentType.UNSUPPORTED, IntentType.ACTIVITY, IntentType.SERVICE})
+    private @interface IntentType {
+        int UNSUPPORTED = -1;
+        int ACTIVITY = 0;
+        int SERVICE = 1;
+    }
+
     @NonNull private final String mText;
     @Nullable private final Drawable mPrimaryIcon;
     @Nullable private final String mPrimaryLabel;
@@ -312,17 +325,58 @@
     }
 
     /**
-     * Creates an OnClickListener that starts an activity with the specified intent.
+     * Creates an OnClickListener that triggers the specified intent.
+     * Returns null if the intent is not supported for the specified context.
      *
      * @throws IllegalArgumentException if context or intent is null
      * @hide
      */
-    @NonNull
-    public static OnClickListener createStartActivityOnClickListener(
+    @Nullable
+    public static OnClickListener createIntentOnClickListener(
             @NonNull final Context context, @NonNull final Intent intent) {
+        switch (getIntentType(intent, context)) {
+            case IntentType.ACTIVITY:
+                return v -> context.startActivity(intent);
+            case IntentType.SERVICE:
+                return v -> context.startService(intent);
+            default:
+                return null;
+        }
+    }
+
+    @IntentType
+    private static int getIntentType(@NonNull Intent intent, @NonNull Context context) {
         Preconditions.checkArgument(context != null);
         Preconditions.checkArgument(intent != null);
-        return v -> context.startActivity(intent);
+
+        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;
     }
 
     /**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 9988661..892b8b6 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4020,10 +4020,11 @@
             if (textClassification == null) {
                 return;
             }
-            if (isValidAssistMenuItem(
+            final OnClickListener onClick = getSupportedOnClickListener(
                     textClassification.getIcon(),
                     textClassification.getLabel(),
-                    textClassification.getIntent())) {
+                    textClassification.getIntent());
+            if (onClick != null) {
                 final MenuItem item = menu.add(
                         TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
                         textClassification.getLabel())
@@ -4031,15 +4032,16 @@
                         .setIntent(textClassification.getIntent());
                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
                 mAssistClickHandlers.put(
-                        item, TextClassification.createStartActivityOnClickListener(
+                        item, TextClassification.createIntentOnClickListener(
                                 mTextView.getContext(), textClassification.getIntent()));
             }
             final int count = textClassification.getSecondaryActionsCount();
             for (int i = 0; i < count; i++) {
-                if (!isValidAssistMenuItem(
+                final OnClickListener onClick1 = getSupportedOnClickListener(
                         textClassification.getSecondaryIcon(i),
                         textClassification.getSecondaryLabel(i),
-                        textClassification.getSecondaryIntent(i))) {
+                        textClassification.getSecondaryIntent(i));
+                if (onClick1 == null) {
                     continue;
                 }
                 final int order = MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i;
@@ -4050,7 +4052,7 @@
                         .setIntent(textClassification.getSecondaryIntent(i));
                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                 mAssistClickHandlers.put(item,
-                        TextClassification.createStartActivityOnClickListener(
+                        TextClassification.createIntentOnClickListener(
                                 mTextView.getContext(), textClassification.getSecondaryIntent(i)));
             }
         }
@@ -4067,30 +4069,15 @@
             }
         }
 
-        private boolean isValidAssistMenuItem(Drawable icon, CharSequence label, Intent intent) {
+        @Nullable
+        private OnClickListener getSupportedOnClickListener(
+                Drawable icon, CharSequence label, Intent intent) {
             final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
-            final boolean hasAction = isSupportedIntent(intent);
-            return hasUi && hasAction;
-        }
-
-        private boolean isSupportedIntent(Intent intent) {
-            if (intent == null) {
-                return false;
+            if (hasUi) {
+                return TextClassification.createIntentOnClickListener(
+                        mTextView.getContext(), intent);
             }
-            final Context context = mTextView.getContext();
-            final ResolveInfo info = context.getPackageManager().resolveActivity(intent, 0);
-            final boolean samePackage = context.getPackageName().equals(
-                    info.activityInfo.packageName);
-            if (samePackage) {
-                return true;
-            }
-
-            final boolean exported =  info.activityInfo.exported;
-            final boolean requiresPermission = info.activityInfo.permission != null;
-            final boolean hasPermission = !requiresPermission
-                    || context.checkSelfPermission(info.activityInfo.permission)
-                            == PackageManager.PERMISSION_GRANTED;
-            return exported && hasPermission;
+            return null;
         }
 
         private boolean onAssistMenuItemClicked(MenuItem assistMenuItem) {
@@ -4107,7 +4094,7 @@
             if (onClickListener == null) {
                 final Intent intent = assistMenuItem.getIntent();
                 if (intent != null) {
-                    onClickListener = TextClassification.createStartActivityOnClickListener(
+                    onClickListener = TextClassification.createIntentOnClickListener(
                             mTextView.getContext(), intent);
                 }
             }