Merge "Adding maximum input size checks"

This commit is contained in:
TreeHugger Robot
2018-02-16 18:46:50 +00:00
committed by Android (Google) Code Review
10 changed files with 126 additions and 11 deletions

View File

@@ -50313,6 +50313,7 @@ package android.view.textclassifier {
method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence);
method public default java.util.Collection<java.lang.String> getEntitiesForPreset(int);
method public default android.view.textclassifier.logging.Logger getLogger(android.view.textclassifier.logging.Logger.Config);
method public default int getMaxGenerateLinksTextLength();
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);

View File

@@ -10457,6 +10457,9 @@ public final class Settings {
* <pre>
* smart_selection_dark_launch (boolean)
* smart_selection_enabled_for_edit_text (boolean)
* suggest_selection_max_range_length (int)
* classify_text_max_range_length (int)
* generate_links_max_text_length (int)
* </pre>
*
* <p>

View File

@@ -644,7 +644,13 @@ public class Linkify {
@Nullable Runnable modifyTextView) {
Preconditions.checkNotNull(text);
Preconditions.checkNotNull(classifier);
final Supplier<TextLinks> supplier = () -> classifier.generateLinks(text, options);
// The input text may exceed the maximum length the text classifier can handle. In such
// cases, we process the text up to the maximum length.
final CharSequence truncatedText = text.subSequence(
0, Math.min(text.length(), classifier.getMaxGenerateLinksTextLength()));
final Supplier<TextLinks> supplier = () -> classifier.generateLinks(truncatedText, options);
final Consumer<TextLinks> consumer = links -> {
if (links.getLinks().isEmpty()) {
if (callback != null) {
@@ -653,7 +659,8 @@ public class Linkify {
return;
}
final TextLinkSpan[] old = text.getSpans(0, text.length(), TextLinkSpan.class);
// Remove spans only for the part of the text we generated links for.
final TextLinkSpan[] old = text.getSpans(0, truncatedText.length(), TextLinkSpan.class);
for (int i = old.length - 1; i >= 0; i--) {
text.removeSpan(old[i]);
}
@@ -662,7 +669,8 @@ public class Linkify {
? null : options.getSpanFactory();
final @TextLinks.ApplyStrategy int applyStrategy = (options == null)
? TextLinks.APPLY_STRATEGY_IGNORE : options.getApplyStrategy();
final @TextLinks.Status int result = links.apply(text, applyStrategy, spanFactory);
final @TextLinks.Status int result = links.apply(text, applyStrategy, spanFactory,
true /*allowPrefix*/);
if (result == TextLinks.STATUS_LINKS_APPLIED) {
if (modifyTextView != null) {
modifyTextView.run();

View File

@@ -121,6 +121,15 @@ final class SystemTextClassifier implements TextClassifier {
return mFallback.generateLinks(text, options);
}
/**
* @inheritDoc
*/
@Override
public int getMaxGenerateLinksTextLength() {
// TODO: retrieve this from the bound service.
return mFallback.getMaxGenerateLinksTextLength();
}
private static final class TextSelectionCallback extends ITextSelectionCallback.Stub {
final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>();

View File

@@ -276,9 +276,11 @@ public interface TextClassifier {
* @param text the text to generate annotations for
* @param options configuration for link generation
*
* @throws IllegalArgumentException if text is null
* @throws IllegalArgumentException if text is null or the text is too long for the
* TextClassifier implementation.
*
* @see #generateLinks(CharSequence)
* @see #getMaxGenerateLinksTextLength()
*/
@WorkerThread
default TextLinks generateLinks(
@@ -299,15 +301,27 @@ public interface TextClassifier {
*
* @param text the text to generate annotations for
*
* @throws IllegalArgumentException if text is null
* @throws IllegalArgumentException if text is null or the text is too long for the
* TextClassifier implementation.
*
* @see #generateLinks(CharSequence, TextLinks.Options)
* @see #getMaxGenerateLinksTextLength()
*/
@WorkerThread
default TextLinks generateLinks(@NonNull CharSequence text) {
return generateLinks(text, null);
}
/**
* Returns the maximal length of text that can be processed by generateLinks.
*
* @see #generateLinks(CharSequence)
* @see #generateLinks(CharSequence, TextLinks.Options)
*/
default int getMaxGenerateLinksTextLength() {
return Integer.MAX_VALUE;
}
/**
* Returns a {@link Collection} of the entity types in the specified preset.
*
@@ -461,6 +475,15 @@ public interface TextClassifier {
checkMainThread(allowInMainThread);
}
/**
* @throws IllegalArgumentException if text is null; the text is too long or options is null
*/
public static void validate(@NonNull CharSequence text, int maxLength,
boolean allowInMainThread) {
validate(text, allowInMainThread);
Preconditions.checkArgumentInRange(text.length(), 0, maxLength, "text.length()");
}
private static void checkMainThread(boolean allowInMainThread) {
if (!allowInMainThread && Looper.myLooper() == Looper.getMainLooper()) {
Slog.w(DEFAULT_LOG_TAG, "TextClassifier called on main thread");

View File

@@ -47,10 +47,19 @@ public final class TextClassifierConstants {
"smart_selection_enabled_for_edit_text";
private static final String SMART_LINKIFY_ENABLED =
"smart_linkify_enabled";
private static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH =
"suggest_selection_max_range_length";
private static final String CLASSIFY_TEXT_MAX_RANGE_LENGTH =
"classify_text_max_range_length";
private static final String GENERATE_LINKS_MAX_TEXT_LENGTH =
"generate_links_max_text_length";
private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false;
private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true;
private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true;
private static final int SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000;
/** Default settings. */
static final TextClassifierConstants DEFAULT = new TextClassifierConstants();
@@ -58,11 +67,17 @@ public final class TextClassifierConstants {
private final boolean mDarkLaunch;
private final boolean mSuggestSelectionEnabledForEditableText;
private final boolean mSmartLinkifyEnabled;
private final int mSuggestSelectionMaxRangeLength;
private final int mClassifyTextMaxRangeLength;
private final int mGenerateLinksMaxTextLength;
private TextClassifierConstants() {
mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT;
mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT;
mSmartLinkifyEnabled = SMART_LINKIFY_ENABLED_DEFAULT;
mSuggestSelectionMaxRangeLength = SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT;
mClassifyTextMaxRangeLength = CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT;
mGenerateLinksMaxTextLength = GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT;
}
private TextClassifierConstants(@Nullable String settings) {
@@ -82,6 +97,15 @@ public final class TextClassifierConstants {
mSmartLinkifyEnabled = parser.getBoolean(
SMART_LINKIFY_ENABLED,
SMART_LINKIFY_ENABLED_DEFAULT);
mSuggestSelectionMaxRangeLength = parser.getInt(
SUGGEST_SELECTION_MAX_RANGE_LENGTH,
SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT);
mClassifyTextMaxRangeLength = parser.getInt(
CLASSIFY_TEXT_MAX_RANGE_LENGTH,
CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT);
mGenerateLinksMaxTextLength = parser.getInt(
GENERATE_LINKS_MAX_TEXT_LENGTH,
GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
}
static TextClassifierConstants loadFromString(String settings) {
@@ -99,4 +123,16 @@ public final class TextClassifierConstants {
public boolean isSmartLinkifyEnabled() {
return mSmartLinkifyEnabled;
}
public int getSuggestSelectionMaxRangeLength() {
return mSuggestSelectionMaxRangeLength;
}
public int getClassifyTextMaxRangeLength() {
return mClassifyTextMaxRangeLength;
}
public int getGenerateLinksMaxTextLength() {
return mGenerateLinksMaxTextLength;
}
}

View File

@@ -128,7 +128,9 @@ public final class TextClassifierImpl implements TextClassifier {
@Nullable TextSelection.Options options) {
Utils.validate(text, selectionStartIndex, selectionEndIndex, false /* allowInMainThread */);
try {
if (text.length() > 0) {
final int rangeLength = selectionEndIndex - selectionStartIndex;
if (text.length() > 0
&& rangeLength <= getSettings().getSuggestSelectionMaxRangeLength()) {
final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
final boolean darkLaunchAllowed = options != null && options.isDarkLaunchAllowed();
final SmartSelection smartSelection = getSmartSelection(locales);
@@ -183,7 +185,8 @@ public final class TextClassifierImpl implements TextClassifier {
@Nullable TextClassification.Options options) {
Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */);
try {
if (text.length() > 0) {
final int rangeLength = endIndex - startIndex;
if (text.length() > 0 && rangeLength <= getSettings().getClassifyTextMaxRangeLength()) {
final String string = text.toString();
final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
final Calendar refTime = (options == null) ? null : options.getReferenceTime();
@@ -207,7 +210,7 @@ public final class TextClassifierImpl implements TextClassifier {
@Override
public TextLinks generateLinks(
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
Utils.validate(text, false /* allowInMainThread */);
Utils.validate(text, getMaxGenerateLinksTextLength(), false /* allowInMainThread */);
final String textString = text.toString();
final TextLinks.Builder builder = new TextLinks.Builder(textString);
@@ -241,6 +244,12 @@ public final class TextClassifierImpl implements TextClassifier {
return mFallback.generateLinks(text, options);
}
/** @inheritDoc */
@Override
public int getMaxGenerateLinksTextLength() {
return getSettings().getGenerateLinksMaxTextLength();
}
@Override
public Collection<String> getEntitiesForPreset(@TextClassifier.EntityPreset int entityPreset) {
switch (entityPreset) {

View File

@@ -108,6 +108,7 @@ public final class TextLinks implements Parcelable {
* @param text the text to apply the links to. Must match the original text
* @param applyStrategy strategy for resolving link conflicts
* @param spanFactory a factory to generate spans from TextLinks. Will use a default if null
* @param allowPrefix whether to allow applying links only to a prefix of the text.
*
* @return a status code indicating whether or not the links were successfully applied
*
@@ -117,10 +118,12 @@ public final class TextLinks implements Parcelable {
public int apply(
@NonNull Spannable text,
@ApplyStrategy int applyStrategy,
@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
@Nullable Function<TextLink, TextLinkSpan> spanFactory,
boolean allowPrefix) {
Preconditions.checkNotNull(text);
checkValidApplyStrategy(applyStrategy);
if (!mFullText.equals(text.toString())) {
final String textString = text.toString();
if (!mFullText.equals(textString) && !(allowPrefix && textString.startsWith(mFullText))) {
return STATUS_DIFFERENT_TEXT;
}
if (mLinks.isEmpty()) {

View File

@@ -19,6 +19,7 @@ package android.view.textclassifier;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.os.LocaleList;
@@ -33,6 +34,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TextClassificationManagerTest {
@@ -183,6 +186,7 @@ public class TextClassificationManagerTest {
@Test
public void testGenerateLinks_none_config() {
if (isTextClassifierDisabled()) return;
String text = "The number is +12122537077. See you tonight!";
assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig(
new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE))),
@@ -209,6 +213,25 @@ public class TextClassificationManagerTest {
TextClassifier.TYPE_ADDRESS));
}
@Test
public void testGenerateLinks_maxLength() {
if (isTextClassifierDisabled()) return;
char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength()];
Arrays.fill(manySpaces, ' ');
TextLinks links = mClassifier.generateLinks(new String(manySpaces), null);
assertTrue(links.getLinks().isEmpty());
}
@Test(expected = IllegalArgumentException.class)
public void testGenerateLinks_tooLong() {
if (isTextClassifierDisabled()) {
throw new IllegalArgumentException("pass if disabled");
}
char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength() + 1];
Arrays.fill(manySpaces, ' ');
mClassifier.generateLinks(new String(manySpaces), null);
}
@Test
public void testSetTextClassifier() {
TextClassifier classifier = mock(TextClassifier.class);

View File

@@ -382,7 +382,7 @@ public class TextViewActivityTest {
TextClassifier textClassifier = textClassificationManager.getTextClassifier();
Spannable content = new SpannableString("Call me at +19148277737");
TextLinks links = textClassifier.generateLinks(content);
links.apply(content, TextLinks.APPLY_STRATEGY_REPLACE, null);
links.apply(content, TextLinks.APPLY_STRATEGY_REPLACE, null, false /* allowPrefix */);
mActivityRule.runOnUiThread(() -> {
textView.setText(content);