Loading core/java/android/view/textclassifier/TextClassification.java +59 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -36,6 +39,8 @@ import android.view.textclassifier.TextClassifier.EntityType; 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; Loading Loading @@ -109,6 +114,14 @@ public final class TextClassification implements Parcelable { 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; Loading Loading @@ -312,17 +325,58 @@ public final class TextClassification implements Parcelable { } /** * 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; } /** Loading core/java/android/widget/Editor.java +16 −29 Original line number Diff line number Diff line Loading @@ -4020,10 +4020,11 @@ public class Editor { 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()) Loading @@ -4031,15 +4032,16 @@ public class Editor { .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; Loading @@ -4050,7 +4052,7 @@ public class Editor { .setIntent(textClassification.getSecondaryIntent(i)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); mAssistClickHandlers.put(item, TextClassification.createStartActivityOnClickListener( TextClassification.createIntentOnClickListener( mTextView.getContext(), textClassification.getSecondaryIntent(i))); } } Loading @@ -4067,30 +4069,15 @@ public class Editor { } } 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; } 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; if (hasUi) { return TextClassification.createIntentOnClickListener( mTextView.getContext(), intent); } 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) { Loading @@ -4107,7 +4094,7 @@ public class Editor { if (onClickListener == null) { final Intent intent = assistMenuItem.getIntent(); if (intent != null) { onClickListener = TextClassification.createStartActivityOnClickListener( onClickListener = TextClassification.createIntentOnClickListener( mTextView.getContext(), intent); } } Loading Loading
core/java/android/view/textclassifier/TextClassification.java +59 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -36,6 +39,8 @@ import android.view.textclassifier.TextClassifier.EntityType; 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; Loading Loading @@ -109,6 +114,14 @@ public final class TextClassification implements Parcelable { 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; Loading Loading @@ -312,17 +325,58 @@ public final class TextClassification implements Parcelable { } /** * 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; } /** Loading
core/java/android/widget/Editor.java +16 −29 Original line number Diff line number Diff line Loading @@ -4020,10 +4020,11 @@ public class Editor { 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()) Loading @@ -4031,15 +4032,16 @@ public class Editor { .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; Loading @@ -4050,7 +4052,7 @@ public class Editor { .setIntent(textClassification.getSecondaryIntent(i)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); mAssistClickHandlers.put(item, TextClassification.createStartActivityOnClickListener( TextClassification.createIntentOnClickListener( mTextView.getContext(), textClassification.getSecondaryIntent(i))); } } Loading @@ -4067,30 +4069,15 @@ public class Editor { } } 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; } 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; if (hasUi) { return TextClassification.createIntentOnClickListener( mTextView.getContext(), intent); } 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) { Loading @@ -4107,7 +4094,7 @@ public class Editor { if (onClickListener == null) { final Intent intent = assistMenuItem.getIntent(); if (intent != null) { onClickListener = TextClassification.createStartActivityOnClickListener( onClickListener = TextClassification.createIntentOnClickListener( mTextView.getContext(), intent); } } Loading