From fafdb7372fe0da277a32b47cff825dbb244e4af0 Mon Sep 17 00:00:00 2001 From: Abodunrinwa Toki Date: Thu, 2 Feb 2017 11:07:05 +0000 Subject: [PATCH] Fix TextClassifier.getTextClassificationResult() Gracefully handles situations where a default app cannot be found to handle the intent. 1. If we can not find any app to handle the intent, do not include an "assist" menu item entry to fire the intent. Also, do not linkify the entry. 2. If we do not have a default app to handle the intent, show a generic title for apps that will handle the intent and do not include an icon. In the ideal case where we find a default app to include the intent, show the app's (preferably activity's) title and icon. Test: Manually tested. There's an AI to write more automated tests. Bug: 34777322 Bug: 34927631 Change-Id: Ia94efbbdda3da8f181fac9228cd2d3a76cb727d3 --- .../textclassifier/TextClassifierImpl.java | 52 +++++++++++++------ core/java/android/widget/Editor.java | 35 ++++++++++--- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 0657067d992f0..783891838fe09 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -18,6 +18,7 @@ package android.view.textclassifier; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -157,18 +158,29 @@ final class TextClassifierImpl implements TextClassifier { .setEntityType(type, 1.0f /* confidence */) .setIntent(intent) .setOnClickListener(TextClassificationResult.createStartActivityOnClick( - mContext, intent)) - .setLabel(IntentFactory.getLabel(mContext, type)); + mContext, intent)); final PackageManager pm = mContext.getPackageManager(); final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); - // TODO: If the resolveInfo is the "chooser", do not set the package name and use a default - // icon for this classification type. - intent.setPackage(resolveInfo.activityInfo.packageName); - Drawable icon = resolveInfo.activityInfo.loadIcon(pm); - if (icon == null) { - icon = resolveInfo.loadIcon(pm); + if (resolveInfo != null && resolveInfo.activityInfo != null) { + final String packageName = resolveInfo.activityInfo.packageName; + if ("android".equals(packageName)) { + // Requires the chooser to find an activity to handle the intent. + builder.setLabel(IntentFactory.getLabel(mContext, type)); + } else { + // A default activity will handle the intent. + intent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name)); + Drawable icon = resolveInfo.activityInfo.loadIcon(pm); + if (icon == null) { + icon = resolveInfo.loadIcon(pm); + } + builder.setIcon(icon); + CharSequence label = resolveInfo.activityInfo.loadLabel(pm); + if (label == null) { + label = resolveInfo.loadLabel(pm); + } + builder.setLabel(label != null ? label.toString() : null); + } } - builder.setIcon(icon); return builder.build(); } @@ -211,9 +223,12 @@ final class TextClassifierImpl implements TextClassifier { final String type = smartSelection.classifyText(text, selectionStart, selectionEnd); if (matches(type, linkMask)) { - final ClickableSpan span = createSpan( - context, type, text.substring(selectionStart, selectionEnd)); - spans.add(new SpanSpec(selectionStart, selectionEnd, span)); + final Intent intent = IntentFactory.create( + type, text.substring(selectionStart, selectionEnd)); + if (hasActivityHandler(context, intent)) { + final ClickableSpan span = createSpan(context, intent); + spans.add(new SpanSpec(selectionStart, selectionEnd, span)); + } } } start = end; @@ -279,17 +294,24 @@ final class TextClassifierImpl implements TextClassifier { return result; } - private static ClickableSpan createSpan( - final Context context, final String type, final String text) { + private static ClickableSpan createSpan(final Context context, final Intent intent) { return new ClickableSpan() { // TODO: Style this span. @Override public void onClick(View widget) { - context.startActivity(IntentFactory.create(type, text)); + context.startActivity(intent); } }; } + private static boolean hasActivityHandler(Context context, @Nullable Intent intent) { + if (intent == null) { + return false; + } + final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0); + return resolveInfo != null && resolveInfo.activityInfo != null; + } + /** * Implementation of LinksInfo that adds ClickableSpans to the specified text. */ diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index c1834f1b79ff0..a2cb491bbb7ad 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -3917,12 +3917,18 @@ public class Editor { private void updateAssistMenuItem( Menu menu, TextClassificationResult textClassificationResult) { menu.removeItem(TextView.ID_ASSIST); - if (textClassificationResult != null - && textClassificationResult.getIcon() != null - && textClassificationResult.getOnClickListener() != null) { - menu.add(Menu.NONE, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, null) - .setIcon(textClassificationResult.getIcon()) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + if (textClassificationResult != null) { + final Drawable icon = textClassificationResult.getIcon(); + final CharSequence label = textClassificationResult.getLabel(); + final OnClickListener onClickListener = + textClassificationResult.getOnClickListener(); + final Intent intent = textClassificationResult.getIntent(); + if ((icon != null || !TextUtils.isEmpty(label)) + && (onClickListener != null || intent != null)) { + menu.add(Menu.NONE, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, label) + .setIcon(icon) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } } } @@ -3935,9 +3941,22 @@ public class Editor { if (customCallback != null && customCallback.onActionItemClicked(mode, item)) { return true; } - if (TextView.ID_ASSIST == item.getItemId() && mTextClassificationResult != null) { - mTextClassificationResult.getOnClickListener().onClick(mTextView); + final TextClassificationResult textClassificationResult = mTextClassificationResult; + if (TextView.ID_ASSIST == item.getItemId() && textClassificationResult != null) { + final OnClickListener onClickListener = + textClassificationResult.getOnClickListener(); + if (onClickListener != null) { + onClickListener.onClick(mTextView); + } else { + final Intent intent = textClassificationResult.getIntent(); + if (intent != null) { + TextClassificationResult.createStartActivityOnClick( + mTextView.getContext(), intent) + .onClick(mTextView); + } + } stopTextActionMode(); + return true; } return mTextView.onTextContextMenuItem(item.getItemId()); }