Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fafdb737 authored by Abodunrinwa Toki's avatar Abodunrinwa Toki
Browse files

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
parent 580f32d7
Loading
Loading
Loading
Loading
+37 −15
Original line number Diff line number Diff line
@@ -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);
        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);
            }
        }
        return builder.build();
    }

@@ -211,11 +223,14 @@ 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));
                        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;
            }
            return new LinksInfoImpl(text, avoidOverlaps(spans, text));
@@ -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.
         */
+27 −8
Original line number Diff line number Diff line
@@ -3917,14 +3917,20 @@ 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())
            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);
                }
            }
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
@@ -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());
        }