From 1e72ef28933823594bf2b1993fed2d4895b20a67 Mon Sep 17 00:00:00 2001 From: Lan Wei Date: Thu, 29 Oct 2020 16:16:46 +0800 Subject: [PATCH] Avoid bad InputConnection call crashing the app Update InputConnection#getTextBeforeCursor(int, int) and InputConnection#getTextAfterCursor(int, int) API to specify that the paramter {@code n} must be non-negative. If the IME using the API incorrectly, throw IllegalArgumentException. Also update these two APIs that return nullable result (no behaivor change). For editor App, return null for bad case. Test: atest BaseInputConnectionTest#testInvalidGetTextBeforeOrAfterCursorRequest Test: atest InputConnectionWrapperTest#testInvalidGetTextBeforeOrAfterCursorRequest BUG: 169114026 Change-Id: I95169735198f8363c981a61e20234dfebfd645b1 --- api/current.txt | 12 +++++----- .../view/inputmethod/BaseInputConnection.java | 12 ++++++++-- .../view/inputmethod/InputConnection.java | 18 +++++++++++---- .../inputmethod/InputConnectionWrapper.java | 16 +++++++++++-- .../internal/view/InputConnectionWrapper.java | 23 ++++++++++++++----- non-updatable-api/current.txt | 12 +++++----- 6 files changed, 67 insertions(+), 26 deletions(-) diff --git a/api/current.txt b/api/current.txt index 26d77ef4318ef..c792cbe4a5a2e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -57227,8 +57227,8 @@ package android.view.inputmethod { method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int); method public android.os.Handler getHandler(); method public CharSequence getSelectedText(int); - method public CharSequence getTextAfterCursor(int, int); - method public CharSequence getTextBeforeCursor(int, int); + method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int); + method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int); method public boolean performContextMenuAction(int); method public boolean performEditorAction(int); method public boolean performPrivateCommand(String, android.os.Bundle); @@ -57454,8 +57454,8 @@ package android.view.inputmethod { method public android.os.Handler getHandler(); method public CharSequence getSelectedText(int); method @Nullable public default android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int); - method public CharSequence getTextAfterCursor(int, int); - method public CharSequence getTextBeforeCursor(int, int); + method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int); + method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int); method public boolean performContextMenuAction(int); method public boolean performEditorAction(int); method public boolean performPrivateCommand(String, android.os.Bundle); @@ -57489,8 +57489,8 @@ package android.view.inputmethod { method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int); method public android.os.Handler getHandler(); method public CharSequence getSelectedText(int); - method public CharSequence getTextAfterCursor(int, int); - method public CharSequence getTextBeforeCursor(int, int); + method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int); + method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int); method public boolean performContextMenuAction(int); method public boolean performEditorAction(int); method public boolean performPrivateCommand(String, android.os.Bundle); diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 093dfb4e196eb..62b1b1f8cf53e 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -502,7 +502,10 @@ public class BaseInputConnection implements InputConnection { * The default implementation returns the given amount of text from the * current cursor position in the buffer. */ - public CharSequence getTextBeforeCursor(int length, int flags) { + @Nullable + public CharSequence getTextBeforeCursor(@IntRange(from = 0) int length, int flags) { + if (length < 0) return null; + final Editable content = getEditable(); if (content == null) return null; @@ -558,7 +561,10 @@ public class BaseInputConnection implements InputConnection { * The default implementation returns the given amount of text from the * current cursor position in the buffer. */ - public CharSequence getTextAfterCursor(int length, int flags) { + @Nullable + public CharSequence getTextAfterCursor(@IntRange(from = 0) int length, int flags) { + if (length < 0) return null; + final Editable content = getEditable(); if (content == null) return null; @@ -594,6 +600,8 @@ public class BaseInputConnection implements InputConnection { @Nullable public SurroundingText getSurroundingText( @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) { + if (beforeLength < 0 || afterLength < 0) return null; + final Editable content = getEditable(); if (content == null) return null; diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index c7acd298cd208..0fe47b7828d92 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -27,6 +27,8 @@ import android.text.TextUtils; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -186,13 +188,15 @@ public interface InputConnection { * the current line, and specifically do not return 0 characters unless * the cursor is really at the start of the text.

