diff --git a/core/api/current.txt b/core/api/current.txt index 779cb4411e6ce..4d55abbe4d60b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -51196,6 +51196,7 @@ package android.view.inputmethod { method public boolean performContextMenuAction(int); method public boolean performEditorAction(int); method public boolean performPrivateCommand(String, android.os.Bundle); + method public default boolean performSpellCheck(); method public boolean reportFullscreenMode(boolean); method public boolean requestCursorUpdates(int); method public boolean sendKeyEvent(android.view.KeyEvent); diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 14b5d7c72d2f8..8c81143e4cdc9 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -857,6 +857,20 @@ public interface InputConnection { */ boolean reportFullscreenMode(boolean enabled); + /** + * Have the editor perform spell checking around the current selection. + * + *

The editor can ignore this method call if it does not support spell checking. + * + * @return For editor authors, the return value will always be ignored. For IME authors, this + * method returns true if the spell check request was sent (whether or not the + * associated editor supports spell checking), false if the input connection is no + * longer valid. + */ + default boolean performSpellCheck() { + return false; + } + /** * API to send private commands from an input method to its * connected editor. This can be used to provide domain-specific diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 77956d15e399c..ca853485d4f39 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -282,6 +282,15 @@ public class InputConnectionWrapper implements InputConnection { return mTarget.reportFullscreenMode(enabled); } + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override + public boolean performSpellCheck() { + return mTarget.performSpellCheck(); + } + /** * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index 0611bea7978de..0788635d1c68d 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -215,6 +215,27 @@ public class SpellChecker implements SpellCheckerSessionListener { spellCheck(); } + void onPerformSpellCheck() { + final int selectionStart = mTextView.getSelectionStart(); + final int selectionEnd = mTextView.getSelectionEnd(); + final int selectionRangeStart; + final int selectionRangeEnd; + if (selectionStart < selectionEnd) { + selectionRangeStart = selectionStart; + selectionRangeEnd = selectionEnd; + } else { + selectionRangeStart = selectionEnd; + selectionRangeEnd = selectionStart; + } + // Expand the range so that it (hopefully) includes the current sentence. + final int start = Math.max(0, selectionRangeStart - MIN_SENTENCE_LENGTH); + final int end = Math.min(mTextView.length(), selectionRangeEnd + MIN_SENTENCE_LENGTH); + if (DBG) { + Log.d(TAG, "performSpellCheckAroundSelection: " + start + ", " + end); + } + spellCheck(start, end); + } + public void spellCheck(int start, int end) { if (DBG) { Log.d(TAG, "Start spell-checking: " + start + ", " + end); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 02a9300179062..ffe1b382cfdaa 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8913,6 +8913,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // intentionally empty } + /** @hide */ + public void onPerformSpellCheck() { + if (mEditor != null && mEditor.mSpellChecker != null) { + mEditor.mSpellChecker.onPerformSpellCheck(); + } + } + /** * Called by the framework in response to a private command from the * current method, provided by it calling diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 1d5935db63225..3a7e66ce76f08 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -72,6 +72,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 81; private static final int DO_BEGIN_BATCH_EDIT = 90; private static final int DO_END_BATCH_EDIT = 95; + private static final int DO_PERFORM_SPELL_CHECK = 110; private static final int DO_PERFORM_PRIVATE_COMMAND = 120; private static final int DO_CLEAR_META_KEY_STATES = 130; private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140; @@ -234,6 +235,15 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(obtainMessage(DO_END_BATCH_EDIT)); } + /** + * Dispatches the request for performing spell check. + * + * @see InputConnection#performSpellCheck() + */ + public void performSpellCheck() { + dispatchMessage(obtainMessage(DO_PERFORM_SPELL_CHECK)); + } + public void performPrivateCommand(String action, Bundle data) { dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data)); } @@ -681,6 +691,20 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } return; } + case DO_PERFORM_SPELL_CHECK: { + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#performSpellCheck"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "performSpellCheck on inactive InputConnection"); + return; + } + ic.performSpellCheck(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); + } + return; + } case DO_PERFORM_PRIVATE_COMMAND: { final SomeArgs args = (SomeArgs) msg.obj; Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#performPrivateCommand"); diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index 074908acdf2aa..53cbf961fff4b 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -70,6 +70,8 @@ import com.android.internal.inputmethod.ISurroundingTextResultCallback; void clearMetaKeyStates(int states); + void performSpellCheck(); + void performPrivateCommand(String action, in Bundle data); void setComposingRegion(int start, int end); diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index b70348a953841..af9c0124078a2 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -441,6 +441,17 @@ public class InputConnectionWrapper implements InputConnection { return false; } + @AnyThread + @Override + public boolean performSpellCheck() { + try { + mIInputContext.performSpellCheck(); + return true; + } catch (RemoteException e) { + return false; + } + } + @AnyThread public boolean performPrivateCommand(String action, Bundle data) { try { diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index 6cdf0df17b65c..767ad42efbf3c 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -177,6 +177,12 @@ public class EditableInputConnection extends BaseInputConnection return null; } + @Override + public boolean performSpellCheck() { + mTextView.onPerformSpellCheck(); + return true; + } + @Override public boolean performPrivateCommand(String action, Bundle data) { mTextView.onPrivateIMECommand(action, data);