diff options
| author | 2019-03-16 17:11:12 +0000 | |
|---|---|---|
| committer | 2019-03-16 17:11:12 +0000 | |
| commit | 6067cfa1a8b59b86354447e5adade23ed25d2e7d (patch) | |
| tree | ad9ca35263483e9de7ca83aed38a566d56a63d4e | |
| parent | 9211b81399088f8fe9d8ca489589bd5d794cf9e0 (diff) | |
| parent | 72e17976f38403a05a62d792613dbb296dcc85fe (diff) | |
Merge "Include language information in intents from the TextClassifier."
17 files changed, 501 insertions, 127 deletions
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java index e7ae7a0ebe78..9c268f2d18a8 100644 --- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java @@ -143,7 +143,7 @@ public final class ActionsSuggestionsHelper { // intent for each action type. LabeledIntent.TitleChooser titleChooser = ActionsSuggestionsHelper.createTitleChooser(nativeSuggestion.getActionType()); - return labeledIntents.get(0).resolve(context, titleChooser); + return labeledIntents.get(0).resolve(context, titleChooser, null); } /** diff --git a/core/java/android/view/textclassifier/ExtrasUtils.java b/core/java/android/view/textclassifier/ExtrasUtils.java index 05702bf08bd7..eadad28284c1 100644 --- a/core/java/android/view/textclassifier/ExtrasUtils.java +++ b/core/java/android/view/textclassifier/ExtrasUtils.java @@ -19,6 +19,7 @@ package android.view.textclassifier; import android.annotation.Nullable; import android.app.RemoteAction; import android.content.Intent; +import android.icu.util.ULocale; import android.os.Bundle; import java.util.ArrayList; @@ -27,6 +28,7 @@ import java.util.ArrayList; * Utility class for inserting and retrieving data in TextClassifier request/response extras. * @hide */ +// TODO: Make this a TestApi for CTS testing. public final class ExtrasUtils { private static final String ENTITIES_EXTRAS = "entities-extras"; @@ -37,6 +39,7 @@ public final class ExtrasUtils { private static final String SCORE = "score"; private static final String MODEL_VERSION = "model-version"; private static final String MODEL_NAME = "model-name"; + private static final String TEXT_LANGUAGES = "text-languages"; private ExtrasUtils() {} @@ -56,6 +59,8 @@ public final class ExtrasUtils { /** * Stores {@code extra} as foreign language information in TextClassifier response object's * extras {@code container}. + * + * @see #getForeignLanguageExtra(TextClassification) */ static void putForeignLanguageExtra(Bundle container, Bundle extra) { container.putParcelable(FOREIGN_LANGUAGE, extra); @@ -64,13 +69,68 @@ public final class ExtrasUtils { /** * Returns foreign language detection information contained in the TextClassification object. * responses. + * + * @see #putForeignLanguageExtra(Bundle, Bundle) */ @Nullable - public static Bundle getForeignLanguageExtra(TextClassification classification) { + public static Bundle getForeignLanguageExtra(@Nullable TextClassification classification) { + if (classification == null) { + return null; + } return classification.getExtras().getBundle(FOREIGN_LANGUAGE); } /** + * @see #getTopLanguage(Intent) + */ + static void putTopLanguageScores(Bundle container, EntityConfidence languageScores) { + final int maxSize = Math.min(3, languageScores.getEntities().size()); + final String[] languages = languageScores.getEntities().subList(0, maxSize) + .toArray(new String[0]); + final float[] scores = new float[languages.length]; + for (int i = 0; i < languages.length; i++) { + scores[i] = languageScores.getConfidenceScore(languages[i]); + } + container.putStringArray(ENTITY_TYPE, languages); + container.putFloatArray(SCORE, scores); + } + + /** + * @see #putTopLanguageScores(Bundle, EntityConfidence) + */ + @Nullable + public static ULocale getTopLanguage(@Nullable Intent intent) { + if (intent == null) { + return null; + } + final Bundle tcBundle = intent.getBundleExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER); + if (tcBundle == null) { + return null; + } + final Bundle textLanguagesExtra = tcBundle.getBundle(TEXT_LANGUAGES); + if (textLanguagesExtra == null) { + return null; + } + final String[] languages = textLanguagesExtra.getStringArray(ENTITY_TYPE); + final float[] scores = textLanguagesExtra.getFloatArray(SCORE); + if (languages == null || scores == null + || languages.length == 0 || languages.length != scores.length) { + return null; + } + int highestScoringIndex = 0; + for (int i = 1; i < languages.length; i++) { + if (scores[highestScoringIndex] < scores[i]) { + highestScoringIndex = i; + } + } + return ULocale.forLanguageTag(languages[highestScoringIndex]); + } + + public static void putTextLanguagesExtra(Bundle container, Bundle extra) { + container.putBundle(TEXT_LANGUAGES, extra); + } + + /** * Stores {@code actionIntents} information in TextClassifier response object's extras * {@code container}. */ @@ -122,7 +182,10 @@ public final class ExtrasUtils { * Returns {@code actionIntents} information contained in the TextClassification object. */ @Nullable - public static ArrayList<Intent> getActionsIntents(TextClassification classification) { + public static ArrayList<Intent> getActionsIntents(@Nullable TextClassification classification) { + if (classification == null) { + return null; + } return classification.getExtras().getParcelableArrayList(ACTIONS_INTENTS); } @@ -131,7 +194,11 @@ public final class ExtrasUtils { * action string, {@code intentAction}. */ @Nullable - public static RemoteAction findAction(TextClassification classification, String intentAction) { + public static RemoteAction findAction( + @Nullable TextClassification classification, @Nullable String intentAction) { + if (classification == null || intentAction == null) { + return null; + } final ArrayList<Intent> actionIntents = getActionsIntents(classification); if (actionIntents != null) { final int size = actionIntents.size(); @@ -149,7 +216,7 @@ public final class ExtrasUtils { * Returns the first "translate" action found in the {@code classification} object. */ @Nullable - public static RemoteAction findTranslateAction(TextClassification classification) { + public static RemoteAction findTranslateAction(@Nullable TextClassification classification) { return findAction(classification, Intent.ACTION_TRANSLATE); } @@ -157,7 +224,10 @@ public final class ExtrasUtils { * Returns the entity type contained in the {@code extra}. */ @Nullable - public static String getEntityType(Bundle extra) { + public static String getEntityType(@Nullable Bundle extra) { + if (extra == null) { + return null; + } return extra.getString(ENTITY_TYPE); } @@ -166,14 +236,21 @@ public final class ExtrasUtils { */ @Nullable public static float getScore(Bundle extra) { - return extra.getFloat(SCORE, -1); + final int defaultValue = -1; + if (extra == null) { + return defaultValue; + } + return extra.getFloat(SCORE, defaultValue); } /** * Returns the model name contained in the {@code extra}. */ @Nullable - public static String getModelName(Bundle extra) { + public static String getModelName(@Nullable Bundle extra) { + if (extra == null) { + return null; + } return extra.getString(MODEL_NAME); } } diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index 2627ae655f9a..876e5cccb2e0 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -50,6 +50,7 @@ import java.util.StringJoiner; * template_intent_factory_enabled (boolean) * translate_in_classification_enabled (boolean) * detect_languages_from_text_enabled (boolean) + * lang_id_context_settings (float[]) * </pre> * * <p> @@ -58,12 +59,14 @@ import java.util.StringJoiner; * * Example of setting the values for testing. * adb shell settings put global text_classifier_constants \ - * model_dark_launch_enabled=true,smart_selection_enabled=true,\ - * entity_list_default=phone:address + * model_dark_launch_enabled=true,smart_selection_enabled=true, \ + * entity_list_default=phone:address, \ + * lang_id_context_settings=20:1.0:0.4 * @hide */ public final class TextClassificationConstants { - private static final String LOG_TAG = "TextClassificationConstants"; + + private static final String LOG_TAG = TextClassifier.DEFAULT_LOG_TAG; /** * Whether the smart linkify feature is enabled. @@ -148,7 +151,6 @@ public final class TextClassificationConstants { * Whether to enable {@link android.view.textclassifier.TemplateIntentFactory}. */ private static final String TEMPLATE_INTENT_FACTORY_ENABLED = "template_intent_factory_enabled"; - /** * Whether to enable "translate" action in classifyText. */ @@ -160,6 +162,20 @@ public final class TextClassificationConstants { */ private static final String DETECT_LANGUAGES_FROM_TEXT_ENABLED = "detect_languages_from_text_enabled"; + /** + * A colon(:) separated string that specifies the configuration to use when including + * surrounding context text in language detection queries. + * <p> + * Format= minimumTextSize<int>:penalizeRatio<float>:textScoreRatio<float> + * <p> + * e.g. 20:1.0:0.4 + * <p> + * Accept all text lengths with minimumTextSize=0 + * <p> + * Reject all text less than minimumTextSize with penalizeRatio=0 + * @see {@code TextClassifierImpl#detectLanguages(String, int, int)} for reference. + */ + private static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings"; private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; @@ -205,6 +221,8 @@ public final class TextClassificationConstants { private static final boolean TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT = true; private static final boolean TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT = true; private static final boolean DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT = true; + private static final String LANG_ID_CONTEXT_SETTINGS_DEFAULT = + new StringJoiner(STRING_LIST_DELIMITER).add("20").add("1.0").add("0.4").toString(); private final boolean mSystemTextClassifierEnabled; private final boolean mLocalTextClassifierEnabled; @@ -226,6 +244,7 @@ public final class TextClassificationConstants { private final boolean mTemplateIntentFactoryEnabled; private final boolean mTranslateInClassificationEnabled; private final boolean mDetectLanguagesFromTextEnabled; + private final float[] mLangIdContextSettings; private TextClassificationConstants(@Nullable String settings) { ConfigParser configParser = new ConfigParser(settings); @@ -273,9 +292,10 @@ public final class TextClassificationConstants { configParser.getInt( GENERATE_LINKS_LOG_SAMPLE_RATE, GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT); - mEntityListDefault = parseStringList(configParser.getString( - ENTITY_LIST_DEFAULT, - ENTITY_LIST_DEFAULT_VALUE)); + mEntityListDefault = parseStringList( + configParser.getString( + ENTITY_LIST_DEFAULT, + ENTITY_LIST_DEFAULT_VALUE)); mEntityListNotEditable = parseStringList( configParser.getString( ENTITY_LIST_NOT_EDITABLE, @@ -296,13 +316,22 @@ public final class TextClassificationConstants { configParser.getFloat( LANG_ID_THRESHOLD_OVERRIDE, LANG_ID_THRESHOLD_OVERRIDE_DEFAULT); - mTemplateIntentFactoryEnabled = configParser.getBoolean( - TEMPLATE_INTENT_FACTORY_ENABLED, - TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT); - mTranslateInClassificationEnabled = configParser.getBoolean( - TRANSLATE_IN_CLASSIFICATION_ENABLED, TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT); - mDetectLanguagesFromTextEnabled = configParser.getBoolean( - DETECT_LANGUAGES_FROM_TEXT_ENABLED, DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT); + mTemplateIntentFactoryEnabled = + configParser.getBoolean( + TEMPLATE_INTENT_FACTORY_ENABLED, + TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT); + mTranslateInClassificationEnabled = + configParser.getBoolean( + TRANSLATE_IN_CLASSIFICATION_ENABLED, + TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT); + mDetectLanguagesFromTextEnabled = + configParser.getBoolean( + DETECT_LANGUAGES_FROM_TEXT_ENABLED, + DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT); + mLangIdContextSettings = parseFloatArray( + configParser, + LANG_ID_CONTEXT_SETTINGS, + LANG_ID_CONTEXT_SETTINGS_DEFAULT); } /** Load from a settings string. */ @@ -390,10 +419,35 @@ public final class TextClassificationConstants { return mDetectLanguagesFromTextEnabled; } + public float[] getLangIdContextSettings() { + return mLangIdContextSettings; + } + private static List<String> parseStringList(String listStr) { return Collections.unmodifiableList(Arrays.asList(listStr.split(STRING_LIST_DELIMITER))); } + private static float[] parseFloatArray( + ConfigParser configParser, String key, String defaultStr) { + final String str = configParser.getString(key, defaultStr); + final String[] defaultSplit = defaultStr.split(STRING_LIST_DELIMITER); + String[] split = str.split(STRING_LIST_DELIMITER); + if (split.length != defaultSplit.length) { + Log.v(LOG_TAG, "Error parsing " + key + " flag. Using defaults."); + split = defaultSplit; + } + final float[] result = new float[split.length]; + for (int i = 0; i < split.length; i++) { + try { + result[i] = Float.parseFloat(split[i]); + } catch (NumberFormatException e) { + Log.v(LOG_TAG, "Error parsing part of " + key + " flag. Using defaults."); + result[i] = Float.parseFloat(defaultSplit[i]); + } + } + return result; + } + void dump(IndentingPrintWriter pw) { pw.println("TextClassificationConstants:"); pw.increaseIndent(); @@ -418,6 +472,7 @@ public final class TextClassificationConstants { pw.printPair("isTemplateIntentFactoryEnabled", mTemplateIntentFactoryEnabled); pw.printPair("isTranslateInClassificationEnabled", mTranslateInClassificationEnabled); pw.printPair("isDetectLanguageFromTextEnabled", mDetectLanguagesFromTextEnabled); + pw.printPair("getLangIdContextSettings", Arrays.toString(mLangIdContextSettings)); pw.decreaseIndent(); pw.println(); } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index a4e550285c97..ac8a429a0fd6 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -33,11 +33,13 @@ import android.text.util.Linkify; import android.text.util.Linkify.LinkifyMask; import android.util.ArrayMap; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.text.BreakIterator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -644,11 +646,14 @@ public interface TextClassifier { * <li>Provides validation of input parameters to TextClassifier methods * </ul> * - * Intended to be used only in this package. + * Intended to be used only for TextClassifier purposes. * @hide */ final class Utils { + @GuardedBy("WORD_ITERATOR") + private static final BreakIterator WORD_ITERATOR = BreakIterator.getWordInstance(); + /** * @throws IllegalArgumentException if text is null; startIndex is negative; * endIndex is greater than text.length() or is not greater than startIndex; @@ -666,6 +671,47 @@ public interface TextClassifier { } /** + * Returns the substring of {@code text} that contains at least text from index + * {@code start} <i>(inclusive)</i> to index {@code end} <i><(exclusive)/i> with the goal of + * returning text that is at least {@code minimumLength}. If {@code text} is not long + * enough, this will return {@code text}. This method returns text at word boundaries. + * + * @param text the source text + * @param start the start index of text that must be included + * @param end the end index of text that must be included + * @param minimumLength minimum length of text to return if {@code text} is long enough + */ + public static String getSubString( + String text, int start, int end, int minimumLength) { + Preconditions.checkArgument(start >= 0); + Preconditions.checkArgument(end <= text.length()); + Preconditions.checkArgument(start <= end); + + if (text.length() < minimumLength) { + return text; + } + + final int length = end - start; + if (length >= minimumLength) { + return text.substring(start, end); + } + + final int offset = (minimumLength - length) / 2; + int iterStart = Math.max(0, Math.min(start - offset, text.length() - minimumLength)); + int iterEnd = Math.min(text.length(), iterStart + minimumLength); + + synchronized (WORD_ITERATOR) { + WORD_ITERATOR.setText(text); + iterStart = WORD_ITERATOR.isBoundary(iterStart) + ? iterStart : Math.max(0, WORD_ITERATOR.preceding(iterStart)); + iterEnd = WORD_ITERATOR.isBoundary(iterEnd) + ? iterEnd : Math.max(iterEnd, WORD_ITERATOR.following(iterEnd)); + WORD_ITERATOR.setText(""); + return text.substring(iterStart, iterEnd); + } + } + + /** * Generates links using legacy {@link Linkify}. */ public static TextLinks generateLegacyLinks(@NonNull TextLinks.Request request) { diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 76d03132e0d4..8f5f0a376c35 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -21,10 +21,14 @@ import android.annotation.Nullable; import android.annotation.WorkerThread; import android.app.RemoteAction; import android.content.Context; +import android.content.Intent; import android.icu.util.ULocale; import android.os.Bundle; import android.os.LocaleList; import android.os.ParcelFileDescriptor; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Pair; import android.view.textclassifier.intent.ClassificationIntentFactory; import android.view.textclassifier.intent.LabeledIntent; import android.view.textclassifier.intent.LegacyClassificationIntentFactory; @@ -38,6 +42,7 @@ import com.android.internal.util.Preconditions; import com.google.android.textclassifier.ActionsSuggestionsModel; import com.google.android.textclassifier.AnnotatorModel; import com.google.android.textclassifier.LangIdModel; +import com.google.android.textclassifier.LangIdModel.LanguageResult; import java.io.File; import java.io.FileNotFoundException; @@ -46,11 +51,12 @@ import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * Default implementation of the {@link TextClassifier} interface. @@ -90,19 +96,19 @@ public final class TextClassifierImpl implements TextClassifier { private final Object mLock = new Object(); - @GuardedBy("mLock") // Do not access outside this lock. + @GuardedBy("mLock") private ModelFileManager.ModelFile mAnnotatorModelInUse; - @GuardedBy("mLock") // Do not access outside this lock. + @GuardedBy("mLock") private AnnotatorModel mAnnotatorImpl; - @GuardedBy("mLock") // Do not access outside this lock. + @GuardedBy("mLock") private ModelFileManager.ModelFile mLangIdModelInUse; - @GuardedBy("mLock") // Do not access outside this lock. + @GuardedBy("mLock") private LangIdModel mLangIdImpl; - @GuardedBy("mLock") // Do not access outside this lock. + @GuardedBy("mLock") private ModelFileManager.ModelFile mActionModelInUse; - @GuardedBy("mLock") // Do not access outside this lock. + @GuardedBy("mLock") private ActionsSuggestionsModel mActionsImpl; private final SelectionSessionLogger mSessionLogger = new SelectionSessionLogger(); @@ -283,7 +289,7 @@ public final class TextClassifierImpl implements TextClassifier { final ZonedDateTime refTime = ZonedDateTime.now(); final Collection<String> entitiesToIdentify = request.getEntityConfig() != null ? request.getEntityConfig().resolveEntityListModifications( - getEntitiesForHints(request.getEntityConfig().getHints())) + getEntitiesForHints(request.getEntityConfig().getHints())) : mSettings.getEntityListDefault(); final String localesString = concatenateLocales(request.getDefaultLocales()); final String detectLanguageTags = detectLanguageTagsFromText(request.getText()); @@ -304,7 +310,7 @@ public final class TextClassifierImpl implements TextClassifier { || !entitiesToIdentify.contains(results[0].getCollection())) { continue; } - final Map<String, Float> entityScores = new HashMap<>(); + final Map<String, Float> entityScores = new ArrayMap<>(); for (int i = 0; i < results.length; i++) { entityScores.put(results[i].getCollection(), results[i].getScore()); } @@ -620,50 +626,67 @@ public final class TextClassifierImpl implements TextClassifier { } } - final Bundle foreignLanguageBundle = detectForeignLanguage(classifiedText); + final Pair<Bundle, Bundle> languagesBundles = generateLanguageBundles(text, start, end); + final Bundle textLanguagesBundle = languagesBundles.first; + final Bundle foreignLanguageBundle = languagesBundles.second; builder.setForeignLanguageExtra(foreignLanguageBundle); boolean isPrimaryAction = true; - List<LabeledIntent> labeledIntents = mClassificationIntentFactory.create( + final List<LabeledIntent> labeledIntents = mClassificationIntentFactory.create( mContext, classifiedText, foreignLanguageBundle != null, referenceTime, highestScoringResult); - LabeledIntent.TitleChooser titleChooser = + final LabeledIntent.TitleChooser titleChooser = (labeledIntent, resolveInfo) -> labeledIntent.titleWithoutEntity; + for (LabeledIntent labeledIntent : labeledIntents) { - LabeledIntent.Result result = labeledIntent.resolve(mContext, titleChooser); + final LabeledIntent.Result result = + labeledIntent.resolve(mContext, titleChooser, textLanguagesBundle); if (result == null) { continue; } + + final Intent intent = result.resolvedIntent; final RemoteAction action = result.remoteAction; if (isPrimaryAction) { // For O backwards compatibility, the first RemoteAction is also written to the // legacy API fields. builder.setIcon(action.getIcon().loadDrawable(mContext)); builder.setLabel(action.getTitle().toString()); - builder.setIntent(result.resolvedIntent); + builder.setIntent(intent); builder.setOnClickListener(TextClassification.createIntentOnClickListener( - TextClassification.createPendingIntent(mContext, - result.resolvedIntent, labeledIntent.requestCode))); + TextClassification.createPendingIntent( + mContext, intent, labeledIntent.requestCode))); isPrimaryAction = false; } - builder.addAction(action, result.resolvedIntent); + builder.addAction(action, intent); } return builder.setId(createId(text, start, end)).build(); } /** - * Returns a bundle with the language and confidence score if it finds the text to be - * in a foreign language. Otherwise returns null. This algorithm defines what the system thinks - * is a foreign language. + * Returns a bundle pair with language detection information for extras. + * <p> + * Pair.first = textLanguagesBundle - A bundle containing information about all detected + * languages in the text. May be null if language detection fails or is disabled. This is + * typically expected to be added to a textClassifier generated remote action intent. + * See {@link ExtrasUtils#putTextLanguagesExtra(Bundle, Bundle)}. + * See {@link ExtrasUtils#getTopLanguage(Intent)}. + * <p> + * Pair.second = foreignLanguageBundle - A bundle with the language and confidence score if the + * system finds the text to be in a foreign language. Otherwise is null. + * See {@link TextClassification.Builder#setForeignLanguageExtra(Bundle)}. + * + * @param context the context of the text to detect languages for + * @param start the start index of the text + * @param end the end index of the text */ // TODO: Revisit this algorithm. // TODO: Consider making this public API. - @Nullable - private Bundle detectForeignLanguage(String text) { + private Pair<Bundle, Bundle> generateLanguageBundles(String context, int start, int end) { if (!mSettings.isTranslateInClassificationEnabled()) { return null; } @@ -672,42 +695,118 @@ public final class TextClassifierImpl implements TextClassifier { if (threshold < 0 || threshold > 1) { Log.w(LOG_TAG, "[detectForeignLanguage] unexpected threshold is found: " + threshold); - return null; + return Pair.create(null, null); } - final LangIdModel langId = getLangIdImpl(); - final LangIdModel.LanguageResult[] langResults = langId.detectLanguages(text); - if (langResults.length <= 0) { - return null; + final EntityConfidence languageScores = detectLanguages(context, start, end); + if (languageScores.getEntities().isEmpty()) { + return Pair.create(null, null); } - LangIdModel.LanguageResult highestScoringResult = langResults[0]; - for (int i = 1; i < langResults.length; i++) { - if (langResults[i].getScore() > highestScoringResult.getScore()) { - highestScoringResult = langResults[i]; - } - } - if (highestScoringResult.getScore() < threshold) { - return null; + final Bundle textLanguagesBundle = new Bundle(); + ExtrasUtils.putTopLanguageScores(textLanguagesBundle, languageScores); + + final String language = languageScores.getEntities().get(0); + final float score = languageScores.getConfidenceScore(language); + if (score < threshold) { + return Pair.create(textLanguagesBundle, null); } - Log.v(LOG_TAG, String.format("Language detected: <%s:%s>", - highestScoringResult.getLanguage(), highestScoringResult.getScore())); + Log.v(LOG_TAG, String.format( + Locale.US, "Language detected: <%s:%.2f>", language, score)); - final Locale detected = new Locale(highestScoringResult.getLanguage()); + final Locale detected = new Locale(language); final LocaleList deviceLocales = LocaleList.getDefault(); final int size = deviceLocales.size(); for (int i = 0; i < size; i++) { if (deviceLocales.get(i).getLanguage().equals(detected.getLanguage())) { - return null; + return Pair.create(textLanguagesBundle, null); } } - return ExtrasUtils.createForeignLanguageExtra( - detected.getLanguage(), highestScoringResult.getScore(), langId.getVersion()); + final Bundle foreignLanguageBundle = ExtrasUtils.createForeignLanguageExtra( + detected.getLanguage(), score, getLangIdImpl().getVersion()); + return Pair.create(textLanguagesBundle, foreignLanguageBundle); } catch (Throwable t) { - Log.e(LOG_TAG, "Error detecting foreign text. Ignored.", t); + Log.e(LOG_TAG, "Error generating language bundles.", t); + } + return Pair.create(null, null); + } + + /** + * Detect the language of a piece of text by taking surrounding text into consideration. + * + * @param text text providing context for the text for which its language is to be detected + * @param start the start index of the text to detect its language + * @param end the end index of the text to detect its language + */ + // TODO: Revisit this algorithm. + private EntityConfidence detectLanguages(String text, int start, int end) + throws FileNotFoundException { + Preconditions.checkArgument(start >= 0); + Preconditions.checkArgument(end <= text.length()); + Preconditions.checkArgument(start <= end); + + final float[] langIdContextSettings = mSettings.getLangIdContextSettings(); + // The minimum size of text to prefer for detection. + final int minimumTextSize = (int) langIdContextSettings[0]; + // For reducing the score when text is less than the preferred size. + final float penalizeRatio = langIdContextSettings[1]; + // Original detection score to surrounding text detection score ratios. + final float subjectTextScoreRatio = langIdContextSettings[2]; + final float moreTextScoreRatio = 1f - subjectTextScoreRatio; + Log.v(LOG_TAG, + String.format(Locale.US, "LangIdContextSettings: " + + "minimumTextSize=%d, penalizeRatio=%.2f, " + + "subjectTextScoreRatio=%.2f, moreTextScoreRatio=%.2f", + minimumTextSize, penalizeRatio, subjectTextScoreRatio, moreTextScoreRatio)); + + if (end - start < minimumTextSize && penalizeRatio <= 0) { + return new EntityConfidence(Collections.emptyMap()); + } + + final String subject = text.substring(start, end); + final EntityConfidence scores = detectLanguages(subject); + + if (subject.length() >= minimumTextSize + || subject.length() == text.length() + || subjectTextScoreRatio * penalizeRatio >= 1) { + return scores; + } + + final EntityConfidence moreTextScores; + if (moreTextScoreRatio >= 0) { + // Attempt to grow the detection text to be at least minimumTextSize long. + final String moreText = Utils.getSubString(text, start, end, minimumTextSize); + moreTextScores = detectLanguages(moreText); + } else { + moreTextScores = new EntityConfidence(Collections.emptyMap()); + } + + // Combine the original detection scores with the those returned after including more text. + final Map<String, Float> newScores = new ArrayMap<>(); + final Set<String> languages = new ArraySet<>(); + languages.addAll(scores.getEntities()); + languages.addAll(moreTextScores.getEntities()); + for (String language : languages) { + final float score = (subjectTextScoreRatio * scores.getConfidenceScore(language) + + moreTextScoreRatio * moreTextScores.getConfidenceScore(language)) + * penalizeRatio; + newScores.put(language, score); + } + return new EntityConfidence(newScores); + } + + /** + * Detect languages for the specified text. + */ + private EntityConfidence detectLanguages(String text) throws FileNotFoundException { + final LangIdModel langId = getLangIdImpl(); + final LangIdModel.LanguageResult[] langResults = langId.detectLanguages(text); + final Map<String, Float> languagesMap = new ArrayMap<>(); + for (LanguageResult langResult : langResults) { + languagesMap.put(langResult.getLanguage(), langResult.getScore()); } - return null; + return new EntityConfidence(languagesMap); } private float getLangIdThreshold() { diff --git a/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java index 4896ae60abff..b0348462fd54 100644 --- a/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java +++ b/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java @@ -18,7 +18,6 @@ package android.view.textclassifier.intent; import android.annotation.Nullable; import android.content.Context; import android.content.Intent; -import android.view.textclassifier.TextClassifier; import com.google.android.textclassifier.AnnotatorModel; @@ -52,8 +51,7 @@ public interface ClassificationIntentFactory { new Intent(Intent.ACTION_TRANSLATE) // TODO: Probably better to introduce a "translate" scheme instead of // using EXTRA_TEXT. - .putExtra(Intent.EXTRA_TEXT, text) - .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true), + .putExtra(Intent.EXTRA_TEXT, text), text.hashCode())); } } diff --git a/core/java/android/view/textclassifier/intent/LabeledIntent.java b/core/java/android/view/textclassifier/intent/LabeledIntent.java index 4b32f1eadb5f..11d64f185e7d 100644 --- a/core/java/android/view/textclassifier/intent/LabeledIntent.java +++ b/core/java/android/view/textclassifier/intent/LabeledIntent.java @@ -24,9 +24,12 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.drawable.Icon; +import android.os.Bundle; import android.text.TextUtils; +import android.view.textclassifier.ExtrasUtils; import android.view.textclassifier.Log; import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassifier; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; @@ -87,10 +90,16 @@ public final class LabeledIntent { /** * Return the resolved result. + * + * @param context the context to resolve the result's intent and action + * @param titleChooser for choosing an action title + * @param textLanguagesBundle containing language detection information */ @Nullable public Result resolve( - Context context, @Nullable TitleChooser titleChooser) { + Context context, + @Nullable TitleChooser titleChooser, + @Nullable Bundle textLanguagesBundle) { final PackageManager pm = context.getPackageManager(); final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); @@ -106,6 +115,10 @@ public final class LabeledIntent { } Intent resolvedIntent = new Intent(intent); resolvedIntent.setComponent(new ComponentName(packageName, className)); + resolvedIntent.putExtra( + TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, + getFromTextClassifierExtra(textLanguagesBundle)); + boolean shouldShowIcon = false; Icon icon = null; if (!"android".equals(packageName)) { @@ -117,25 +130,32 @@ public final class LabeledIntent { } if (icon == null) { // RemoteAction requires that there be an icon. - icon = Icon.createWithResource("android", - com.android.internal.R.drawable.ic_more_items); + icon = Icon.createWithResource( + "android", com.android.internal.R.drawable.ic_more_items); } final PendingIntent pendingIntent = TextClassification.createPendingIntent(context, resolvedIntent, requestCode); - if (titleChooser == null) { - titleChooser = DEFAULT_TITLE_CHOOSER; - } + titleChooser = titleChooser == null ? DEFAULT_TITLE_CHOOSER : titleChooser; CharSequence title = titleChooser.chooseTitle(this, resolveInfo); if (TextUtils.isEmpty(title)) { Log.w(TAG, "Custom titleChooser return null, fallback to the default titleChooser"); title = DEFAULT_TITLE_CHOOSER.chooseTitle(this, resolveInfo); } - final RemoteAction action = - new RemoteAction(icon, title, description, pendingIntent); + final RemoteAction action = new RemoteAction(icon, title, description, pendingIntent); action.setShouldShowIcon(shouldShowIcon); return new Result(resolvedIntent, action); } + private Bundle getFromTextClassifierExtra(@Nullable Bundle textLanguagesBundle) { + if (textLanguagesBundle != null) { + final Bundle bundle = new Bundle(); + ExtrasUtils.putTextLanguagesExtra(bundle, textLanguagesBundle); + return bundle; + } else { + return Bundle.EMPTY; + } + } + /** * Data class that holds the result. */ diff --git a/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java index 1bb5430f1898..7916791e141d 100644 --- a/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java +++ b/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java @@ -52,8 +52,6 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn private static final long MIN_EVENT_FUTURE_MILLIS = TimeUnit.MINUTES.toMillis(5); private static final long DEFAULT_EVENT_DURATION = TimeUnit.HOURS.toMillis(1); - public LegacyClassificationIntentFactory() {} - @NonNull @Override public List<LabeledIntent> create(Context context, String text, boolean foreignText, @@ -100,8 +98,6 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn if (foreignText) { ClassificationIntentFactory.insertTranslateAction(actions, context, text); } - actions.forEach( - action -> action.intent.putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true)); return actions; } diff --git a/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java b/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java index e630f61a9f6a..59cd7aba05e6 100644 --- a/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java +++ b/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java @@ -66,8 +66,6 @@ public final class TemplateIntentFactory { ? LabeledIntent.DEFAULT_REQUEST_CODE : remoteActionTemplate.requestCode)); } - labeledIntents.forEach( - action -> action.intent.putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true)); return labeledIntents; } diff --git a/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java b/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java index fef6583f4f98..2674e3796c4a 100644 --- a/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java +++ b/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java @@ -44,8 +44,7 @@ import java.util.UUID; /** * A builder used to build a fake context for testing. */ -// TODO: Consider making public. -final class FakeContextBuilder { +public final class FakeContextBuilder { /** * A component name that can be used for tests. @@ -57,7 +56,7 @@ final class FakeContextBuilder { private final Map<String, ComponentName> mComponents = new HashMap<>(); private @Nullable ComponentName mAllIntentComponent; - FakeContextBuilder() { + public FakeContextBuilder() { mPackageManager = mock(PackageManager.class); when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(null); mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) { diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java index 066076ce5d12..f6bb1bff3635 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,7 +49,8 @@ public class TextClassificationConstantsTest { + "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"; + + "lang_id_threshold_override=0.3," + + "lang_id_context_settings=10:1:0.5"; final TextClassificationConstants constants = TextClassificationConstants.loadFromString(s); @@ -91,6 +93,8 @@ public class TextClassificationConstantsTest { .containsExactly("send_email", "call_phone"); assertWithMessage("lang_id_threshold_override") .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(0.3f); + Assert.assertArrayEquals("lang_id_context_settings", + constants.getLangIdContextSettings(), new float[]{10, 1, 0.5f}, EPSILON); } @Test @@ -111,7 +115,8 @@ public class TextClassificationConstantsTest { + "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"; + + "lang_id_threshold_override=2," + + "lang_id_context_settings=30:0.5:0.3"; final TextClassificationConstants constants = TextClassificationConstants.loadFromString(s); @@ -154,6 +159,8 @@ public class TextClassificationConstantsTest { .containsExactly("share_location"); assertWithMessage("lang_id_threshold_override") .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(2f); + Assert.assertArrayEquals("lang_id_context_settings", + constants.getLangIdContextSettings(), new float[]{30, 0.5f, 0.3f}, EPSILON); } @Test @@ -204,5 +211,7 @@ public class TextClassificationConstantsTest { "add_contact", "copy"); assertWithMessage("lang_id_threshold_override") .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(-1f); + Assert.assertArrayEquals("lang_id_context_settings", + constants.getLangIdContextSettings(), new float[]{20, 1, 0.4f}, EPSILON); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index 9ceb989474bd..8de5f137c96c 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -32,6 +32,7 @@ import android.text.SpannableString; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import com.google.common.truth.Truth; @@ -41,7 +42,6 @@ import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import java.util.Arrays; import java.util.Collections; @@ -53,22 +53,10 @@ import java.util.List; * Tests are skipped if such a textclassifier does not exist. */ @SmallTest -@RunWith(Parameterized.class) +@RunWith(AndroidJUnit4.class) public class TextClassifierTest { - private static final String LOCAL = "local"; - private static final String SYSTEM = "system"; - @Parameterized.Parameters(name = "{0}") - public static Iterable<Object> textClassifierTypes() { - return Arrays.asList(LOCAL); - - // TODO: The following will fail on any device that specifies a no-op TextClassifierService. - // Enable when we can set a specified TextClassifierService for testing. - // return Arrays.asList(LOCAL, SYSTEM); - } - - @Parameterized.Parameter - public String mTextClassifierType; + // TODO: Implement TextClassifierService testing. private static final TextClassificationConstants TC_CONSTANTS = TextClassificationConstants.loadFromString(""); @@ -83,8 +71,7 @@ public class TextClassifierTest { public void setup() { mContext = InstrumentationRegistry.getTargetContext(); mTcm = mContext.getSystemService(TextClassificationManager.class); - mClassifier = mTcm.getTextClassifier( - mTextClassifierType.equals(LOCAL) ? TextClassifier.LOCAL : TextClassifier.SYSTEM); + mClassifier = mTcm.getTextClassifier(TextClassifier.LOCAL); } @Test @@ -278,6 +265,8 @@ public class TextClassifierTest { assertEquals("ja", ExtrasUtils.getEntityType(foreignLanguageInfo)); assertTrue(ExtrasUtils.getScore(foreignLanguageInfo) >= 0); assertTrue(ExtrasUtils.getScore(foreignLanguageInfo) <= 1); + assertTrue(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)); + assertEquals("ja", ExtrasUtils.getTopLanguage(intent).getLanguage()); LocaleList.setDefault(originalLocales); } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java new file mode 100644 index 000000000000..011866de4848 --- /dev/null +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TextClassifierUtilsTest { + + @Test + public void testGetSubString() { + final String text = "Yakuza call themselves 任侠団体"; + int start; + int end; + int minimumLength; + + // End index at end of text. + start = text.indexOf("任侠団体"); + end = text.length(); + minimumLength = 20; + assertThat(TextClassifier.Utils.getSubString(text, start, end, minimumLength)) + .isEqualTo("call themselves 任侠団体"); + + // Start index at beginning of text. + start = 0; + end = "Yakuza".length(); + minimumLength = 15; + assertThat(TextClassifier.Utils.getSubString(text, start, end, minimumLength)) + .isEqualTo("Yakuza call themselves"); + + // Text in the middle + start = text.indexOf("all"); + end = start + 1; + minimumLength = 10; + assertThat(TextClassifier.Utils.getSubString(text, start, end, minimumLength)) + .isEqualTo("Yakuza call themselves"); + + // Selection >= minimumLength. + start = text.indexOf("themselves"); + end = start + "themselves".length(); + minimumLength = end - start; + assertThat(TextClassifier.Utils.getSubString(text, start, end, minimumLength)) + .isEqualTo("themselves"); + + // text.length < minimumLength. + minimumLength = text.length() + 1; + assertThat(TextClassifier.Utils.getSubString(text, start, end, minimumLength)) + .isEqualTo(text); + } + + @Test + public void testGetSubString_invalidParams() { + final String text = "The Yoruba regard Olodumare as the principal agent of creation"; + final int length = text.length(); + final int minimumLength = 10; + + // Null text + assertThrows(() -> TextClassifier.Utils.getSubString(null, 0, 1, minimumLength)); + // start > end + assertThrows(() -> TextClassifier.Utils.getSubString(text, 6, 5, minimumLength)); + // start < 0 + assertThrows(() -> TextClassifier.Utils.getSubString(text, -1, 5, minimumLength)); + // end > text.length + assertThrows(() -> TextClassifier.Utils.getSubString(text, 6, length + 1, minimumLength)); + } +} diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java index c8826f01fffa..857408f12049 100644 --- a/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java @@ -23,8 +23,10 @@ import static org.testng.Assert.assertThrows; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Bundle; +import android.view.textclassifier.FakeContextBuilder; +import android.view.textclassifier.TextClassifier; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -41,11 +43,15 @@ public final class LabeledIntentTest { private static final Intent INTENT = new Intent(Intent.ACTION_VIEW).setDataAndNormalize(Uri.parse("http://www.android.com")); private static final int REQUEST_CODE = 42; + private static final Bundle TEXT_LANGUAGES_BUNDLE = Bundle.EMPTY; + private Context mContext; @Before public void setup() { - mContext = InstrumentationRegistry.getTargetContext(); + mContext = new FakeContextBuilder() + .setIntentComponent(Intent.ACTION_VIEW, FakeContextBuilder.DEFAULT_COMPONENT) + .build(); } @Test @@ -58,8 +64,8 @@ public final class LabeledIntentTest { REQUEST_CODE ); - LabeledIntent.Result result = - labeledIntent.resolve(mContext, /*titleChooser*/ null); + LabeledIntent.Result result = labeledIntent.resolve( + mContext, /*titleChooser*/ null, TEXT_LANGUAGES_BUNDLE); assertThat(result).isNotNull(); assertThat(result.remoteAction.getTitle()).isEqualTo(TITLE_WITH_ENTITY); @@ -67,6 +73,7 @@ public final class LabeledIntentTest { Intent intent = result.resolvedIntent; assertThat(intent.getAction()).isEqualTo(intent.getAction()); assertThat(intent.getComponent()).isNotNull(); + assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue(); } @Test @@ -79,8 +86,8 @@ public final class LabeledIntentTest { REQUEST_CODE ); - LabeledIntent.Result result = - labeledIntent.resolve(mContext, /*titleChooser*/ null); + LabeledIntent.Result result = labeledIntent.resolve( + mContext, /*titleChooser*/ null, TEXT_LANGUAGES_BUNDLE); assertThat(result).isNotNull(); assertThat(result.remoteAction.getTitle()).isEqualTo(TITLE_WITHOUT_ENTITY); @@ -100,8 +107,8 @@ public final class LabeledIntentTest { REQUEST_CODE ); - LabeledIntent.Result result = - labeledIntent.resolve(mContext, (labeledIntent1, resolveInfo) -> "chooser"); + LabeledIntent.Result result = labeledIntent.resolve( + mContext, (labeledIntent1, resolveInfo) -> "chooser", TEXT_LANGUAGES_BUNDLE); assertThat(result).isNotNull(); assertThat(result.remoteAction.getTitle()).isEqualTo("chooser"); @@ -121,8 +128,8 @@ public final class LabeledIntentTest { REQUEST_CODE ); - LabeledIntent.Result result = - labeledIntent.resolve(mContext, (labeledIntent1, resolveInfo) -> null); + LabeledIntent.Result result = labeledIntent.resolve( + mContext, (labeledIntent1, resolveInfo) -> null, TEXT_LANGUAGES_BUNDLE); assertThat(result).isNotNull(); assertThat(result.remoteAction.getTitle()).isEqualTo(TITLE_WITHOUT_ENTITY); @@ -148,15 +155,16 @@ public final class LabeledIntentTest { @Test public void resolve_noIntentHandler() { - Intent intent = new Intent("some.thing.does.not.exist"); + // See setup(). mContext can only resolve Intent.ACTION_VIEW. + Intent unresolvableIntent = new Intent(Intent.ACTION_TRANSLATE); LabeledIntent labeledIntent = new LabeledIntent( TITLE_WITHOUT_ENTITY, null, DESCRIPTION, - intent, + unresolvableIntent, REQUEST_CODE); - LabeledIntent.Result result = labeledIntent.resolve(mContext, null); + LabeledIntent.Result result = labeledIntent.resolve(mContext, null, null); assertThat(result).isNull(); } diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java index 13863ed467f7..19e5b0a250bb 100644 --- a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java @@ -77,8 +77,6 @@ public class LegacyIntentClassificationFactoryTest { Intent intent = labeledIntent.intent; assertThat(intent.getAction()).isEqualTo(Intent.ACTION_DEFINE); assertThat(intent.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(TEXT); - assertThat( - intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue(); } @Test diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java index 1dc8f0145b70..eaef0a0c42b1 100644 --- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java @@ -39,7 +39,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.List; @@ -97,12 +96,10 @@ public class TemplateClassificationIntentFactoryTest { assertThat(labeledIntent.titleWithoutEntity).isEqualTo(TITLE_WITHOUT_ENTITY); Intent intent = labeledIntent.intent; assertThat(intent.getAction()).isEqualTo(ACTION); - assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue(); labeledIntent = intents.get(1); intent = labeledIntent.intent; assertThat(intent.getAction()).isEqualTo(Intent.ACTION_TRANSLATE); - assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue(); } @Test @@ -137,7 +134,6 @@ public class TemplateClassificationIntentFactoryTest { assertThat(labeledIntent.titleWithoutEntity).isEqualTo(TITLE_WITHOUT_ENTITY); Intent intent = labeledIntent.intent; assertThat(intent.getAction()).isEqualTo(ACTION); - assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue(); } @Test diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java index 1cc800457f89..6e3de2d378b4 100644 --- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Intent; import android.net.Uri; -import android.view.textclassifier.TextClassifier; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -102,7 +101,6 @@ public class TemplateIntentFactoryTest { assertThat(intent.getPackage()).isNull(); assertThat(intent.getStringExtra(KEY_ONE)).isEqualTo(VALUE_ONE); assertThat(intent.getIntExtra(KEY_TWO, 0)).isEqualTo(VALUE_TWO); - assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue(); } @Test @@ -161,7 +159,6 @@ public class TemplateIntentFactoryTest { assertThat(intent.getFlags()).isEqualTo(0); assertThat(intent.getCategories()).isNull(); assertThat(intent.getPackage()).isNull(); - assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue(); } @Test |