* - * @param n The expected length of the text. + * @param n The expected length of the text. This must be non-negative. * @param flags Supplies additional options controlling how the text is * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}. * @return the text before the cursor position; the length of the * returned text might be less than n. + * @throws IllegalArgumentException if {@code n} is negative. */ - CharSequence getTextBeforeCursor(int n, int flags); + @Nullable + CharSequence getTextBeforeCursor(@IntRange(from = 0) int n, int flags); /** * Get n characters of text after the current cursor @@ -228,14 +232,16 @@ public interface InputConnection { * the current line, and specifically do not return 0 characters unless * the cursor is really at the end of the text.

* - * @param n The expected length of the text. + * @param n The expected length of the text. This must be non-negative. * @param flags Supplies additional options controlling how the text is * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}. * * @return the text after the cursor position; the length of the * returned text might be less than n. + * @throws IllegalArgumentException if {@code n} is negative. */ - CharSequence getTextAfterCursor(int n, int flags); + @Nullable + CharSequence getTextAfterCursor(@IntRange(from = 0) int n, int flags); /** * Gets the selected text, if any. @@ -307,11 +313,15 @@ public interface InputConnection { * editor can't comply with the request for some reason, or the application does not implement * this method. The length of the returned text might be less than the sum of * beforeLength and afterLength . + * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is negative. */ @Nullable default SurroundingText getSurroundingText( @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, @GetTextType int flags) { + Preconditions.checkArgumentNonnegative(beforeLength); + Preconditions.checkArgumentNonnegative(afterLength); + CharSequence textBeforeCursor = getTextBeforeCursor(beforeLength, flags); if (textBeforeCursor == null) { textBeforeCursor = ""; diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index ec7fa60a62ba8..77956d15e399c 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -16,11 +16,14 @@ package android.view.inputmethod; +import android.annotation.IntRange; import android.annotation.Nullable; import android.os.Bundle; import android.os.Handler; import android.view.KeyEvent; +import com.android.internal.util.Preconditions; + /** *

Wrapper class for proxying calls to another InputConnection. Subclass and have fun! */ @@ -74,18 +77,24 @@ public class InputConnectionWrapper implements InputConnection { /** * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. + * @throws IllegalArgumentException if {@code length} is negative. */ + @Nullable @Override - public CharSequence getTextBeforeCursor(int n, int flags) { + public CharSequence getTextBeforeCursor(@IntRange(from = 0) int n, int flags) { + Preconditions.checkArgumentNonnegative(n); return mTarget.getTextBeforeCursor(n, flags); } /** * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. + * @throws IllegalArgumentException if {@code length} is negative. */ + @Nullable @Override - public CharSequence getTextAfterCursor(int n, int flags) { + public CharSequence getTextAfterCursor(@IntRange(from = 0) int n, int flags) { + Preconditions.checkArgumentNonnegative(n); return mTarget.getTextAfterCursor(n, flags); } @@ -101,10 +110,13 @@ public class InputConnectionWrapper implements InputConnection { /** * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. + * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is negative. */ @Nullable @Override public SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) { + Preconditions.checkArgumentNonnegative(beforeLength); + Preconditions.checkArgumentNonnegative(afterLength); return mTarget.getSurroundingText(beforeLength, afterLength, flags); } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index f086dd79758b7..e05aa83516817 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -17,6 +17,7 @@ package com.android.internal.view; import android.annotation.AnyThread; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.inputmethodservice.AbstractInputMethodService; @@ -106,9 +107,13 @@ public class InputConnectionWrapper implements InputConnection { return null; } + /** + * See {@link InputConnection#getTextAfterCursor(int, int)}. + */ + @Nullable @AnyThread - public CharSequence getTextAfterCursor(int length, int flags) { - if (mCancellationGroup.isCanceled()) { + public CharSequence getTextAfterCursor(@IntRange(from = 0) int length, int flags) { + if (length < 0 || mCancellationGroup.isCanceled()) { return null; } @@ -122,9 +127,13 @@ public class InputConnectionWrapper implements InputConnection { return getResultOrNull(value, "getTextAfterCursor()"); } + /** + * See {@link InputConnection#getTextBeforeCursor(int, int)}. + */ + @Nullable @AnyThread - public CharSequence getTextBeforeCursor(int length, int flags) { - if (mCancellationGroup.isCanceled()) { + public CharSequence getTextBeforeCursor(@IntRange(from = 0) int length, int flags) { + if (length < 0 || mCancellationGroup.isCanceled()) { return null; } @@ -171,10 +180,12 @@ public class InputConnectionWrapper implements InputConnection { * not support this protocol. */ @AnyThread - public SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) { - if (mCancellationGroup.isCanceled()) { + public SurroundingText getSurroundingText( + @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) { + if (beforeLength < 0 || afterLength < 0 || mCancellationGroup.isCanceled()) { return null; } + if (isMethodMissing(MissingMethodFlags.GET_SURROUNDING_TEXT)) { // This method is not implemented. return null; diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index 41a9057c005ab..a04af8b2a5a78 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -55346,8 +55346,8 @@ package android.view.inputmethod { method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int); method public android.os.Handler getHandler(); method public CharSequence getSelectedText(int); - method public CharSequence getTextAfterCursor(int, int); - method public CharSequence getTextBeforeCursor(int, int); + method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int); + method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int); method public boolean performContextMenuAction(int); method public boolean performEditorAction(int); method public boolean performPrivateCommand(String, android.os.Bundle); @@ -55573,8 +55573,8 @@ package android.view.inputmethod { method public android.os.Handler getHandler(); method public CharSequence getSelectedText(int); method @Nullable public default android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int); - method public CharSequence getTextAfterCursor(int, int); - method public CharSequence getTextBeforeCursor(int, int); + method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int); + method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int); method public boolean performContextMenuAction(int); method public boolean performEditorAction(int); method public boolean performPrivateCommand(String, android.os.Bundle); @@ -55608,8 +55608,8 @@ package android.view.inputmethod { method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int); method public android.os.Handler getHandler(); method public CharSequence getSelectedText(int); - method public CharSequence getTextAfterCursor(int, int); - method public CharSequence getTextBeforeCursor(int, int); + method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int); + method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int); method public boolean performContextMenuAction(int); method public boolean performEditorAction(int); method public boolean performPrivateCommand(String, android.os.Bundle);