summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Abodunrinwa Toki <toki@google.com> 2019-03-16 17:11:12 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-03-16 17:11:12 +0000
commit6067cfa1a8b59b86354447e5adade23ed25d2e7d (patch)
treead9ca35263483e9de7ca83aed38a566d56a63d4e
parent9211b81399088f8fe9d8ca489589bd5d794cf9e0 (diff)
parent72e17976f38403a05a62d792613dbb296dcc85fe (diff)
Merge "Include language information in intents from the TextClassifier."
-rw-r--r--core/java/android/view/textclassifier/ActionsSuggestionsHelper.java2
-rw-r--r--core/java/android/view/textclassifier/ExtrasUtils.java91
-rw-r--r--core/java/android/view/textclassifier/TextClassificationConstants.java83
-rw-r--r--core/java/android/view/textclassifier/TextClassifier.java48
-rw-r--r--core/java/android/view/textclassifier/TextClassifierImpl.java185
-rw-r--r--core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java4
-rw-r--r--core/java/android/view/textclassifier/intent/LabeledIntent.java36
-rw-r--r--core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java4
-rw-r--r--core/java/android/view/textclassifier/intent/TemplateIntentFactory.java2
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java5
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java13
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java23
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java89
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java34
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java2
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java4
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java3
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