diff options
12 files changed, 145 insertions, 12 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index ddfbb44e44a6..213ed8337b97 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -52948,6 +52948,7 @@ package android.view.inputmethod { method public default boolean performSpellCheck(); method public boolean reportFullscreenMode(boolean); method public boolean requestCursorUpdates(int); + method public default boolean requestCursorUpdates(int, int); method public boolean sendKeyEvent(android.view.KeyEvent); method public boolean setComposingRegion(int, int); method public default boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute); diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java index 6b7815d0f732..5b0129ee814c 100644 --- a/core/java/android/inputmethodservice/RemoteInputConnection.java +++ b/core/java/android/inputmethodservice/RemoteInputConnection.java @@ -433,6 +433,26 @@ public final class RemoteInputConnection implements InputConnection { mCancellationGroup, MAX_WAIT_TIME_MILLIS); } + @Override + @AnyThread + public boolean requestCursorUpdates(@CursorUpdateMode int cursorUpdateMode, + @CursorUpdateFilter int cursorUpdateFilter) { + if (mCancellationGroup.isCanceled()) { + return false; + } + + final InputMethodServiceInternal ims = mImsInternal.getAndWarnIfNull(); + if (ims == null) { + return false; + } + + final int displayId = ims.getContext().getDisplayId(); + final CompletableFuture<Boolean> value = + mInvoker.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter, displayId); + return CompletableFutureUtil.getResultOrFalse(value, TAG, "requestCursorUpdates()", + mCancellationGroup, MAX_WAIT_TIME_MILLIS); + } + @AnyThread public Handler getHandler() { // Nothing should happen when called from input method. diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java index 8600f55eee70..7f07146776ab 100644 --- a/core/java/android/view/inputmethod/CursorAnchorInfo.java +++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java @@ -575,7 +575,9 @@ public final class CursorAnchorInfo implements Parcelable { } /** - * Returns {@link EditorBoundsInfo} editor related bounds. + * Returns {@link EditorBoundsInfo} for the current editor, or {@code null} if IME is not + * subscribed with {@link InputConnection#CURSOR_UPDATE_FILTER_EDITOR_BOUNDS} + * or {@link InputConnection#CURSOR_UPDATE_MONITOR}. */ @Nullable public EditorBoundsInfo getEditorBoundsInfo() { diff --git a/core/java/android/view/inputmethod/EditorBoundsInfo.java b/core/java/android/view/inputmethod/EditorBoundsInfo.java index 081dc819f10b..df82438b7132 100644 --- a/core/java/android/view/inputmethod/EditorBoundsInfo.java +++ b/core/java/android/view/inputmethod/EditorBoundsInfo.java @@ -30,13 +30,13 @@ import java.util.Objects; public final class EditorBoundsInfo implements Parcelable { /** - * Container of rectangular position of Editor in the current window. + * The bounding box of the of currently focused text editor in local coordinates. */ private final RectF mEditorBounds; /** - * Container of rectangular position of Editor with additional padding around for initiating - * Stylus Handwriting in the current window. + * The bounding box of the of currently focused text editor with additional padding around it + * for initiating Stylus Handwriting in the current window. */ private final RectF mHandwritingBounds; diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 78509fe6e0b5..dac1be6f5a13 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -1043,6 +1043,23 @@ public interface InputConnection { int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 1 << 4; /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {CURSOR_UPDATE_IMMEDIATE, CURSOR_UPDATE_MONITOR}, flag = true, + prefix = { "CURSOR_UPDATE_" }) + @interface CursorUpdateMode{} + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {CURSOR_UPDATE_FILTER_EDITOR_BOUNDS, CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS, + CURSOR_UPDATE_FILTER_INSERTION_MARKER}, flag = true, + prefix = { "CURSOR_UPDATE_FILTER_" }) + @interface CursorUpdateFilter{} + + /** * Called by the input method to ask the editor for calling back * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} to * notify cursor/anchor locations. @@ -1063,6 +1080,34 @@ public interface InputConnection { boolean requestCursorUpdates(int cursorUpdateMode); /** + * Called by the input method to ask the editor for calling back + * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} to + * notify cursor/anchor locations. + * + * @param cursorUpdateMode combination of update modes: + * {@link #CURSOR_UPDATE_IMMEDIATE}, {@link #CURSOR_UPDATE_MONITOR} + * @param cursorUpdateFilter any combination of data filters: + * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}. + * + * <p>Pass {@code 0} to disable them. However, if an unknown flag is provided, request will be + * rejected and method will return {@code false}.</p> + * @return {@code true} if the request is scheduled. {@code false} to indicate that when the + * application will not call {@link InputMethodManager#updateCursorAnchorInfo( + * android.view.View, CursorAnchorInfo)}. + * Since Android {@link android.os.Build.VERSION_CODES#N} until + * {@link android.os.Build.VERSION_CODES#TIRAMISU}, this API returned {@code false} when + * the target application does not implement this method. + */ + default boolean requestCursorUpdates(@CursorUpdateMode int cursorUpdateMode, + @CursorUpdateFilter int cursorUpdateFilter) { + if (cursorUpdateFilter == 0) { + return requestCursorUpdates(cursorUpdateMode); + } + return false; + } + + /** * Called by the system to enable application developers to specify a dedicated thread on which * {@link InputConnection} methods are called back. * diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 27174630bfa3..a8e094b35cd5 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1912,8 +1912,8 @@ public final class InputMethodManager { // TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here. // instead of mDisplayId. mServedInputConnection.requestCursorUpdatesFromImm( - CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR - | CURSOR_UPDATE_FILTER_EDITOR_BOUNDS, + CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR, + CURSOR_UPDATE_FILTER_EDITOR_BOUNDS, mDisplayId); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 1d8e9a36fb3f..231ae084dd6c 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -6180,6 +6180,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } @Override + public boolean requestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter) { + return getTarget().requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter); + } + + @Override public Handler getHandler() { return getTarget().getHandler(); } diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index 630c271d9418..f8b268fbc1f2 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -205,6 +205,13 @@ public final class EditableInputConnection extends BaseInputConnection } @Override + public boolean requestCursorUpdates( + @CursorUpdateMode int cursorUpdateMode, @CursorUpdateFilter int cursorUpdateFilter) { + // TODO(b/210039666): use separate attrs for updateMode and updateFilter. + return requestCursorUpdates(cursorUpdateMode | cursorUpdateFilter); + } + + @Override public boolean requestCursorUpdates(int cursorUpdateMode) { if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode); diff --git a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java index e6fafe0ac56a..e8838c8bfcae 100644 --- a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java +++ b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java @@ -603,6 +603,30 @@ public final class IInputContextInvoker { } /** + * Invokes {@link IInputContext#requestCursorUpdatesWithFilter(InputConnectionCommandHeader, + * int, int, int, AndroidFuture)}. + * + * @param cursorUpdateMode {@code cursorUpdateMode} parameter to be passed. + * @param cursorUpdateFilter {@code cursorUpdateFilter} parameter to be passed. + * @param imeDisplayId the display ID that is associated with the IME. + * @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation + * result. {@link RemoteException} will be treated as an error. + */ + @AnyThread + @NonNull + public AndroidFuture<Boolean> requestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter, + int imeDisplayId) { + final AndroidFuture<Boolean> future = new AndroidFuture<>(); + try { + mIInputContext.requestCursorUpdatesWithFilter(createHeader(), cursorUpdateMode, + cursorUpdateFilter, imeDisplayId, future); + } catch (RemoteException e) { + future.completeExceptionally(e); + } + return future; + } + + /** * Invokes {@link IInputContext#commitContent(InputConnectionCommandHeader, InputContentInfo, * int, Bundle, AndroidFuture)}. * diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index 780de0eb4da2..f81fd376fef0 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -935,17 +935,20 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { * Dispatches {@link InputConnection#requestCursorUpdates(int)}. * * <p>This method is intended to be called only from {@link InputMethodManager}.</p> - * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int)} + * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int, int)} + * @param cursorUpdateFilter the filter for + * {@link InputConnection#requestCursorUpdates(int, int)} * @param imeDisplayId displayId on which IME is displayed. */ @Dispatching(cancellable = true) - public void requestCursorUpdatesFromImm(int cursorUpdateMode, int imeDisplayId) { + public void requestCursorUpdatesFromImm(int cursorUpdateMode, int cursorUpdateFilter, + int imeDisplayId) { final int currentSessionId = mCurrentSessionId.get(); dispatchWithTracing("requestCursorUpdatesFromImm", () -> { if (currentSessionId != mCurrentSessionId.get()) { return; // cancelled } - requestCursorUpdatesInternal(cursorUpdateMode, imeDisplayId); + requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId); }); } @@ -957,11 +960,28 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { if (header.mSessionId != mCurrentSessionId.get()) { return false; // cancelled } - return requestCursorUpdatesInternal(cursorUpdateMode, imeDisplayId); + return requestCursorUpdatesInternal( + cursorUpdateMode, 0 /* cursorUpdateFilter */, imeDisplayId); }); } - private boolean requestCursorUpdatesInternal(int cursorUpdateMode, int imeDisplayId) { + @Dispatching(cancellable = true) + @Override + public void requestCursorUpdatesWithFilter(InputConnectionCommandHeader header, + int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId, + AndroidFuture future /* T=Boolean */) { + dispatchWithTracing("requestCursorUpdates", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return false; // cancelled + } + return requestCursorUpdatesInternal( + cursorUpdateMode, cursorUpdateFilter, imeDisplayId); + }); + } + + private boolean requestCursorUpdatesInternal( + @InputConnection.CursorUpdateMode int cursorUpdateMode, + @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) { final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection"); @@ -972,7 +992,7 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { return false; } try { - return ic.requestCursorUpdates(cursorUpdateMode); + return ic.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter); } catch (AbstractMethodError ignored) { // TODO(b/199934664): See if we can remove this by providing a default impl. return false; diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index 7da0f116c358..909a5379993c 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -98,6 +98,10 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader; void requestCursorUpdates(in InputConnectionCommandHeader header, int cursorUpdateMode, int imeDisplayId, in AndroidFuture future /* T=Boolean */); + void requestCursorUpdatesWithFilter(in InputConnectionCommandHeader header, + int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId, + in AndroidFuture future /* T=Boolean */); + void commitContent(in InputConnectionCommandHeader header, in InputContentInfo inputContentInfo, int flags, in Bundle opts, in AndroidFuture future /* T=Boolean */); diff --git a/core/tests/coretests/src/android/view/ViewInputConnectionTest.java b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java index d60c0c688e1a..fda294c73db3 100644 --- a/core/tests/coretests/src/android/view/ViewInputConnectionTest.java +++ b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java @@ -549,6 +549,11 @@ public class ViewInputConnectionTest { } @Override + public boolean requestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter) { + return false; + } + + @Override public Handler getHandler() { return null; } |