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

Commit 1fc998b0 authored by Richard Ledley's avatar Richard Ledley
Browse files

Replace Entity Presets with hints and an explicit factory.

This remove APIs to inspect the list of entities a TextClassifier will choose. We decided the developer should have no need to see these, and by not exposing it we allow more flexibility on the TextClassifier.

Test: bit FrameworksCoreTests:android.widget.TextViewActivityTest CtsViewTestCases:android.view.textclassifier.cts.TextClassificationManagerTest FrameworksCoreTests:android.view.textclassifier.TextLinksTest

Bug: 67629726

Change-Id: I70978e692bb8a1edee1567a10c31d5ded44baa49
parent 1d84b173
Loading
Loading
Loading
Loading
+7 −8
Original line number Diff line number Diff line
@@ -50311,15 +50311,13 @@ package android.view.textclassifier {
    method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
    method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
    method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence);
    method public default java.util.Collection<java.lang.String> getEntitiesForPreset(int);
    method public default android.view.textclassifier.logging.Logger getLogger(android.view.textclassifier.logging.Logger.Config);
    method public default int getMaxGenerateLinksTextLength();
    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
    method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
    field public static final int ENTITY_PRESET_ALL = 0; // 0x0
    field public static final int ENTITY_PRESET_BASE = 2; // 0x2
    field public static final int ENTITY_PRESET_NONE = 1; // 0x1
    field public static final java.lang.String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
    field public static final java.lang.String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable";
    field public static final android.view.textclassifier.TextClassifier NO_OP;
    field public static final java.lang.String TYPE_ADDRESS = "address";
    field public static final java.lang.String TYPE_DATE = "date";
@@ -50333,11 +50331,12 @@ package android.view.textclassifier {
  }
  public static final class TextClassifier.EntityConfig implements android.os.Parcelable {
    ctor public TextClassifier.EntityConfig(int);
    method public static android.view.textclassifier.TextClassifier.EntityConfig create(java.util.Collection<java.lang.String>);
    method public static android.view.textclassifier.TextClassifier.EntityConfig create(java.util.Collection<java.lang.String>, java.util.Collection<java.lang.String>, java.util.Collection<java.lang.String>);
    method public static android.view.textclassifier.TextClassifier.EntityConfig createWithEntityList(java.util.Collection<java.lang.String>);
    method public int describeContents();
    method public android.view.textclassifier.TextClassifier.EntityConfig excludeEntities(java.lang.String...);
    method public java.util.List<java.lang.String> getEntities(android.view.textclassifier.TextClassifier);
    method public android.view.textclassifier.TextClassifier.EntityConfig includeEntities(java.lang.String...);
    method public java.util.Collection<java.lang.String> getHints();
    method public java.util.List<java.lang.String> resolveEntityListModifications(java.util.Collection<java.lang.String>);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifier.EntityConfig> CREATOR;
  }
+74 −50
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package android.view.textclassifier;

