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

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

Add threshold flag for foreign language detection.

Required for feature tuning and experiments

Also
- Updates Javadoc as per API review request
- Updates TextClassificationConstants test

Bug: 120794314
Bug: 118296637
Bug: 34780395
Test: atest core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
Test: (MANUAL)
      1. Install an app that handles Intent.ACTION_TRANSLATE
      2. Run adb shell settings put global text_classifier_constants system_textclassifier_enabled=false,lang_id_threshold_override=0
      3. Select foreign text
      4. Observe that a "Translate" option is shown in the selection toolbar

      1. Install an app that handles Intent.ACTION_TRANSLATE
      2. Run adb shell settings put global text_classifier_constants system_textclassifier_enabled=false,lang_id_threshold_override=2
      3. Select foreign text
      4. Observe that a "Translate" option is not shown in the selection toolbar

Change-Id: I02b6ca48669e66a24150b04bba2ebfcf9ebe6bfd
parent c49da391
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -11722,6 +11722,7 @@ public final class Settings {
         * entity_list_default                      (String[])
         * entity_list_not_editable                 (String[])
         * entity_list_editable                     (String[])
         * lang_id_threshold_override               (float)
         * </pre>
         *
         * <p>
+29 −10
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import java.util.StringJoiner;
 * entity_list_default                      (String[])
 * entity_list_not_editable                 (String[])
 * entity_list_editable                     (String[])
 * lang_id_threshold_override               (float)
 * </pre>
 *
 * <p>
@@ -94,6 +95,8 @@ public final class TextClassificationConstants {
            "in_app_conversation_action_types_default";
    private static final String NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT =
            "notification_conversation_action_types_default";
    private static final String LANG_ID_THRESHOLD_OVERRIDE =
            "lang_id_threshold_override";

    private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
    private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
@@ -106,8 +109,8 @@ public final class TextClassificationConstants {
    private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
    private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000;
    private static final int GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT = 100;
    private static final String ENTITY_LIST_DELIMITER = ":";
    private static final String ENTITY_LIST_DEFAULT_VALUE = new StringJoiner(ENTITY_LIST_DELIMITER)
    private static final String STRING_LIST_DELIMITER = ":";
    private static final String ENTITY_LIST_DEFAULT_VALUE = new StringJoiner(STRING_LIST_DELIMITER)
            .add(TextClassifier.TYPE_ADDRESS)
            .add(TextClassifier.TYPE_EMAIL)
            .add(TextClassifier.TYPE_PHONE)
@@ -116,7 +119,7 @@ public final class TextClassificationConstants {
            .add(TextClassifier.TYPE_DATE_TIME)
            .add(TextClassifier.TYPE_FLIGHT_NUMBER).toString();
    private static final String CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES =
            new StringJoiner(ENTITY_LIST_DELIMITER)
            new StringJoiner(STRING_LIST_DELIMITER)
                    .add(ConversationAction.TYPE_TEXT_REPLY)
                    .add(ConversationAction.TYPE_CREATE_REMINDER)
                    .add(ConversationAction.TYPE_CALL_PHONE)
@@ -127,6 +130,13 @@ public final class TextClassificationConstants {
                    .add(ConversationAction.TYPE_VIEW_CALENDAR)
                    .add(ConversationAction.TYPE_VIEW_MAP)
                    .toString();
    /**
     * < 0  : Not set. Use value from LangId model.
     * 0 - 1: Override value in LangId model.
     * > 1  : Effectively turns off the foreign language detection. Scores should never be > 1.
     * @see EntityConfidence
     */
    private static final float LANG_ID_THRESHOLD_OVERRIDE_DEFAULT = -1f;

    private final boolean mSystemTextClassifierEnabled;
    private final boolean mLocalTextClassifierEnabled;
@@ -144,6 +154,7 @@ public final class TextClassificationConstants {
    private final List<String> mEntityListEditable;
    private final List<String> mInAppConversationActionTypesDefault;
    private final List<String> mNotificationConversationActionTypesDefault;
    private final float mLangIdThresholdOverride;

    private TextClassificationConstants(@Nullable String settings) {
        final KeyValueListParser parser = new KeyValueListParser(',');
@@ -186,21 +197,24 @@ public final class TextClassificationConstants {
        mGenerateLinksLogSampleRate = parser.getInt(
                GENERATE_LINKS_LOG_SAMPLE_RATE,
                GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
        mEntityListDefault = parseEntityList(parser.getString(
        mEntityListDefault = parseStringList(parser.getString(
                ENTITY_LIST_DEFAULT,
                ENTITY_LIST_DEFAULT_VALUE));
        mEntityListNotEditable = parseEntityList(parser.getString(
        mEntityListNotEditable = parseStringList(parser.getString(
                ENTITY_LIST_NOT_EDITABLE,
                ENTITY_LIST_DEFAULT_VALUE));
        mEntityListEditable = parseEntityList(parser.getString(
        mEntityListEditable = parseStringList(parser.getString(
                ENTITY_LIST_EDITABLE,
                ENTITY_LIST_DEFAULT_VALUE));
        mInAppConversationActionTypesDefault = parseEntityList(parser.getString(
        mInAppConversationActionTypesDefault = parseStringList(parser.getString(
                IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT,
                CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES));
        mNotificationConversationActionTypesDefault = parseEntityList(parser.getString(
        mNotificationConversationActionTypesDefault = parseStringList(parser.getString(
                NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT,
                CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES));
        mLangIdThresholdOverride = parser.getFloat(
                LANG_ID_THRESHOLD_OVERRIDE,
                LANG_ID_THRESHOLD_OVERRIDE_DEFAULT);
    }

    /** Load from a settings string. */
@@ -272,8 +286,12 @@ public final class TextClassificationConstants {
        return mNotificationConversationActionTypesDefault;
    }

    private static List<String> parseEntityList(String listStr) {
        return Collections.unmodifiableList(Arrays.asList(listStr.split(ENTITY_LIST_DELIMITER)));
    public float getLangIdThresholdOverride() {
        return mLangIdThresholdOverride;
    }

    private static List<String> parseStringList(String listStr) {
        return Collections.unmodifiableList(Arrays.asList(listStr.split(STRING_LIST_DELIMITER)));
    }

    void dump(IndentingPrintWriter pw) {
@@ -296,6 +314,7 @@ public final class TextClassificationConstants {
        pw.printPair("getInAppConversationActionTypes", mInAppConversationActionTypesDefault);
        pw.printPair("getNotificationConversationActionTypes",
                mNotificationConversationActionTypesDefault);
        pw.printPair("getLangIdThresholdOverride", mLangIdThresholdOverride);
        pw.decreaseIndent();
        pw.println();
    }
+7 −2
Original line number Diff line number Diff line
@@ -567,8 +567,9 @@ public final class TextClassifierImpl implements TextClassifier {
            }
        }

        // TODO: Make this configurable.
        final float foreignTextThreshold = typeCount == 0 ? 0.5f : 0.7f;
        final float foreignTextThreshold = mSettings.getLangIdThresholdOverride() >= 0
                ? mSettings.getLangIdThresholdOverride()
                : 0.5f /* TODO: Load this from the langId model. */;
        boolean isPrimaryAction = true;
        final ArrayList<Intent> sourceIntents = new ArrayList<>();
        for (LabeledIntent labeledIntent : IntentFactory.create(
@@ -602,6 +603,10 @@ public final class TextClassifierImpl implements TextClassifier {
    }

    private boolean isForeignText(String text, float threshold) {
        if (threshold > 1) {
            return false;
        }

        // TODO: Revisit this algorithm.
        try {
            final LangIdModel.LanguageResult[] langResults = getLangIdImpl().detectLanguages(text);
+4 −2
Original line number Diff line number Diff line
@@ -89,9 +89,10 @@ public final class TextLanguage implements Parcelable {
    /**
     * Returns the language locale at the specified index. Locales are ordered from high
     * confidence to low confidence.
     * <p>
     * See {@link #getLocaleHypothesisCount()} for the number of locales available.
     *
     * @throws IndexOutOfBoundsException if the specified index is out of range.
     * @see #getLocaleHypothesisCount() for the number of locales available.
     */
    @NonNull
    public ULocale getLocale(int index) {
@@ -109,7 +110,8 @@ public final class TextLanguage implements Parcelable {
    }

    /**
     * Returns a bundle containing non-structured extra information about this result.
     * Returns a bundle containing non-structured extra information about this result. What is
     * returned in the extras is specific to the {@link TextClassifier} implementation.
     *
     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should prefer
     * to hold a reference to the returned bundle rather than frequently calling this method.
+142 −53
Original line number Diff line number Diff line
@@ -16,9 +16,7 @@

package android.view.textclassifier;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static com.google.common.truth.Truth.assertWithMessage;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -30,6 +28,8 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class TextClassificationConstantsTest {

    private static final float EPSILON = 0.0001f;

    @Test
    public void testLoadFromString() {
        final String s = "local_textclassifier_enabled=true,"
@@ -42,26 +42,55 @@ public class TextClassificationConstantsTest {
                + "suggest_selection_max_range_length=10,"
                + "classify_text_max_range_length=11,"
                + "generate_links_max_text_length=12,"
                + "generate_links_log_sample_rate=13";
                + "generate_links_log_sample_rate=13,"
                + "entity_list_default=phone,"
                + "entity_list_not_editable=address:flight,"
                + "entity_list_editable=date:datetime,"
                + "in_app_conversation_action_types_default=text_reply,"
                + "notification_conversation_action_types_default=send_email:call_phone,"
                + "lang_id_threshold_override=0.3";
        final TextClassificationConstants constants =
                TextClassificationConstants.loadFromString(s);
        assertTrue("local_textclassifier_enabled",
                constants.isLocalTextClassifierEnabled());
        assertTrue("system_textclassifier_enabled",
                constants.isSystemTextClassifierEnabled());
        assertTrue("model_dark_launch_enabled", constants.isModelDarkLaunchEnabled());
        assertTrue("smart_selection_enabled", constants.isSmartSelectionEnabled());
        assertTrue("smart_text_share_enabled", constants.isSmartTextShareEnabled());
        assertTrue("smart_linkify_enabled", constants.isSmartLinkifyEnabled());
        assertTrue("smart_select_animation_enabled", constants.isSmartSelectionAnimationEnabled());
        assertEquals("suggest_selection_max_range_length",
                10, constants.getSuggestSelectionMaxRangeLength());
        assertEquals("classify_text_max_range_length",
                11, constants.getClassifyTextMaxRangeLength());
        assertEquals("generate_links_max_text_length",
                12, constants.getGenerateLinksMaxTextLength());
        assertEquals("generate_links_log_sample_rate",
                13, constants.getGenerateLinksLogSampleRate());

        assertWithMessage("local_textclassifier_enabled")
                .that(constants.isLocalTextClassifierEnabled()).isTrue();
        assertWithMessage("system_textclassifier_enabled")
                .that(constants.isSystemTextClassifierEnabled()).isTrue();
        assertWithMessage("model_dark_launch_enabled")
                .that(constants.isModelDarkLaunchEnabled()).isTrue();
        assertWithMessage("smart_selection_enabled")
                .that(constants.isSmartSelectionEnabled()).isTrue();
        assertWithMessage("smart_text_share_enabled")
                .that(constants.isSmartTextShareEnabled()).isTrue();
        assertWithMessage("smart_linkify_enabled")
                .that(constants.isSmartLinkifyEnabled()).isTrue();
        assertWithMessage("smart_select_animation_enabled")
                .that(constants.isSmartSelectionAnimationEnabled()).isTrue();
        assertWithMessage("suggest_selection_max_range_length")
                .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10);
        assertWithMessage("classify_text_max_range_length")
                .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(11);
        assertWithMessage("generate_links_max_text_length")
                .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(12);
        assertWithMessage("generate_links_log_sample_rate")
                .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(13);
        assertWithMessage("entity_list_default")
                .that(constants.getEntityListDefault())
                .containsExactly("phone");
        assertWithMessage("entity_list_not_editable")
                .that(constants.getEntityListNotEditable())
                .containsExactly("address", "flight");
        assertWithMessage("entity_list_editable")
                .that(constants.getEntityListEditable())
                .containsExactly("date", "datetime");
        assertWithMessage("in_app_conversation_action_types_default")
                .that(constants.getInAppConversationActionTypes())
                .containsExactly("text_reply");
        assertWithMessage("notification_conversation_action_types_default")
                .that(constants.getNotificationConversationActionTypes())
                .containsExactly("send_email", "call_phone");
        assertWithMessage("lang_id_threshold_override")
                .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(0.3f);
    }

    @Test
@@ -76,42 +105,102 @@ public class TextClassificationConstantsTest {
                + "suggest_selection_max_range_length=8,"
                + "classify_text_max_range_length=7,"
                + "generate_links_max_text_length=6,"
                + "generate_links_log_sample_rate=5";
                + "generate_links_log_sample_rate=5,"
                + "entity_list_default=email:url,"
                + "entity_list_not_editable=date,"
                + "entity_list_editable=flight,"
                + "in_app_conversation_action_types_default=view_map:track_flight,"
                + "notification_conversation_action_types_default=share_location,"
                + "lang_id_threshold_override=2";
        final TextClassificationConstants constants =
                TextClassificationConstants.loadFromString(s);
        assertFalse("local_textclassifier_enabled",
                constants.isLocalTextClassifierEnabled());
        assertFalse("system_textclassifier_enabled",
                constants.isSystemTextClassifierEnabled());
        assertFalse("model_dark_launch_enabled", constants.isModelDarkLaunchEnabled());
        assertFalse("smart_selection_enabled", constants.isSmartSelectionEnabled());
        assertFalse("smart_text_share_enabled", constants.isSmartTextShareEnabled());
        assertFalse("smart_linkify_enabled", constants.isSmartLinkifyEnabled());
        assertFalse("smart_select_animation_enabled",
                constants.isSmartSelectionAnimationEnabled());
        assertEquals("suggest_selection_max_range_length",
                8, constants.getSuggestSelectionMaxRangeLength());
        assertEquals("classify_text_max_range_length",
                7, constants.getClassifyTextMaxRangeLength());
        assertEquals("generate_links_max_text_length",
                6, constants.getGenerateLinksMaxTextLength());
        assertEquals("generate_links_log_sample_rate",
                5, constants.getGenerateLinksLogSampleRate());

        assertWithMessage("local_textclassifier_enabled")
                .that(constants.isLocalTextClassifierEnabled()).isFalse();
        assertWithMessage("system_textclassifier_enabled")
                .that(constants.isSystemTextClassifierEnabled()).isFalse();
        assertWithMessage("model_dark_launch_enabled")
                .that(constants.isModelDarkLaunchEnabled()).isFalse();
        assertWithMessage("smart_selection_enabled")
                .that(constants.isSmartSelectionEnabled()).isFalse();
        assertWithMessage("smart_text_share_enabled")
                .that(constants.isSmartTextShareEnabled()).isFalse();
        assertWithMessage("smart_linkify_enabled")
                .that(constants.isSmartLinkifyEnabled()).isFalse();
        assertWithMessage("smart_select_animation_enabled")
                .that(constants.isSmartSelectionAnimationEnabled()).isFalse();
        assertWithMessage("suggest_selection_max_range_length")
                .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(8);
        assertWithMessage("classify_text_max_range_length")
                .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(7);
        assertWithMessage("generate_links_max_text_length")
                .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(6);
        assertWithMessage("generate_links_log_sample_rate")
                .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(5);
        assertWithMessage("entity_list_default")
                .that(constants.getEntityListDefault())
                .containsExactly("email", "url");
        assertWithMessage("entity_list_not_editable")
                .that(constants.getEntityListNotEditable())
                .containsExactly("date");
        assertWithMessage("entity_list_editable")
                .that(constants.getEntityListEditable())
                .containsExactly("flight");
        assertWithMessage("in_app_conversation_action_types_default")
                .that(constants.getInAppConversationActionTypes())
                .containsExactly("view_map", "track_flight");
        assertWithMessage("notification_conversation_action_types_default")
                .that(constants.getNotificationConversationActionTypes())
                .containsExactly("share_location");
        assertWithMessage("lang_id_threshold_override")
                .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(2f);
    }

    @Test
    public void testEntityListParsing() {
        final TextClassificationConstants constants = TextClassificationConstants.loadFromString(
                "entity_list_default=phone,"
                        + "entity_list_not_editable=address:flight,"
                        + "entity_list_editable=date:datetime");
        assertEquals(1, constants.getEntityListDefault().size());
        assertEquals("phone", constants.getEntityListDefault().get(0));
        assertEquals(2, constants.getEntityListNotEditable().size());
        assertEquals("address", constants.getEntityListNotEditable().get(0));
        assertEquals("flight", constants.getEntityListNotEditable().get(1));
        assertEquals(2, constants.getEntityListEditable().size());
        assertEquals("date", constants.getEntityListEditable().get(0));
        assertEquals("datetime", constants.getEntityListEditable().get(1));
    public void testLoadFromString_defaultValues() {
        final TextClassificationConstants constants =
                TextClassificationConstants.loadFromString("");

        assertWithMessage("local_textclassifier_enabled")
                .that(constants.isLocalTextClassifierEnabled()).isTrue();
        assertWithMessage("system_textclassifier_enabled")
                .that(constants.isSystemTextClassifierEnabled()).isTrue();
        assertWithMessage("model_dark_launch_enabled")
                .that(constants.isModelDarkLaunchEnabled()).isFalse();
        assertWithMessage("smart_selection_enabled")
                .that(constants.isSmartSelectionEnabled()).isTrue();
        assertWithMessage("smart_text_share_enabled")
                .that(constants.isSmartTextShareEnabled()).isTrue();
        assertWithMessage("smart_linkify_enabled")
                .that(constants.isSmartLinkifyEnabled()).isTrue();
        assertWithMessage("smart_select_animation_enabled")
                .that(constants.isSmartSelectionAnimationEnabled()).isTrue();
        assertWithMessage("suggest_selection_max_range_length")
                .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10 * 1000);
        assertWithMessage("classify_text_max_range_length")
                .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(10 * 1000);
        assertWithMessage("generate_links_max_text_length")
                .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(100 * 1000);
        assertWithMessage("generate_links_log_sample_rate")
                .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(100);
        assertWithMessage("entity_list_default")
                .that(constants.getEntityListDefault())
                .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight");
        assertWithMessage("entity_list_not_editable")
                .that(constants.getEntityListNotEditable())
                .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight");
        assertWithMessage("entity_list_editable")
                .that(constants.getEntityListEditable())
                .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight");
        assertWithMessage("in_app_conversation_action_types_default")
                .that(constants.getInAppConversationActionTypes())
                .containsExactly("text_reply", "create_reminder", "call_phone", "open_url",
                        "send_email", "send_sms", "track_flight", "view_calendar", "view_map");
        assertWithMessage("notification_conversation_action_types_default")
                .that(constants.getNotificationConversationActionTypes())
                .containsExactly("text_reply", "create_reminder", "call_phone", "open_url",
                        "send_email", "send_sms", "track_flight", "view_calendar", "view_map");
        assertWithMessage("lang_id_threshold_override")
                .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(-1f);
    }
}