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);
}
}