import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -86,19 +85,15 @@ public interface TextClassifier {
    })
    @interface EntityType {}

    /** Designates that the TextClassifier should identify all entity types it can. **/
    int ENTITY_PRESET_ALL = 0;
    /** Designates that the TextClassifier should identify no entities. **/
    int ENTITY_PRESET_NONE = 1;
    /** Designates that the TextClassifier should identify a base set of entities determined by the
     * TextClassifier. **/
    int ENTITY_PRESET_BASE = 2;
    /** Designates that the text in question is editable. **/
    String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
    /** Designates that the text in question is not editable. **/
    String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable";

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "ENTITY_CONFIG_" },
            value = {ENTITY_PRESET_ALL, ENTITY_PRESET_NONE, ENTITY_PRESET_BASE})
    @interface EntityPreset {}
    @StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE})
    @interface Hints {}

    /**
     * No-op TextClassifier.
@@ -322,17 +317,6 @@ public interface TextClassifier {
        return Integer.MAX_VALUE;
    }

    /**
     * Returns a {@link Collection} of the entity types in the specified preset.
     *
     * @see #ENTITY_PRESET_ALL
     * @see #ENTITY_PRESET_NONE
     * @see #ENTITY_PRESET_BASE
     */
    default Collection<String> getEntitiesForPreset(@EntityPreset int entityPreset) {
        return Collections.EMPTY_LIST;
    }

    /**
     * Returns a helper for logging TextClassifier related events.
     *
@@ -358,54 +342,92 @@ public interface TextClassifier {
     * Configs are initially based on a predefined preset, and can be modified from there.
     */
    final class EntityConfig implements Parcelable {
        private final @TextClassifier.EntityPreset int mEntityPreset;
        private final Collection<String> mHints;
        private final Collection<String> mExcludedEntityTypes;
        private final Collection<String> mIncludedEntityTypes;
        private final boolean mUseHints;

        public EntityConfig(@TextClassifier.EntityPreset int mEntityPreset) {
            this.mEntityPreset = mEntityPreset;
            mExcludedEntityTypes = new ArraySet<>();
            mIncludedEntityTypes = new ArraySet<>();
        private EntityConfig(boolean useHints, Collection<String> hints,
                Collection<String> includedEntityTypes, Collection<String> excludedEntityTypes) {
            mHints = hints == null
                    ? Collections.EMPTY_LIST
                    : Collections.unmodifiableCollection(new ArraySet<>(hints));
            mExcludedEntityTypes = excludedEntityTypes == null
                    ? Collections.EMPTY_LIST : new ArraySet<>(excludedEntityTypes);
            mIncludedEntityTypes = includedEntityTypes == null
                    ? Collections.EMPTY_LIST : new ArraySet<>(includedEntityTypes);
            mUseHints = useHints;
        }

        /**
         * Specifies an entity to include in addition to any specified by the enity preset.
         * Creates an EntityConfig.
         *
         * Note that if an entity has been excluded, the exclusion will take precedence.
         * @param hints Hints for the TextClassifier to determine what types of entities to find.
         */
        public EntityConfig includeEntities(String... entities) {
            for (String entity : entities) {
                mIncludedEntityTypes.add(entity);
            }
            return this;
        public static EntityConfig create(@Nullable Collection<String> hints) {
            return new EntityConfig(/* useHints */ true, hints,
                    /* includedEntityTypes */null, /* excludedEntityTypes */ null);
        }

        /**
         * Specifies an entity to be excluded.
         * Creates an EntityConfig.
         *
         * @param hints Hints for the TextClassifier to determine what types of entities to find
         * @param includedEntityTypes Entity types, e.g. {@link #TYPE_EMAIL}, to explicitly include
         * @param excludedEntityTypes Entity types, e.g. {@link #TYPE_PHONE}, to explicitly exclude
         *
         *
         * Note that if an entity has been excluded, the exclusion will take precedence.
         */
        public EntityConfig excludeEntities(String... entities) {
            for (String entity : entities) {
                mExcludedEntityTypes.add(entity);
        public static EntityConfig create(@Nullable Collection<String> hints,
                @Nullable Collection<String> includedEntityTypes,
                @Nullable Collection<String> excludedEntityTypes) {
            return new EntityConfig(/* useHints */ true, hints,
                    includedEntityTypes, excludedEntityTypes);
        }
            return this;

        /**
         * Creates an EntityConfig with an explicit entity list.
         *
         * @param entityTypes Complete set of entities, e.g. {@link #TYPE_URL} to find.
         *
         */
        public static EntityConfig createWithEntityList(@Nullable Collection<String> entityTypes) {
            return new EntityConfig(/* useHints */ false, /* hints */ null,
                    /* includedEntityTypes */ entityTypes, /* excludedEntityTypes */ null);
        }

        /**
         * Returns an unmodifiable list of the final set of entities to find.
         * Returns a list of the final set of entities to find.
         *
         * @param entities Entities we think should be found before factoring in includes/excludes
         *
         * This method is intended for use by TextClassifier implementations.
         */
        public List<String> getEntities(TextClassifier textClassifier) {
            ArrayList<String> entities = new ArrayList<>();
            for (String entity : textClassifier.getEntitiesForPreset(mEntityPreset)) {
        public List<String> resolveEntityListModifications(@NonNull Collection<String> entities) {
            final ArrayList<String> finalList = new ArrayList<>();
            if (mUseHints) {
                for (String entity : entities) {
                    if (!mExcludedEntityTypes.contains(entity)) {
                    entities.add(entity);
                        finalList.add(entity);
                    }
                }
            }
            for (String entity : mIncludedEntityTypes) {
                if (!mExcludedEntityTypes.contains(entity) && !entities.contains(entity)) {
                    entities.add(entity);
                if (!mExcludedEntityTypes.contains(entity) && !finalList.contains(entity)) {
                    finalList.add(entity);
                }
            }
            return finalList;
        }
            return Collections.unmodifiableList(entities);

        /**
         * Retrieves the list of hints.
         *
         * @return An unmodifiable collection of the hints.
         */
        public Collection<String> getHints() {
            return mHints;
        }

        @Override
@@ -415,9 +437,10 @@ public interface TextClassifier {

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(mEntityPreset);
            dest.writeStringList(new ArrayList<>(mHints));
            dest.writeStringList(new ArrayList<>(mExcludedEntityTypes));
            dest.writeStringList(new ArrayList<>(mIncludedEntityTypes));
            dest.writeInt(mUseHints ? 1 : 0);
        }

        public static final Parcelable.Creator<EntityConfig> CREATOR =
@@ -434,9 +457,10 @@ public interface TextClassifier {
                };

        private EntityConfig(Parcel in) {
            mEntityPreset = in.readInt();
            mHints = new ArraySet<>(in.createStringArrayList());
            mExcludedEntityTypes = new ArraySet<>(in.createStringArrayList());
            mIncludedEntityTypes = new ArraySet<>(in.createStringArrayList());
            mUseHints = in.readInt() == 1;
        }
    }

+5 −19
Original line number Diff line number Diff line
@@ -88,12 +88,6 @@ public final class TextClassifierImpl implements TextClassifier {
                    TextClassifier.TYPE_DATE,
                    TextClassifier.TYPE_DATE_TIME,
                    TextClassifier.TYPE_FLIGHT_NUMBER));
    private static final List<String> ENTITY_TYPES_BASE =
            Collections.unmodifiableList(Arrays.asList(
                    TextClassifier.TYPE_ADDRESS,
                    TextClassifier.TYPE_EMAIL,
                    TextClassifier.TYPE_PHONE,
                    TextClassifier.TYPE_URL));

    private final Context mContext;
    private final TextClassifier mFallback;
@@ -222,7 +216,9 @@ public final class TextClassifierImpl implements TextClassifier {
            final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
            final Collection<String> entitiesToIdentify =
                    options != null && options.getEntityConfig() != null
                            ? options.getEntityConfig().getEntities(this) : ENTITY_TYPES_ALL;
                            ? options.getEntityConfig().resolveEntityListModifications(
                                    getEntitiesForHints(options.getEntityConfig().getHints()))
                            : ENTITY_TYPES_ALL;
            final SmartSelection smartSelection = getSmartSelection(defaultLocales);
            final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString);
            for (SmartSelection.AnnotatedSpan span : annotations) {
@@ -250,19 +246,9 @@ public final class TextClassifierImpl implements TextClassifier {
        return getSettings().getGenerateLinksMaxTextLength();
    }

    @Override
    public Collection<String> getEntitiesForPreset(@TextClassifier.EntityPreset int entityPreset) {
        switch (entityPreset) {
            case TextClassifier.ENTITY_PRESET_NONE:
                return Collections.emptyList();
            case TextClassifier.ENTITY_PRESET_BASE:
                return ENTITY_TYPES_BASE;
            case TextClassifier.ENTITY_PRESET_ALL:
                // fall through
            default:
    private Collection<String> getEntitiesForHints(Collection<String> hints) {
        return ENTITY_TYPES_ALL;
    }
    }

    @Override
    public Logger getLogger(@NonNull Logger.Config config) {
+7 −7
Original line number Diff line number Diff line
@@ -309,23 +309,23 @@ public final class TextLinks implements Parcelable {
         * Returns a new options object based on the specified link mask.
         */
        public static Options fromLinkMask(@LinkifyMask int mask) {
            final TextClassifier.EntityConfig entityConfig =
                    new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE);
            final List<String> entitiesToFind = new ArrayList<>();

            if ((mask & Linkify.WEB_URLS) != 0) {
                entityConfig.includeEntities(TextClassifier.TYPE_URL);
                entitiesToFind.add(TextClassifier.TYPE_URL);
            }
            if ((mask & Linkify.EMAIL_ADDRESSES) != 0) {
                entityConfig.includeEntities(TextClassifier.TYPE_EMAIL);
                entitiesToFind.add(TextClassifier.TYPE_EMAIL);
            }
            if ((mask & Linkify.PHONE_NUMBERS) != 0) {
                entityConfig.includeEntities(TextClassifier.TYPE_PHONE);
                entitiesToFind.add(TextClassifier.TYPE_PHONE);
            }
            if ((mask & Linkify.MAP_ADDRESSES) != 0) {
                entityConfig.includeEntities(TextClassifier.TYPE_ADDRESS);
                entitiesToFind.add(TextClassifier.TYPE_ADDRESS);
            }

            return new Options().setEntityConfig(entityConfig);
            return new Options().setEntityConfig(
                    TextClassifier.EntityConfig.createWithEntityList(entitiesToFind));
        }

        public Options() {}
+13 −20
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Arrays;
import java.util.Collections;

@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -178,39 +179,31 @@ public class TextClassificationManagerTest {
        if (isTextClassifierDisabled()) return;
        String text = "The number is +12122537077. See you tonight!";
        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
                new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_ALL)
                        .excludeEntities(TextClassifier.TYPE_PHONE))),
                TextClassifier.EntityConfig.create(Collections.EMPTY_LIST,
                        Collections.EMPTY_LIST, Arrays.asList(TextClassifier.TYPE_PHONE)))),
                not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
    }

    @Test
    public void testGenerateLinks_none_config() {
        if (isTextClassifierDisabled()) return;

        String text = "The number is +12122537077. See you tonight!";
        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
                new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE))),
                not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
    }

    @Test
    public void testGenerateLinks_address() {
    public void testGenerateLinks_explicit_address() {
        if (isTextClassifierDisabled()) return;
        String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
        assertThat(mClassifier.generateLinks(text, null),
        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
                TextClassifier.EntityConfig.createWithEntityList(
                        Arrays.asList(TextClassifier.TYPE_ADDRESS)))),
                isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
                        TextClassifier.TYPE_ADDRESS));
    }

    @Test
    public void testGenerateLinks_include() {
    public void testGenerateLinks_exclude_override() {
        if (isTextClassifierDisabled()) return;
        String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
        String text = "The number is +12122537077. See you tonight!";
        assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
                new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE)
                        .includeEntities(TextClassifier.TYPE_ADDRESS))),
                isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
                        TextClassifier.TYPE_ADDRESS));
                TextClassifier.EntityConfig.create(Collections.EMPTY_LIST,
                        Arrays.asList(TextClassifier.TYPE_PHONE),
                        Arrays.asList(TextClassifier.TYPE_PHONE)))),
                not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
    }

    @Test
Loading