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
This commit is contained in:
Abodunrinwa Toki
2017-02-02 11:07:05 +00:00
parent 580f32d7b0
commit fafdb7372f
2 changed files with 64 additions and 23 deletions

View File

@@ -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.
*/

View File

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