diff options
| author | 2022-04-18 08:59:44 -0700 | |
|---|---|---|
| committer | 2022-04-18 08:59:44 -0700 | |
| commit | c60176c1f315e10c95f3eec0019149d7b7932aa9 (patch) | |
| tree | 8c8348bc4263f5fec9da0baf624d094e91ccf83e | |
| parent | 4008e21f0a9528bb2126d859ed6b6550f988c7c7 (diff) | |
Let A11yIME use its own IPC definitions
This is a follow up CL to our previuos CL [1], which enabled
AccessibilityService to use a subset of InputConnection APIs.
In that CL we have reused existing AIDL interfaces that were designed
and maintained for IMEs for simplicity, where a non trivial amount of
unnecessary IPC endpoints were included.
From the security and maintainability viewpoints, however, exposing
unnecessary IPC endpoints is discouraged in general. To address such
concerns this CL introduces a set of dedicated IPC definitions for
A11yIME so that we do not need to reuse IPCs for IMEs.
This CL also stops passing InputBinding object to A11yIME process as
it contains IInputContext Binder Proxy, which can still be used to
directly invoke fallback InputConnection. This is doable now because
A11yIME no longer relies on fallback InputConnection [2].
This CL is should not have any observable changes in the semantics.
End-to-end CTS tests guarantee that everything is still working as
intended now and in the future.
[1]: Ia651a811093a939d00c081be1961e24ed3ad0356
fb17e5ae7a9e1a095d114d8dde76f14578b6c233
[2]: I2af3cd50444d8ddf25aa0f6479238156914e6fff
dc635efb687ac04045f2756b02c5ca2435762956
Fix: 215633021
Fix: 215636776
Test: atest CtsInputMethodTestCases:AccessibilityInputMethodTest
Test: atest CtsAccessibilityServiceTestCases:AccessibilityInputConnectionTest
Test: atest CtsAccessibilityServiceTestCases:AccessibilityImeTest
Change-Id: I5ff2e804cbcf90828370a0612ff54111130bdff4
24 files changed, 1203 insertions, 414 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityInputMethodSession.java b/core/java/android/accessibilityservice/AccessibilityInputMethodSession.java new file mode 100644 index 000000000000..ecf449d7936a --- /dev/null +++ b/core/java/android/accessibilityservice/AccessibilityInputMethodSession.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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.accessibilityservice; + +import android.view.inputmethod.EditorInfo; + +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; + +interface AccessibilityInputMethodSession { + void finishInput(); + + void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd); + + void invalidateInput(EditorInfo editorInfo, IRemoteAccessibilityInputConnection connection, + int sessionId); + + void setEnabled(boolean enabled); +} diff --git a/core/java/android/accessibilityservice/AccessibilityInputMethodSessionWrapper.java b/core/java/android/accessibilityservice/AccessibilityInputMethodSessionWrapper.java new file mode 100644 index 000000000000..3252ab23486f --- /dev/null +++ b/core/java/android/accessibilityservice/AccessibilityInputMethodSessionWrapper.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 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.accessibilityservice; + +import android.annotation.AnyThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.view.inputmethod.EditorInfo; + +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; + +import java.util.concurrent.atomic.AtomicReference; + +final class AccessibilityInputMethodSessionWrapper extends IAccessibilityInputMethodSession.Stub { + private final Handler mHandler; + + @NonNull + private final AtomicReference<AccessibilityInputMethodSession> mSessionRef; + + AccessibilityInputMethodSessionWrapper( + @NonNull Looper looper, @NonNull AccessibilityInputMethodSession session) { + mSessionRef = new AtomicReference<>(session); + mHandler = Handler.createAsync(looper); + } + + @AnyThread + @Nullable + AccessibilityInputMethodSession getSession() { + return mSessionRef.get(); + } + + @Override + public void updateSelection(int oldSelStart, int oldSelEnd, + int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { + if (mHandler.getLooper().isCurrentThread()) { + doUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, + candidatesEnd); + } else { + mHandler.post(() -> doUpdateSelection(oldSelStart, oldSelEnd, newSelStart, + newSelEnd, candidatesStart, candidatesEnd)); + } + } + + private void doUpdateSelection(int oldSelStart, int oldSelEnd, + int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { + final AccessibilityInputMethodSession session = mSessionRef.get(); + if (session != null) { + session.updateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, + candidatesEnd); + } + } + + @Override + public void finishInput() { + if (mHandler.getLooper().isCurrentThread()) { + doFinishInput(); + } else { + mHandler.post(this::doFinishInput); + } + } + + private void doFinishInput() { + final AccessibilityInputMethodSession session = mSessionRef.get(); + if (session != null) { + session.finishInput(); + } + } + + @Override + public void finishSession() { + if (mHandler.getLooper().isCurrentThread()) { + doFinishSession(); + } else { + mHandler.post(this::doFinishSession); + } + } + + private void doFinishSession() { + mSessionRef.set(null); + } + + @Override + public void invalidateInput(EditorInfo editorInfo, + IRemoteAccessibilityInputConnection connection, int sessionId) { + if (mHandler.getLooper().isCurrentThread()) { + doInvalidateInput(editorInfo, connection, sessionId); + } else { + mHandler.post(() -> doInvalidateInput(editorInfo, connection, sessionId)); + } + } + + private void doInvalidateInput(EditorInfo editorInfo, + IRemoteAccessibilityInputConnection connection, int sessionId) { + final AccessibilityInputMethodSession session = mSessionRef.get(); + if (session != null) { + session.invalidateInput(editorInfo, connection, sessionId); + } + } +} diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 3cb04e710d3b..c17fbf19516b 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -40,8 +40,6 @@ import android.graphics.ParcelableColorSpace; import android.graphics.Region; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; -import android.inputmethodservice.IInputMethodSessionWrapper; -import android.inputmethodservice.RemoteInputConnection; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -68,22 +66,19 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityWindowInfo; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputBinding; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodSession; import com.android.internal.inputmethod.CancellationGroup; +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +import com.android.internal.inputmethod.RemoteAccessibilityInputConnection; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.internal.view.IInputContext; -import com.android.internal.view.IInputMethodSession; -import com.android.internal.view.IInputSessionWithIdCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; @@ -639,20 +634,10 @@ public abstract class AccessibilityService extends Service { /** This is called when the system action list is changed. */ void onSystemActionsChanged(); /** This is called when an app requests ime sessions or when the service is enabled. */ - void createImeSession(IInputSessionWithIdCallback callback); - /** - * This is called when InputMethodManagerService requests to set the session enabled or - * disabled - */ - void setImeSessionEnabled(InputMethodSession session, boolean enabled); - /** This is called when an app binds input or when the service is enabled. */ - void bindInput(InputBinding binding); - /** This is called when an app unbinds input or when the service is disabled. */ - void unbindInput(); + void createImeSession(IAccessibilityInputMethodSessionCallback callback); /** This is called when an app starts input or when the service is enabled. */ - void startInput(@Nullable InputConnection inputConnection, - @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken); + void startInput(@Nullable RemoteAccessibilityInputConnection inputConnection, + @NonNull EditorInfo editorInfo, boolean restarting); } /** @@ -2740,42 +2725,20 @@ public abstract class AccessibilityService extends Service { } @Override - public void createImeSession(IInputSessionWithIdCallback callback) { + public void createImeSession(IAccessibilityInputMethodSessionCallback callback) { if (mInputMethod != null) { mInputMethod.createImeSession(callback); } } @Override - public void setImeSessionEnabled(InputMethodSession session, boolean enabled) { - if (mInputMethod != null) { - mInputMethod.setImeSessionEnabled(session, enabled); - } - } - - @Override - public void bindInput(InputBinding binding) { - if (mInputMethod != null) { - mInputMethod.bindInput(binding); - } - } - - @Override - public void unbindInput() { - if (mInputMethod != null) { - mInputMethod.unbindInput(); - } - } - - @Override - public void startInput(@Nullable InputConnection inputConnection, - @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken) { + public void startInput(@Nullable RemoteAccessibilityInputConnection connection, + @NonNull EditorInfo editorInfo, boolean restarting) { if (mInputMethod != null) { if (restarting) { - mInputMethod.restartInput(inputConnection, editorInfo); + mInputMethod.restartInput(connection, editorInfo); } else { - mInputMethod.startInput(inputConnection, editorInfo); + mInputMethod.startInput(connection, editorInfo); } } } @@ -2806,8 +2769,6 @@ public abstract class AccessibilityService extends Service { private static final int DO_ON_SYSTEM_ACTIONS_CHANGED = 14; private static final int DO_CREATE_IME_SESSION = 15; private static final int DO_SET_IME_SESSION_ENABLED = 16; - private static final int DO_BIND_INPUT = 17; - private static final int DO_UNBIND_INPUT = 18; private static final int DO_START_INPUT = 19; private final HandlerCaller mCaller; @@ -2818,15 +2779,14 @@ public abstract class AccessibilityService extends Service { private int mConnectionId = AccessibilityInteractionClient.NO_ID; /** - * This is not {@null} only between {@link #bindInput(InputBinding)} and - * {@link #unbindInput()} so that {@link RemoteInputConnection} can query if - * {@link #unbindInput()} has already been called or not, mainly to avoid unnecessary - * blocking operations. + * This is not {@code null} only between {@link #bindInput()} and {@link #unbindInput()} so + * that {@link RemoteAccessibilityInputConnection} can query if {@link #unbindInput()} has + * already been called or not, mainly to avoid unnecessary blocking operations. * * <p>This field must be set and cleared only from the binder thread(s), where the system - * guarantees that {@link #bindInput(InputBinding)}, - * {@link #startInput(IBinder, IInputContext, EditorInfo, boolean)}, and - * {@link #unbindInput()} are called with the same order as the original calls + * guarantees that {@link #bindInput()}, + * {@link #startInput(IRemoteAccessibilityInputConnection, EditorInfo, boolean)}, + * and {@link #unbindInput()} are called with the same order as the original calls * in {@link com.android.server.inputmethod.InputMethodManagerService}. * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p> */ @@ -2927,7 +2887,7 @@ public abstract class AccessibilityService extends Service { } /** This is called when an app requests ime sessions or when the service is enabled. */ - public void createImeSession(IInputSessionWithIdCallback callback) { + public void createImeSession(IAccessibilityInputMethodSessionCallback callback) { final Message message = mCaller.obtainMessageO(DO_CREATE_IME_SESSION, callback); mCaller.sendMessage(message); } @@ -2936,10 +2896,11 @@ public abstract class AccessibilityService extends Service { * This is called when InputMethodManagerService requests to set the session enabled or * disabled */ - public void setImeSessionEnabled(IInputMethodSession session, boolean enabled) { + public void setImeSessionEnabled(IAccessibilityInputMethodSession session, + boolean enabled) { try { - InputMethodSession ls = ((IInputMethodSessionWrapper) - session).getInternalInputMethodSession(); + AccessibilityInputMethodSession ls = + ((AccessibilityInputMethodSessionWrapper) session).getSession(); if (ls == null) { Log.w(LOG_TAG, "Session is already finished: " + session); return; @@ -2952,17 +2913,11 @@ public abstract class AccessibilityService extends Service { } /** This is called when an app binds input or when the service is enabled. */ - public void bindInput(InputBinding binding) { + public void bindInput() { if (mCancellationGroup != null) { Log.e(LOG_TAG, "bindInput must be paired with unbindInput."); } mCancellationGroup = new CancellationGroup(); - InputConnection ic = new RemoteInputConnection(new WeakReference<>(() -> mContext), - IInputContext.Stub.asInterface(binding.getConnectionToken()), - mCancellationGroup); - InputBinding nu = new InputBinding(ic, binding); - final Message message = mCaller.obtainMessageO(DO_BIND_INPUT, nu); - mCaller.sendMessage(message); } /** This is called when an app unbinds input or when the service is disabled. */ @@ -2974,18 +2929,17 @@ public abstract class AccessibilityService extends Service { } else { Log.e(LOG_TAG, "unbindInput must be paired with bindInput."); } - mCaller.sendMessage(mCaller.obtainMessage(DO_UNBIND_INPUT)); } /** This is called when an app starts input or when the service is enabled. */ - public void startInput(IBinder startInputToken, IInputContext inputContext, + public void startInput(IRemoteAccessibilityInputConnection connection, EditorInfo editorInfo, boolean restarting) { if (mCancellationGroup == null) { Log.e(LOG_TAG, "startInput must be called after bindInput."); mCancellationGroup = new CancellationGroup(); } - final Message message = mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken, - inputContext, editorInfo, mCancellationGroup, restarting ? 1 : 0, + final Message message = mCaller.obtainMessageOOOOII(DO_START_INPUT, null /* unused */, + connection, editorInfo, mCancellationGroup, restarting ? 1 : 0, 0 /* unused */); mCaller.sendMessage(message); } @@ -3157,44 +3111,33 @@ public abstract class AccessibilityService extends Service { } case DO_CREATE_IME_SESSION: { if (mConnectionId != AccessibilityInteractionClient.NO_ID) { - IInputSessionWithIdCallback callback = - (IInputSessionWithIdCallback) message.obj; + IAccessibilityInputMethodSessionCallback callback = + (IAccessibilityInputMethodSessionCallback) message.obj; mCallback.createImeSession(callback); } return; } case DO_SET_IME_SESSION_ENABLED: { if (mConnectionId != AccessibilityInteractionClient.NO_ID) { - mCallback.setImeSessionEnabled((InputMethodSession) message.obj, - message.arg1 != 0); - } - return; - } - case DO_BIND_INPUT: { - if (mConnectionId != AccessibilityInteractionClient.NO_ID) { - mCallback.bindInput((InputBinding) message.obj); - } - return; - } - case DO_UNBIND_INPUT: { - if (mConnectionId != AccessibilityInteractionClient.NO_ID) { - mCallback.unbindInput(); + AccessibilityInputMethodSession session = + (AccessibilityInputMethodSession) message.obj; + session.setEnabled(message.arg1 != 0); } return; } case DO_START_INPUT: { if (mConnectionId != AccessibilityInteractionClient.NO_ID) { final SomeArgs args = (SomeArgs) message.obj; - final IBinder startInputToken = (IBinder) args.arg1; - final IInputContext inputContext = (IInputContext) args.arg2; + final IRemoteAccessibilityInputConnection connection = + (IRemoteAccessibilityInputConnection) args.arg2; final EditorInfo info = (EditorInfo) args.arg3; final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; final boolean restarting = args.argi5 == 1; - final InputConnection ic = inputContext != null - ? new RemoteInputConnection(new WeakReference<>(() -> mContext), - inputContext, cancellationGroup) : null; + final RemoteAccessibilityInputConnection ic = connection == null ? null + : new RemoteAccessibilityInputConnection( + connection, cancellationGroup); info.makeCompatible(mContext.getApplicationInfo().targetSdkVersion); - mCallback.startInput(ic, info, restarting, startInputToken); + mCallback.startInput(ic, info, restarting); args.recycle(); } return; diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl index 94da61f82d29..3bc61e560d8c 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -25,10 +25,9 @@ import android.accessibilityservice.MagnificationConfig; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputBinding; -import com.android.internal.view.IInputContext; -import com.android.internal.view.IInputMethodSession; -import com.android.internal.view.IInputSessionWithIdCallback; +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; /** * Top-level interface to an accessibility service component. @@ -69,14 +68,14 @@ import com.android.internal.view.IInputSessionWithIdCallback; void onSystemActionsChanged(); - void createImeSession(IInputSessionWithIdCallback callback); + void createImeSession(in IAccessibilityInputMethodSessionCallback callback); - void setImeSessionEnabled(IInputMethodSession session, boolean enabled); + void setImeSessionEnabled(in IAccessibilityInputMethodSession session, boolean enabled); - void bindInput(in InputBinding binding); + void bindInput(); void unbindInput(); - void startInput(in IBinder startInputToken, in IInputContext inputContext, - in EditorInfo editorInfo, boolean restarting); + void startInput(in IRemoteAccessibilityInputConnection connection, in EditorInfo editorInfo, + boolean restarting); } diff --git a/core/java/android/accessibilityservice/InputMethod.java b/core/java/android/accessibilityservice/InputMethod.java index 647a26637687..2cd145000626 100644 --- a/core/java/android/accessibilityservice/InputMethod.java +++ b/core/java/android/accessibilityservice/InputMethod.java @@ -18,36 +18,23 @@ package android.accessibilityservice; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import android.annotation.CallbackExecutor; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.graphics.Rect; -import android.inputmethodservice.IInputMethodSessionWrapper; -import android.inputmethodservice.RemoteInputConnection; -import android.os.Bundle; import android.os.RemoteException; import android.os.Trace; import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSession; import android.view.inputmethod.SurroundingText; import android.view.inputmethod.TextAttribute; -import com.android.internal.view.IInputContext; -import com.android.internal.view.IInputSessionWithIdCallback; - -import java.util.concurrent.Executor; +import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +import com.android.internal.inputmethod.RemoteAccessibilityInputConnection; /** * This class provides input method APIs. Some public methods such as @@ -61,9 +48,8 @@ public class InputMethod { private static final String LOG_TAG = "A11yInputMethod"; private final AccessibilityService mService; - private InputBinding mInputBinding; private boolean mInputStarted; - private InputConnection mStartedInputConnection; + private RemoteAccessibilityInputConnection mStartedInputConnection; private EditorInfo mInputEditorInfo; /** @@ -131,9 +117,7 @@ public class InputMethod { * to perform whatever behavior you would like. */ public void onFinishInput() { - if (mStartedInputConnection != null) { - mStartedInputConnection.finishComposingText(); - } + // Intentionally empty } /** @@ -152,41 +136,26 @@ public class InputMethod { // Intentionally empty } - final void createImeSession(IInputSessionWithIdCallback callback) { - InputMethodSession session = onCreateInputMethodSessionInterface(); + final void createImeSession(IAccessibilityInputMethodSessionCallback callback) { + final AccessibilityInputMethodSessionWrapper wrapper = + new AccessibilityInputMethodSessionWrapper(mService.getMainLooper(), + new SessionImpl()); try { - IInputMethodSessionWrapper wrap = - new IInputMethodSessionWrapper(mService, session, null); - callback.sessionCreated(wrap, mService.getConnectionId()); + callback.sessionCreated(wrapper, mService.getConnectionId()); } catch (RemoteException ignored) { } } - final void setImeSessionEnabled(@NonNull InputMethodSession session, boolean enabled) { - ((InputMethodSessionForAccessibility) session).setEnabled(enabled); - } - - final void bindInput(@NonNull InputBinding binding) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.bindInput"); - mInputBinding = binding; - Log.v(LOG_TAG, "bindInput(): binding=" + binding); - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - - final void unbindInput() { - Log.v(LOG_TAG, "unbindInput(): binding=" + mInputBinding); - // Unbind input is per process per display. - mInputBinding = null; - } - - final void startInput(@Nullable InputConnection ic, @NonNull EditorInfo attribute) { + final void startInput(@Nullable RemoteAccessibilityInputConnection ic, + @NonNull EditorInfo attribute) { Log.v(LOG_TAG, "startInput(): editor=" + attribute); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.startInput"); doStartInput(ic, attribute, false /* restarting */); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } - final void restartInput(@Nullable InputConnection ic, @NonNull EditorInfo attribute) { + final void restartInput(@Nullable RemoteAccessibilityInputConnection ic, + @NonNull EditorInfo attribute) { Log.v(LOG_TAG, "restartInput(): editor=" + attribute); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.restartInput"); doStartInput(ic, attribute, true /* restarting */); @@ -194,7 +163,8 @@ public class InputMethod { } - final void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { + final void doStartInput(RemoteAccessibilityInputConnection ic, EditorInfo attribute, + boolean restarting) { if ((ic == null || !restarting) && mInputStarted) { doFinishInput(); if (ic == null) { @@ -220,17 +190,13 @@ public class InputMethod { mInputEditorInfo = null; } - private InputMethodSession onCreateInputMethodSessionInterface() { - return new InputMethodSessionForAccessibility(); - } - /** * This class provides the allowed list of {@link InputConnection} APIs for * accessibility services. */ public final class AccessibilityInputConnection { - private InputConnection mIc; - AccessibilityInputConnection(InputConnection ic) { + private final RemoteAccessibilityInputConnection mIc; + AccessibilityInputConnection(RemoteAccessibilityInputConnection ic) { this.mIc = ic; } @@ -249,7 +215,7 @@ public class InputMethod { * int, int)} on the current accessibility service after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the accessibility service by calling - * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, + * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)}, * but be careful to wait until the batch edit is over if one is * in progress.</p> * @@ -282,7 +248,7 @@ public class InputMethod { * int,int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling - * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, + * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)}, * but be careful to wait until the batch edit is over if one is * in progress.</p> * @@ -367,9 +333,8 @@ public class InputMethod { * delete only half of a surrogate pair. Also take care not to * delete more characters than are in the editor, as that may have * ill effects on the application. Calling this method will cause - * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, - * int, int)} on your service after the batch input is over.</p> + * the editor to call {@link InputMethod#onUpdateSelection(int, int, int, int, int, int)} + * on your service after the batch input is over.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change @@ -381,7 +346,7 @@ public class InputMethod { * indices to the size of the contents to avoid crashes. Since * this changes the contents of the editor, you need to make the * changes known to the input method by calling - * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, + * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)}, * but be careful to wait until the batch edit is over if one is * in progress.</p> * @@ -522,12 +487,13 @@ public class InputMethod { } /** - * Concrete implementation of InputMethodSession that provides all of the standard behavior - * for an input method session. + * Concrete implementation of {@link AccessibilityInputMethodSession} that provides all of the + * standard behavior for an A11y input method session. */ - private final class InputMethodSessionForAccessibility implements InputMethodSession { + private final class SessionImpl implements AccessibilityInputMethodSession { boolean mEnabled = true; + @Override public void setEnabled(boolean enabled) { mEnabled = enabled; } @@ -549,86 +515,15 @@ public class InputMethod { } @Override - public void viewClicked(boolean focusChanged) { - } - - @Override - public void updateCursor(@NonNull Rect newCursor) { - } - - @Override - public void displayCompletions( - @SuppressLint("ArrayReturn") @NonNull CompletionInfo[] completions) { - } - - @Override - public void updateExtractedText(int token, @NonNull ExtractedText text) { - } - - public void dispatchKeyEvent(int seq, @NonNull KeyEvent event, - @NonNull @CallbackExecutor Executor executor, @NonNull EventCallback callback) { - } - - @Override - public void dispatchKeyEvent(int seq, @NonNull KeyEvent event, - @NonNull EventCallback callback) { - } - - public void dispatchTrackballEvent(int seq, @NonNull MotionEvent event, - @NonNull @CallbackExecutor Executor executor, @NonNull EventCallback callback) { - } - - @Override - public void dispatchTrackballEvent(int seq, @NonNull MotionEvent event, - @NonNull EventCallback callback) { - } - - public void dispatchGenericMotionEvent(int seq, @NonNull MotionEvent event, - @NonNull @CallbackExecutor Executor executor, @NonNull EventCallback callback) { - } - - @Override - public void dispatchGenericMotionEvent(int seq, @NonNull MotionEvent event, - @NonNull EventCallback callback) { - } - - @Override - public void appPrivateCommand(@NonNull String action, @NonNull Bundle data) { - } - - @Override - public void toggleSoftInput(int showFlags, int hideFlags) { - } - - @Override - public void updateCursorAnchorInfo(@NonNull CursorAnchorInfo cursorAnchorInfo) { - } - - @Override - public void notifyImeHidden() { - } - - @Override - public void removeImeSurface() { - } - - /** - * {@inheritDoc} - */ - @Override - public void invalidateInputInternal(EditorInfo editorInfo, IInputContext inputContext, - int sessionId) { - if (mStartedInputConnection instanceof RemoteInputConnection) { - final RemoteInputConnection ric = - (RemoteInputConnection) mStartedInputConnection; - if (!ric.isSameConnection(inputContext)) { - // This is not an error, and can be safely ignored. - return; - } - editorInfo.makeCompatible( - mService.getApplicationInfo().targetSdkVersion); - restartInput(new RemoteInputConnection(ric, sessionId), editorInfo); + public void invalidateInput(EditorInfo editorInfo, + IRemoteAccessibilityInputConnection connection, int sessionId) { + if (!mStartedInputConnection.isSameConnection(connection)) { + // This is not an error, and can be safely ignored. + return; } + editorInfo.makeCompatible(mService.getApplicationInfo().targetSdkVersion); + restartInput(new RemoteAccessibilityInputConnection(mStartedInputConnection, sessionId), + editorInfo); } } } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 893dc2f6ace4..ac6759396c8f 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -64,13 +64,11 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputBinding; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodSession; import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; +import com.android.internal.inputmethod.RemoteAccessibilityInputConnection; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.internal.view.IInputSessionWithIdCallback; import libcore.io.IoUtils; @@ -1574,26 +1572,14 @@ public final class UiAutomation { } @Override - public void createImeSession(IInputSessionWithIdCallback callback) { + public void createImeSession(IAccessibilityInputMethodSessionCallback callback) { /* do nothing */ } @Override - public void setImeSessionEnabled(InputMethodSession session, boolean enabled) { - } - - @Override - public void bindInput(InputBinding binding) { - } - - @Override - public void unbindInput() { - } - - @Override - public void startInput(@Nullable InputConnection inputConnection, - @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken) { + public void startInput( + @Nullable RemoteAccessibilityInputConnection inputConnection, + @NonNull EditorInfo editorInfo, boolean restarting) { } @Override diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index 75356d1ce994..eccbb403b306 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -41,9 +41,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodSession; -/** @hide */ -// TODO(b/215636776): move IInputMethodSessionWrapper to proper package -public class IInputMethodSessionWrapper extends IInputMethodSession.Stub +class IInputMethodSessionWrapper extends IInputMethodSession.Stub implements HandlerCaller.Callback { private static final String TAG = "InputMethodWrapper"; diff --git a/core/java/android/inputmethodservice/InputMethodServiceInternal.java b/core/java/android/inputmethodservice/InputMethodServiceInternal.java index 09dbb27359b0..f44f49d7dcaf 100644 --- a/core/java/android/inputmethodservice/InputMethodServiceInternal.java +++ b/core/java/android/inputmethodservice/InputMethodServiceInternal.java @@ -32,11 +32,8 @@ import java.io.PrintWriter; * framework classes for internal use. * * <p>CAVEATS: {@link AbstractInputMethodService} does not support all the methods here.</p> - * - * @hide */ -// TODO(b/215636776): move InputMethodServiceInternal to proper package -public interface InputMethodServiceInternal { +interface InputMethodServiceInternal { /** * @return {@link Context} associated with the service. */ diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java index 5b0129ee814c..86e59e9dcf2f 100644 --- a/core/java/android/inputmethodservice/RemoteInputConnection.java +++ b/core/java/android/inputmethodservice/RemoteInputConnection.java @@ -53,11 +53,8 @@ import java.util.concurrent.CompletableFuture; * * <p>See also {@link IInputContext} for the actual {@link android.os.Binder} IPC protocols under * the hood.</p> - * - * @hide */ -// TODO(b/215636776): move RemoteInputConnection to proper package -public final class RemoteInputConnection implements InputConnection { +final class RemoteInputConnection implements InputConnection { private static final String TAG = "RemoteInputConnection"; private static final int MAX_WAIT_TIME_MILLIS = 2000; @@ -98,7 +95,7 @@ public final class RemoteInputConnection implements InputConnection { @NonNull private final CancellationGroup mCancellationGroup; - public RemoteInputConnection( + RemoteInputConnection( @NonNull WeakReference<InputMethodServiceInternal> inputMethodService, IInputContext inputContext, @NonNull CancellationGroup cancellationGroup) { mImsInternal = new InputMethodServiceInternalHolder(inputMethodService); @@ -111,7 +108,7 @@ public final class RemoteInputConnection implements InputConnection { return mInvoker.isSameConnection(inputContext); } - public RemoteInputConnection(@NonNull RemoteInputConnection original, int sessionId) { + RemoteInputConnection(@NonNull RemoteInputConnection original, int sessionId) { mImsInternal = original.mImsInternal; mInvoker = original.mInvoker.cloneWithSessionId(sessionId); mCancellationGroup = original.mCancellationGroup; diff --git a/core/java/android/view/inputmethod/IAccessibilityInputMethodSessionInvoker.java b/core/java/android/view/inputmethod/IAccessibilityInputMethodSessionInvoker.java new file mode 100644 index 000000000000..240e38b107a5 --- /dev/null +++ b/core/java/android/view/inputmethod/IAccessibilityInputMethodSessionInvoker.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 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.inputmethod; + +import android.annotation.AnyThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; + +final class IAccessibilityInputMethodSessionInvoker { + private static final String TAG = "IAccessibilityInputMethodSessionInvoker"; + + /** + * The actual instance of the method to make calls on it. + */ + @NonNull + private final IAccessibilityInputMethodSession mSession; + + private IAccessibilityInputMethodSessionInvoker( + @NonNull IAccessibilityInputMethodSession session) { + mSession = session; + } + + /** + * Create a {@link IAccessibilityInputMethodSessionInvoker} instance if applicable. + * + * @param session {@link IAccessibilityInputMethodSession} object to be wrapped. + * @return an instance of {@link IAccessibilityInputMethodSessionInvoker} if + * {@code inputMethodSession} is not {@code null}. {@code null} otherwise. + */ + @Nullable + public static IAccessibilityInputMethodSessionInvoker createOrNull( + @NonNull IAccessibilityInputMethodSession session) { + return session == null ? null : new IAccessibilityInputMethodSessionInvoker(session); + } + + @AnyThread + void finishInput() { + try { + mSession.finishInput(); + } catch (RemoteException e) { + Log.w(TAG, "A11yIME died", e); + } + } + + @AnyThread + void updateSelection(int oldSelStart, int oldSelEnd, int selStart, int selEnd, + int candidatesStart, int candidatesEnd) { + try { + mSession.updateSelection( + oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd); + } catch (RemoteException e) { + Log.w(TAG, "A11yIME died", e); + } + } + + @AnyThread + void invalidateInput(EditorInfo editorInfo, IRemoteAccessibilityInputConnection connection, + int sessionId) { + try { + mSession.invalidateInput(editorInfo, connection, sessionId); + } catch (RemoteException e) { + Log.w(TAG, "A11yIME died", e); + } + } +} diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 805f8e7551a5..84f13930e03a 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -93,6 +93,7 @@ import android.view.autofill.AutofillManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.DirectBootAwareness; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodDebug; @@ -506,8 +507,8 @@ public final class InputMethodManager { */ @Nullable @GuardedBy("mH") - private final SparseArray<InputMethodSessionWrapper> mAccessibilityInputMethodSession = - new SparseArray<>(); + private final SparseArray<IAccessibilityInputMethodSessionInvoker> + mAccessibilityInputMethodSession = new SparseArray<>(); InputChannel mCurChannel; ImeInputEventSender mCurSender; @@ -669,7 +670,8 @@ public final class InputMethodManager { if (mCurrentInputMethodSession != null) { mCurrentInputMethodSession.finishInput(); } - forAccessibilitySessions(InputMethodSessionWrapper::finishInput); + forAccessibilitySessionsLocked( + IAccessibilityInputMethodSessionInvoker::finishInput); } } @@ -730,7 +732,7 @@ public final class InputMethodManager { focusedView.getWindowToken(), startInputFlags, softInputMode, windowFlags, null, - null, + null, null, mCurRootView.mContext.getApplicationInfo().targetSdkVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -963,13 +965,13 @@ public final class InputMethodManager { // we send a notification so that the a11y service knows the session is // registered and update the a11y service with the current cursor positions. if (res.accessibilitySessions != null) { - InputMethodSessionWrapper wrapper = - InputMethodSessionWrapper.createOrNull( + IAccessibilityInputMethodSessionInvoker invoker = + IAccessibilityInputMethodSessionInvoker.createOrNull( res.accessibilitySessions.get(id)); - if (wrapper != null) { - mAccessibilityInputMethodSession.put(id, wrapper); + if (invoker != null) { + mAccessibilityInputMethodSession.put(id, invoker); if (mServedInputConnection != null) { - wrapper.updateSelection(mInitialSelStart, mInitialSelEnd, + invoker.updateSelection(mInitialSelStart, mInitialSelEnd, mCursorSelStart, mCursorSelEnd, mCursorCandStart, mCursorCandEnd); } else { @@ -978,9 +980,7 @@ public final class InputMethodManager { // binds before or after input starts, it may wonder if it binds // after input starts, why it doesn't receive a notification of // the current cursor positions. - wrapper.updateSelection(-1, -1, - -1, -1, -1, - -1); + invoker.updateSelection(-1, -1, -1, -1, -1, -1); } } } @@ -2148,8 +2148,10 @@ public final class InputMethodManager { editorInfo.setInitialSurroundingTextInternal(textSnapshot.getSurroundingText()); mCurrentInputMethodSession.invalidateInput(editorInfo, mServedInputConnection, sessionId); - forAccessibilitySessions(wrapper -> wrapper.invalidateInput(editorInfo, - mServedInputConnection, sessionId)); + final IRemoteAccessibilityInputConnection accessibilityInputConnection = + mServedInputConnection.asIRemoteAccessibilityInputConnection(); + forAccessibilitySessionsLocked(wrapper -> wrapper.invalidateInput(editorInfo, + accessibilityInputConnection, sessionId)); return true; } } @@ -2323,6 +2325,8 @@ public final class InputMethodManager { res = mService.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, startInputFlags, softInputMode, windowFlags, tba, servedInputConnection, + servedInputConnection == null ? null + : servedInputConnection.asIRemoteAccessibilityInputConnection(), view.getContext().getApplicationInfo().targetSdkVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2347,8 +2351,9 @@ public final class InputMethodManager { mAccessibilityInputMethodSession.clear(); if (res.accessibilitySessions != null) { for (int i = 0; i < res.accessibilitySessions.size(); i++) { - InputMethodSessionWrapper wrapper = InputMethodSessionWrapper.createOrNull( - res.accessibilitySessions.valueAt(i)); + IAccessibilityInputMethodSessionInvoker wrapper = + IAccessibilityInputMethodSessionInvoker.createOrNull( + res.accessibilitySessions.valueAt(i)); if (wrapper != null) { mAccessibilityInputMethodSession.append( res.accessibilitySessions.keyAt(i), wrapper); @@ -2598,7 +2603,7 @@ public final class InputMethodManager { mCursorCandEnd = candidatesEnd; mCurrentInputMethodSession.updateSelection( oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd); - forAccessibilitySessions(wrapper -> wrapper.updateSelection(oldSelStart, + forAccessibilitySessionsLocked(wrapper -> wrapper.updateSelection(oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd)); } } @@ -3658,7 +3663,8 @@ public final class InputMethodManager { } } - private void forAccessibilitySessions(Consumer<InputMethodSessionWrapper> consumer) { + private void forAccessibilitySessionsLocked( + Consumer<IAccessibilityInputMethodSessionInvoker> consumer) { for (int i = 0; i < mAccessibilityInputMethodSession.size(); i++) { consumer.accept(mAccessibilityInputMethodSession.valueAt(i)); } diff --git a/core/java/com/android/internal/inputmethod/IAccessibilityInputMethodSession.aidl b/core/java/com/android/internal/inputmethod/IAccessibilityInputMethodSession.aidl new file mode 100644 index 000000000000..ccfe3fb014b4 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/IAccessibilityInputMethodSession.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 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 com.android.internal.inputmethod; + +import android.graphics.Rect; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; + +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +import com.android.internal.view.IInputContext; + +/** + * Sub-interface of IInputMethodSession which is safe to give to A11y IME. + */ +oneway interface IAccessibilityInputMethodSession { + void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd); + + void finishInput(); + + void finishSession(); + + void invalidateInput(in EditorInfo editorInfo, + in IRemoteAccessibilityInputConnection connection, int sessionId); +} diff --git a/core/java/com/android/internal/view/IInputSessionWithIdCallback.aidl b/core/java/com/android/internal/inputmethod/IAccessibilityInputMethodSessionCallback.aidl index 8fbdefe5931e..bb42c60271fd 100644 --- a/core/java/com/android/internal/view/IInputSessionWithIdCallback.aidl +++ b/core/java/com/android/internal/inputmethod/IAccessibilityInputMethodSessionCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2022 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. @@ -14,14 +14,14 @@ * limitations under the License. */ - package com.android.internal.view; + package com.android.internal.inputmethod; - import com.android.internal.view.IInputMethodSession; + import com.android.internal.inputmethod.IAccessibilityInputMethodSession; /** * Helper interface for IInputMethod to allow the input method to notify the client when a new * session has been created. */ -oneway interface IInputSessionWithIdCallback { - void sessionCreated(IInputMethodSession session, int id); -}
\ No newline at end of file +oneway interface IAccessibilityInputMethodSessionCallback { + void sessionCreated(IAccessibilityInputMethodSession session, int id); +} diff --git a/core/java/com/android/internal/inputmethod/IRemoteAccessibilityInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteAccessibilityInputConnection.aidl new file mode 100644 index 000000000000..f2064bb93e0c --- /dev/null +++ b/core/java/com/android/internal/inputmethod/IRemoteAccessibilityInputConnection.aidl @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 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 com.android.internal.inputmethod; + +import android.view.KeyEvent; +import android.view.inputmethod.TextAttribute; + +import com.android.internal.infra.AndroidFuture; +import com.android.internal.inputmethod.InputConnectionCommandHeader; + +/** + * Interface from A11y IMEs to the application, allowing it to perform edits on the current input + * field and other interactions with the application. + */ +oneway interface IRemoteAccessibilityInputConnection { + void commitText(in InputConnectionCommandHeader header, CharSequence text, + int newCursorPosition, in TextAttribute textAttribute); + + void setSelection(in InputConnectionCommandHeader header, int start, int end); + + void getSurroundingText(in InputConnectionCommandHeader header, int beforeLength, + int afterLength, int flags, in AndroidFuture future /* T=SurroundingText */); + + void deleteSurroundingText(in InputConnectionCommandHeader header, int beforeLength, + int afterLength); + + void sendKeyEvent(in InputConnectionCommandHeader header, in KeyEvent event); + + void performEditorAction(in InputConnectionCommandHeader header, int actionCode); + + void performContextMenuAction(in InputConnectionCommandHeader header, int id); + + void getCursorCapsMode(in InputConnectionCommandHeader header, int reqModes, + in AndroidFuture future /* T=Integer */); + + void clearMetaKeyStates(in InputConnectionCommandHeader header, int states); +} diff --git a/core/java/com/android/internal/inputmethod/IRemoteAccessibilityInputConnectionInvoker.java b/core/java/com/android/internal/inputmethod/IRemoteAccessibilityInputConnectionInvoker.java new file mode 100644 index 000000000000..dcc67d49251f --- /dev/null +++ b/core/java/com/android/internal/inputmethod/IRemoteAccessibilityInputConnectionInvoker.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2022 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 com.android.internal.inputmethod; + +import android.annotation.AnyThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; +import android.view.KeyEvent; +import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextAttribute; + +import com.android.internal.infra.AndroidFuture; + +import java.util.Objects; + +final class IRemoteAccessibilityInputConnectionInvoker { + @NonNull + private final IRemoteAccessibilityInputConnection mConnection; + private final int mSessionId; + + private IRemoteAccessibilityInputConnectionInvoker( + @NonNull IRemoteAccessibilityInputConnection inputContext, int sessionId) { + mConnection = inputContext; + mSessionId = sessionId; + } + + /** + * Creates a new instance of {@link IRemoteAccessibilityInputConnectionInvoker} for the given + * {@link IRemoteAccessibilityInputConnection}. + * + * @param connection {@link IRemoteAccessibilityInputConnection} to be wrapped. + * @return A new instance of {@link IRemoteAccessibilityInputConnectionInvoker}. + */ + public static IRemoteAccessibilityInputConnectionInvoker create( + @NonNull IRemoteAccessibilityInputConnection connection) { + Objects.requireNonNull(connection); + return new IRemoteAccessibilityInputConnectionInvoker(connection, 0); + } + + /** + * Creates a new instance of {@link IRemoteAccessibilityInputConnectionInvoker} with the given + * {@code sessionId}. + * + * @param sessionId the new session ID to be used. + * @return A new instance of {@link IRemoteAccessibilityInputConnectionInvoker}. + */ + @NonNull + public IRemoteAccessibilityInputConnectionInvoker cloneWithSessionId(int sessionId) { + return new IRemoteAccessibilityInputConnectionInvoker(mConnection, sessionId); + } + + /** + * @param connection {@code IRemoteAccessibilityInputConnection} to be compared with + * @return {@code true} if the underlying {@code IRemoteAccessibilityInputConnection} is the + * same. {@code false} if {@code connection} is {@code null}. + */ + @AnyThread + public boolean isSameConnection(@NonNull IRemoteAccessibilityInputConnection connection) { + if (connection == null) { + return false; + } + return mConnection.asBinder() == connection.asBinder(); + } + + @NonNull + InputConnectionCommandHeader createHeader() { + return new InputConnectionCommandHeader(mSessionId); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#commitText(InputConnectionCommandHeader, + * int, CharSequence)}. + * + * @param text {@code text} parameter to be passed. + * @param newCursorPosition {@code newCursorPosition} parameter to be passed. + * @param textAttribute The extra information about the text. + */ + @AnyThread + public void commitText(CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + try { + mConnection.commitText(createHeader(), text, newCursorPosition, textAttribute); + } catch (RemoteException e) { + } + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#setSelection(InputConnectionCommandHeader, + * int, int)}. + * + * @param start {@code start} parameter to be passed. + * @param end {@code start} parameter to be passed. + */ + @AnyThread + public void setSelection(int start, int end) { + try { + mConnection.setSelection(createHeader(), start, end); + } catch (RemoteException e) { + } + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#getSurroundingText( + * InputConnectionCommandHeader, int, int, int, AndroidFuture)}. + * + * @param beforeLength {@code beforeLength} parameter to be passed. + * @param afterLength {@code afterLength} parameter to be passed. + * @param flags {@code flags} parameter to be passed. + * @return {@link AndroidFuture< SurroundingText >} that can be used to retrieve the + * invocation result. {@link RemoteException} will be treated as an error. + */ + @AnyThread + @NonNull + public AndroidFuture<SurroundingText> getSurroundingText(int beforeLength, int afterLength, + int flags) { + final AndroidFuture<SurroundingText> future = new AndroidFuture<>(); + try { + mConnection.getSurroundingText(createHeader(), beforeLength, afterLength, flags, + future); + } catch (RemoteException e) { + future.completeExceptionally(e); + } + return future; + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#deleteSurroundingText( + * InputConnectionCommandHeader, int, int)}. + * + * @param beforeLength {@code beforeLength} parameter to be passed. + * @param afterLength {@code afterLength} parameter to be passed. + */ + @AnyThread + public void deleteSurroundingText(int beforeLength, int afterLength) { + try { + mConnection.deleteSurroundingText(createHeader(), beforeLength, afterLength); + } catch (RemoteException e) { + } + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#sendKeyEvent( + * InputConnectionCommandHeader, KeyEvent)}. + * + * @param event {@code event} parameter to be passed. + */ + @AnyThread + public void sendKeyEvent(KeyEvent event) { + try { + mConnection.sendKeyEvent(createHeader(), event); + } catch (RemoteException e) { + } + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#performEditorAction( + * InputConnectionCommandHeader, int)}. + * + * @param actionCode {@code start} parameter to be passed. + */ + @AnyThread + public void performEditorAction(int actionCode) { + try { + mConnection.performEditorAction(createHeader(), actionCode); + } catch (RemoteException e) { + } + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#performContextMenuAction( + * InputConnectionCommandHeader, int)}. + * + * @param id {@code id} parameter to be passed. + */ + @AnyThread + public void performContextMenuAction(int id) { + try { + mConnection.performContextMenuAction(createHeader(), id); + } catch (RemoteException e) { + } + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#getCursorCapsMode( + * InputConnectionCommandHeader, int, AndroidFuture)}. + * + * @param reqModes {@code reqModes} parameter to be passed. + * @return {@link AndroidFuture<Integer>} that can be used to retrieve the invocation + * result. {@link RemoteException} will be treated as an error. + */ + @AnyThread + @NonNull + public AndroidFuture<Integer> getCursorCapsMode(int reqModes) { + final AndroidFuture<Integer> future = new AndroidFuture<>(); + try { + mConnection.getCursorCapsMode(createHeader(), reqModes, future); + } catch (RemoteException e) { + future.completeExceptionally(e); + } + return future; + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#clearMetaKeyStates( + * InputConnectionCommandHeader, int)}. + * + * @param states {@code states} parameter to be passed. + */ + @AnyThread + public void clearMetaKeyStates(int states) { + try { + mConnection.clearMetaKeyStates(createHeader(), states); + } catch (RemoteException e) { + } + } +} diff --git a/core/java/com/android/internal/inputmethod/InputBindResult.java b/core/java/com/android/internal/inputmethod/InputBindResult.java index f7341a5c4574..10c83c34f417 100644 --- a/core/java/com/android/internal/inputmethod/InputBindResult.java +++ b/core/java/com/android/internal/inputmethod/InputBindResult.java @@ -186,7 +186,7 @@ public final class InputBindResult implements Parcelable { /** * The accessibility services. */ - public SparseArray<IInputMethodSession> accessibilitySessions; + public SparseArray<IAccessibilityInputMethodSession> accessibilitySessions; /** * The input channel used to send input events to this IME. @@ -231,8 +231,8 @@ public final class InputBindResult implements Parcelable { * * @param result A result code defined in {@link ResultCode}. * @param method {@link IInputMethodSession} to interact with the IME. - * @param accessibilitySessions {@link IInputMethodSession} to interact with accessibility - * services. + * @param accessibilitySessions {@link IAccessibilityInputMethodSession} to interact with + * accessibility services. * @param channel {@link InputChannel} to forward input events to the IME. * @param id The {@link String} representations of the IME, which is the same as * {@link android.view.inputmethod.InputMethodInfo#getId()} and @@ -242,7 +242,8 @@ public final class InputBindResult implements Parcelable { * {@code suppressesSpellChecker="true"}. */ public InputBindResult(@ResultCode int result, - IInputMethodSession method, SparseArray<IInputMethodSession> accessibilitySessions, + IInputMethodSession method, + SparseArray<IAccessibilityInputMethodSession> accessibilitySessions, InputChannel channel, String id, int sequence, @Nullable Matrix virtualDisplayToScreenMatrix, boolean isInputMethodSuppressingSpellChecker) { @@ -271,8 +272,9 @@ public final class InputBindResult implements Parcelable { accessibilitySessions = new SparseArray<>(n); while (n > 0) { int key = source.readInt(); - IInputMethodSession value = - IInputMethodSession.Stub.asInterface(source.readStrongBinder()); + IAccessibilityInputMethodSession value = + IAccessibilityInputMethodSession.Stub.asInterface( + source.readStrongBinder()); accessibilitySessions.append(key, value); n--; } diff --git a/core/java/com/android/internal/inputmethod/RemoteAccessibilityInputConnection.java b/core/java/com/android/internal/inputmethod/RemoteAccessibilityInputConnection.java new file mode 100644 index 000000000000..b2ab819eb8a5 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/RemoteAccessibilityInputConnection.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2022 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 com.android.internal.inputmethod; + +import android.annotation.AnyThread; +import android.annotation.DurationMillisLong; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.view.KeyEvent; +import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextAttribute; + +import com.android.internal.infra.AndroidFuture; +import com.android.internal.view.IInputMethod; + +import java.util.concurrent.CompletableFuture; + +/** + * A wrapper object for A11y IME. + * + * <p>This needs to be public to be referenced from {@link android.app.UiAutomation}.</p> + */ +public final class RemoteAccessibilityInputConnection { + private static final String TAG = "RemoteA11yInputConnection"; + + @DurationMillisLong + private static final int MAX_WAIT_TIME_MILLIS = 2000; + + @NonNull + IRemoteAccessibilityInputConnectionInvoker mInvoker; + + /** + * Signaled when the system decided to take away IME focus from the target app. + * + * <p>This is expected to be signaled immediately when the IME process receives + * {@link IInputMethod#unbindInput()}.</p> + */ + @NonNull + private final CancellationGroup mCancellationGroup; + + public RemoteAccessibilityInputConnection( + @NonNull IRemoteAccessibilityInputConnection connection, + @NonNull CancellationGroup cancellationGroup) { + mInvoker = IRemoteAccessibilityInputConnectionInvoker.create(connection); + mCancellationGroup = cancellationGroup; + } + + public RemoteAccessibilityInputConnection(@NonNull RemoteAccessibilityInputConnection original, + int sessionId) { + mInvoker = original.mInvoker.cloneWithSessionId(sessionId); + mCancellationGroup = original.mCancellationGroup; + } + + /** + * Test if this object holds the given {@link IRemoteAccessibilityInputConnection} or not. + * + * @param connection {@link IRemoteAccessibilityInputConnection} to be tested. + * @return {@code true} if this object holds the same object. + */ + @AnyThread + public boolean isSameConnection(@NonNull IRemoteAccessibilityInputConnection connection) { + return mInvoker.isSameConnection(connection); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#commitText(InputConnectionCommandHeader, + * CharSequence, int, TextAttribute)}. + * + * @param text The {@code "text"} parameter to be passed. + * @param newCursorPosition The {@code "newCursorPosition"} parameter to be passed. + * @param textAttribute The {@code "textAttribute"} parameter to be passed. + */ + @AnyThread + public void commitText(@NonNull CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + mInvoker.commitText(text, newCursorPosition, textAttribute); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#setSelection(InputConnectionCommandHeader, + * int, int)}. + * + * @param start The {@code "start"} parameter to be passed. + * @param end The {@code "end"} parameter to be passed. + */ + @AnyThread + public void setSelection(int start, int end) { + mInvoker.setSelection(start, end); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#getSurroundingText( + * InputConnectionCommandHeader, int, int, int, AndroidFuture)}. + * + * @param beforeLength The {@code "beforeLength"} parameter to be passed. + * @param afterLength The {@code "afterLength"} parameter to be passed. + * @param flags The {@code "flags"} parameter to be passed. + * @return The {@link SurroundingText} object returned from the target application. + */ + @AnyThread + public SurroundingText getSurroundingText( + @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) { + if (beforeLength < 0) { + throw new IllegalArgumentException("beforeLength cannot be negative but was " + + beforeLength); + } + if (afterLength < 0) { + throw new IllegalArgumentException("afterLength cannot be negative but was " + + afterLength); + } + if (mCancellationGroup.isCanceled()) { + return null; + } + + final CompletableFuture<SurroundingText> value = mInvoker.getSurroundingText(beforeLength, + afterLength, flags); + return CompletableFutureUtil.getResultOrNull( + value, TAG, "getSurroundingText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#deleteSurroundingText( + * InputConnectionCommandHeader, int, int)}. + * + * @param beforeLength The {@code "beforeLength"} parameter to be passed. + * @param afterLength The {@code "afterLength"} parameter to be passed. + */ + @AnyThread + public void deleteSurroundingText(int beforeLength, int afterLength) { + mInvoker.deleteSurroundingText(beforeLength, afterLength); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#sendKeyEvent(InputConnectionCommandHeader, + * KeyEvent)}. + * + * @param event The {@code "event"} parameter to be passed. + */ + @AnyThread + public void sendKeyEvent(KeyEvent event) { + mInvoker.sendKeyEvent(event); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#performEditorAction( + * InputConnectionCommandHeader, int)}. + * + * @param actionCode The {@code "actionCode"} parameter to be passed. + */ + @AnyThread + public void performEditorAction(int actionCode) { + mInvoker.performEditorAction(actionCode); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#performContextMenuAction( + * InputConnectionCommandHeader, int)}. + * + * @param id The {@code "id"} parameter to be passed. + */ + @AnyThread + public void performContextMenuAction(int id) { + mInvoker.performContextMenuAction(id); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#getCursorCapsMode( + * InputConnectionCommandHeader, int, AndroidFuture)}. + * + * @param reqModes The {@code "reqModes"} parameter to be passed. + * @return integer result returned from the target application. + */ + @AnyThread + public int getCursorCapsMode(int reqModes) { + if (mCancellationGroup.isCanceled()) { + return 0; + } + + final CompletableFuture<Integer> value = mInvoker.getCursorCapsMode(reqModes); + + return CompletableFutureUtil.getResultOrZero( + value, TAG, "getCursorCapsMode()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); + } + + /** + * Invokes {@link IRemoteAccessibilityInputConnection#clearMetaKeyStates( + * InputConnectionCommandHeader, int)}. + * + * @param states The {@code "states"} parameter to be passed. + */ + @AnyThread + public void clearMetaKeyStates(int states) { + mInvoker.clearMetaKeyStates(states); + } +} diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index 7f11e3018849..66c4b6899557 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -66,6 +66,13 @@ import java.util.function.Supplier; * {@link IInputContext} binder calls in the IME client (editor app) process, and forwards them to * {@link InputConnection} that the IME client provided, on the {@link Looper} associated to the * {@link InputConnection}.</p> + * + * <p>{@link com.android.internal.inputmethod.RemoteAccessibilityInputConnection} code is executed + * in the {@link android.accessibilityservice.AccessibilityService} process. It makes + * {@link com.android.internal.inputmethod.IRemoteAccessibilityInputConnection} binder calls under + * the hood. {@link #mAccessibilityInputConnection} receives the binder calls in the IME client + * (editor app) process, and forwards them to {@link InputConnection} that the IME client provided, + * on the {@link Looper} associated to the {@link InputConnection}.</p> */ public final class RemoteInputConnectionImpl extends IInputContext.Stub { private static final String TAG = "RemoteInputConnectionImpl"; @@ -1043,6 +1050,175 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + private final IRemoteAccessibilityInputConnection mAccessibilityInputConnection = + new IRemoteAccessibilityInputConnection.Stub() { + @Dispatching(cancellable = true) + @Override + public void commitText(InputConnectionCommandHeader header, CharSequence text, + int newCursorPosition, @Nullable TextAttribute textAttribute) { + dispatchWithTracing("commitTextFromA11yIme", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "commitText on inactive InputConnection"); + return; + } + ic.commitText(text, newCursorPosition, textAttribute); + }); + } + + @Dispatching(cancellable = true) + @Override + public void setSelection(InputConnectionCommandHeader header, int start, int end) { + dispatchWithTracing("setSelectionFromA11yIme", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "setSelection on inactive InputConnection"); + return; + } + ic.setSelection(start, end); + }); + } + + @Dispatching(cancellable = true) + @Override + public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength, + int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) { + dispatchWithTracing("getSurroundingTextFromA11yIme", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return null; // cancelled + } + final InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "getSurroundingText on inactive InputConnection"); + return null; + } + if (beforeLength < 0) { + Log.i(TAG, "Returning null to getSurroundingText due to an invalid" + + " beforeLength=" + beforeLength); + return null; + } + if (afterLength < 0) { + Log.i(TAG, "Returning null to getSurroundingText due to an invalid" + + " afterLength=" + afterLength); + return null; + } + return ic.getSurroundingText(beforeLength, afterLength, flags); + }, useImeTracing() ? result -> buildGetSurroundingTextProto( + beforeLength, afterLength, flags, result) : null); + } + + @Dispatching(cancellable = true) + @Override + public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength, + int afterLength) { + dispatchWithTracing("deleteSurroundingTextFromA11yIme", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "deleteSurroundingText on inactive InputConnection"); + return; + } + ic.deleteSurroundingText(beforeLength, afterLength); + }); + } + + @Dispatching(cancellable = true) + @Override + public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) { + dispatchWithTracing("sendKeyEventFromA11yIme", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "sendKeyEvent on inactive InputConnection"); + return; + } + ic.sendKeyEvent(event); + }); + } + + @Dispatching(cancellable = true) + @Override + public void performEditorAction(InputConnectionCommandHeader header, int id) { + dispatchWithTracing("performEditorActionFromA11yIme", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "performEditorAction on inactive InputConnection"); + return; + } + ic.performEditorAction(id); + }); + } + + @Dispatching(cancellable = true) + @Override + public void performContextMenuAction(InputConnectionCommandHeader header, int id) { + dispatchWithTracing("performContextMenuActionFromA11yIme", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "performContextMenuAction on inactive InputConnection"); + return; + } + ic.performContextMenuAction(id); + }); + } + + @Dispatching(cancellable = true) + @Override + public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes, + AndroidFuture future /* T=Integer */) { + dispatchWithTracing("getCursorCapsModeFromA11yIme", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return 0; // cancelled + } + final InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "getCursorCapsMode on inactive InputConnection"); + return 0; + } + return ic.getCursorCapsMode(reqModes); + }, useImeTracing() ? result -> buildGetCursorCapsModeProto(reqModes, result) : null); + } + + @Dispatching(cancellable = true) + @Override + public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) { + dispatchWithTracing("clearMetaKeyStatesFromA11yIme", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "clearMetaKeyStates on inactive InputConnection"); + return; + } + ic.clearMetaKeyStates(states); + }); + } + }; + + /** + * @return {@link IRemoteAccessibilityInputConnection} associated with this object. + */ + public IRemoteAccessibilityInputConnection asIRemoteAccessibilityInputConnection() { + return mAccessibilityInputConnection; + } + private void dispatch(@NonNull Runnable runnable) { // If we are calling this from the target thread, then we can call right through. // Otherwise, we need to send the message to the target thread. diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 616411fee5f2..d7bb2cb10b8c 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -22,6 +22,7 @@ import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.EditorInfo; import com.android.internal.inputmethod.InputBindResult; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; @@ -54,7 +55,8 @@ interface IInputMethodManager { in IInputMethodClient client, in IBinder windowToken, /* @StartInputFlags */ int startInputFlags, /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode, - int windowFlags, in EditorInfo attribute, IInputContext inputContext, + int windowFlags, in EditorInfo attribute, in IInputContext inputContext, + in IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion); void showInputMethodPickerFromClient(in IInputMethodClient client, diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 562d11a0b197..649328d1eef3 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -84,16 +84,15 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputBinding; import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.internal.view.IInputContext; -import com.android.internal.view.IInputMethodSession; -import com.android.internal.view.IInputSessionWithIdCallback; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; import com.android.server.accessibility.magnification.MagnificationProcessor; @@ -1655,21 +1654,22 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mInvocationHandler.createImeSessionLocked(); } - public void setImeSessionEnabledLocked(IInputMethodSession session, boolean enabled) { + public void setImeSessionEnabledLocked(IAccessibilityInputMethodSession session, + boolean enabled) { mInvocationHandler.setImeSessionEnabledLocked(session, enabled); } - public void bindInputLocked(InputBinding binding) { - mInvocationHandler.bindInputLocked(binding); + public void bindInputLocked() { + mInvocationHandler.bindInputLocked(); } public void unbindInputLocked() { mInvocationHandler.unbindInputLocked(); } - public void startInputLocked(IBinder startInputToken, IInputContext inputContext, + public void startInputLocked(IRemoteAccessibilityInputConnection connection, EditorInfo editorInfo, boolean restarting) { - mInvocationHandler.startInputLocked(startInputToken, inputContext, editorInfo, restarting); + mInvocationHandler.startInputLocked(connection, editorInfo, restarting); } @@ -1827,7 +1827,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - private void setImeSessionEnabledInternal(IInputMethodSession session, boolean enabled) { + private void setImeSessionEnabledInternal(IAccessibilityInputMethodSession session, + boolean enabled) { final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null && session != null) { try { @@ -1842,14 +1843,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - private void bindInputInternal(InputBinding binding) { + private void bindInputInternal() { final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { if (svcClientTracingEnabled()) { - logTraceSvcClient("bindInput", binding.toString()); + logTraceSvcClient("bindInput", ""); } - listener.bindInput(binding); + listener.bindInput(); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error binding input to " + mService, re); @@ -1872,16 +1873,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - private void startInputInternal(IBinder startInputToken, IInputContext inputContext, + private void startInputInternal(IRemoteAccessibilityInputConnection connection, EditorInfo editorInfo, boolean restarting) { final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { if (svcClientTracingEnabled()) { - logTraceSvcClient("startInput", startInputToken + " " - + inputContext + " " + editorInfo + restarting); + logTraceSvcClient("startInput", "editorInfo=" + editorInfo + + " restarting=" + restarting); } - listener.startInput(startInputToken, inputContext, editorInfo, restarting); + listener.startInput(connection, editorInfo, restarting); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error starting input to " + mService, re); @@ -2141,12 +2142,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ break; case MSG_SET_IME_SESSION_ENABLED: final boolean enabled = (message.arg1 != 0); - final IInputMethodSession session = (IInputMethodSession) message.obj; + final IAccessibilityInputMethodSession session = + (IAccessibilityInputMethodSession) message.obj; setImeSessionEnabledInternal(session, enabled); break; case MSG_BIND_INPUT: - final InputBinding binding = (InputBinding) message.obj; - bindInputInternal(binding); + bindInputInternal(); break; case MSG_UNBIND_INPUT: unbindInputInternal(); @@ -2154,10 +2155,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ case MSG_START_INPUT: final boolean restarting = (message.arg1 != 0); final SomeArgs args = (SomeArgs) message.obj; - final IBinder startInputToken = (IBinder) args.arg1; - final IInputContext inputContext = (IInputContext) args.arg2; - final EditorInfo editorInfo = (EditorInfo) args.arg3; - startInputInternal(startInputToken, inputContext, editorInfo, restarting); + final IRemoteAccessibilityInputConnection connection = + (IRemoteAccessibilityInputConnection) args.arg1; + final EditorInfo editorInfo = (EditorInfo) args.arg2; + startInputInternal(connection, editorInfo, restarting); + args.recycle(); break; default: { throw new IllegalArgumentException("Unknown message: " + type); @@ -2227,14 +2229,15 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ msg.sendToTarget(); } - public void setImeSessionEnabledLocked(IInputMethodSession session, boolean enabled) { + public void setImeSessionEnabledLocked(IAccessibilityInputMethodSession session, + boolean enabled) { final Message msg = obtainMessage(MSG_SET_IME_SESSION_ENABLED, (enabled ? 1 : 0), 0, session); msg.sendToTarget(); } - public void bindInputLocked(InputBinding binding) { - final Message msg = obtainMessage(MSG_BIND_INPUT, binding); + public void bindInputLocked() { + final Message msg = obtainMessage(MSG_BIND_INPUT); msg.sendToTarget(); } @@ -2243,12 +2246,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ msg.sendToTarget(); } - public void startInputLocked(IBinder startInputToken, IInputContext inputContext, + public void startInputLocked( + IRemoteAccessibilityInputConnection connection, EditorInfo editorInfo, boolean restarting) { final SomeArgs args = SomeArgs.obtain(); - args.arg1 = startInputToken; - args.arg2 = inputContext; - args.arg3 = editorInfo; + args.arg1 = connection; + args.arg2 = editorInfo; final Message msg = obtainMessage(MSG_START_INPUT, restarting ? 1 : 0, 0, args); msg.sendToTarget(); } @@ -2402,10 +2405,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - private static final class AccessibilityCallback extends IInputSessionWithIdCallback.Stub { + private static final class AccessibilityCallback + extends IAccessibilityInputMethodSessionCallback.Stub { @Override - public void sessionCreated(IInputMethodSession session, int id) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.sessionCreated"); + public void sessionCreated(IAccessibilityInputMethodSession session, int id) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AACS.sessionCreated"); final long ident = Binder.clearCallingIdentity(); try { InputMethodManagerInternal.get().onSessionForAccessibilityCreated(id, session); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index ac0c051794b3..cbeb01a68778 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -118,7 +118,6 @@ import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; import android.view.accessibility.IWindowMagnificationConnection; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputBinding; import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController; @@ -128,12 +127,12 @@ import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserAct import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IntPair; -import com.android.internal.view.IInputContext; -import com.android.internal.view.IInputMethodSession; import com.android.server.AccessibilityManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -281,9 +280,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private Point mTempPoint = new Point(); private boolean mIsAccessibilityButtonShown; - private InputBinding mInputBinding; - IBinder mStartInputToken; - IInputContext mInputContext; + private boolean mInputBound; + IRemoteAccessibilityInputConnection mRemoteInputConnection; EditorInfo mEditorInfo; boolean mRestarting; boolean mInputSessionRequested; @@ -322,7 +320,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions, + public void setImeSessionEnabled(SparseArray<IAccessibilityInputMethodSession> sessions, boolean enabled) { mService.scheduleSetImeSessionEnabled(sessions, enabled); } @@ -333,8 +331,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public void bindInput(InputBinding binding) { - mService.scheduleBindInput(binding); + public void bindInput() { + mService.scheduleBindInput(); } @Override @@ -343,9 +341,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public void startInput(IBinder startInputToken, IInputContext inputContext, + public void startInput( + IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, EditorInfo editorInfo, boolean restarting) { - mService.scheduleStartInput(startInputToken, inputContext, editorInfo, restarting); + mService.scheduleStartInput(remoteAccessibilityInputConnection, editorInfo, restarting); } } @@ -4350,10 +4349,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void bindAndStartInputForConnection(AbstractAccessibilityServiceConnection connection) { synchronized (mLock) { - if (mInputBinding != null) { - connection.bindInputLocked(mInputBinding); - connection.startInputLocked(mStartInputToken, mInputContext, mEditorInfo, - mRestarting); + if (mInputBound) { + connection.bindInputLocked(); + connection.startInputLocked(mRemoteInputConnection, mEditorInfo, mRestarting); } } } @@ -4396,23 +4394,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /** * Bind input for accessibility services which request ime capabilities. - * - * @param binding Information given to an accessibility service about a client connecting to it. */ - public void scheduleBindInput(InputBinding binding) { - mMainHandler.sendMessage(obtainMessage(AccessibilityManagerService::bindInput, this, - binding)); + public void scheduleBindInput() { + mMainHandler.sendMessage(obtainMessage(AccessibilityManagerService::bindInput, this)); } - private void bindInput(InputBinding binding) { + private void bindInput() { synchronized (mLock) { // Keep records of these in case new Accessibility Services are enabled. - mInputBinding = binding; + mInputBound = true; AccessibilityUserState userState = getCurrentUserStateLocked(); for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { final AccessibilityServiceConnection service = userState.mBoundServices.get(i); if (service.requestImeApis()) { - service.bindInputLocked(binding); + service.bindInputLocked(); } } } @@ -4427,6 +4422,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void unbindInput() { synchronized (mLock) { + mInputBound = false; AccessibilityUserState userState = getCurrentUserStateLocked(); for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { final AccessibilityServiceConnection service = userState.mBoundServices.get(i); @@ -4440,25 +4436,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /** * Start input for accessibility services which request ime capabilities. */ - public void scheduleStartInput(IBinder startInputToken, IInputContext inputContext, + public void scheduleStartInput(IRemoteAccessibilityInputConnection connection, EditorInfo editorInfo, boolean restarting) { mMainHandler.sendMessage(obtainMessage(AccessibilityManagerService::startInput, this, - startInputToken, inputContext, editorInfo, restarting)); + connection, editorInfo, restarting)); } - private void startInput(IBinder startInputToken, IInputContext inputContext, - EditorInfo editorInfo, boolean restarting) { + private void startInput(IRemoteAccessibilityInputConnection connection, EditorInfo editorInfo, + boolean restarting) { synchronized (mLock) { // Keep records of these in case new Accessibility Services are enabled. - mStartInputToken = startInputToken; - mInputContext = inputContext; + mRemoteInputConnection = connection; mEditorInfo = editorInfo; mRestarting = restarting; AccessibilityUserState userState = getCurrentUserStateLocked(); for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { final AccessibilityServiceConnection service = userState.mBoundServices.get(i); if (service.requestImeApis()) { - service.startInputLocked(startInputToken, inputContext, editorInfo, restarting); + service.startInputLocked(connection, editorInfo, restarting); } } } @@ -4492,13 +4487,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * @param sessions Sessions to enable or disable. * @param enabled True if enable the sessions or false if disable the sessions. */ - public void scheduleSetImeSessionEnabled(SparseArray<IInputMethodSession> sessions, + public void scheduleSetImeSessionEnabled(SparseArray<IAccessibilityInputMethodSession> sessions, boolean enabled) { mMainHandler.sendMessage(obtainMessage(AccessibilityManagerService::setImeSessionEnabled, this, sessions, enabled)); } - private void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions, boolean enabled) { + private void setImeSessionEnabled(SparseArray<IAccessibilityInputMethodSession> sessions, + boolean enabled) { synchronized (mLock) { AccessibilityUserState userState = getCurrentUserStateLocked(); for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/AccessibilityManagerInternal.java b/services/core/java/com/android/server/AccessibilityManagerInternal.java index 28f6db1c800b..6ca32af8c3cb 100644 --- a/services/core/java/com/android/server/AccessibilityManagerInternal.java +++ b/services/core/java/com/android/server/AccessibilityManagerInternal.java @@ -17,28 +17,26 @@ package com.android.server; import android.annotation.NonNull; -import android.os.IBinder; import android.util.ArraySet; import android.util.SparseArray; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputBinding; -import com.android.internal.view.IInputContext; -import com.android.internal.view.IInputMethodSession; +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; /** * Accessibility manager local system service interface. */ public abstract class AccessibilityManagerInternal { /** Enable or disable the sessions. */ - public abstract void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions, - boolean enabled); + public abstract void setImeSessionEnabled( + SparseArray<IAccessibilityInputMethodSession> sessions, boolean enabled); /** Unbind input for all accessibility services which require ime capabilities. */ public abstract void unbindInput(); /** Bind input for all accessibility services which require ime capabilities. */ - public abstract void bindInput(InputBinding binding); + public abstract void bindInput(); /** * Request input session from all accessibility services which require ime capabilities and @@ -47,12 +45,13 @@ public abstract class AccessibilityManagerInternal { public abstract void createImeSession(ArraySet<Integer> ignoreSet); /** Start input for all accessibility services which require ime capabilities. */ - public abstract void startInput(IBinder startInputToken, IInputContext inputContext, + public abstract void startInput( + IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, EditorInfo editorInfo, boolean restarting); private static final AccessibilityManagerInternal NOP = new AccessibilityManagerInternal() { @Override - public void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions, + public void setImeSessionEnabled(SparseArray<IAccessibilityInputMethodSession> sessions, boolean enabled) { } @@ -61,7 +60,7 @@ public abstract class AccessibilityManagerInternal { } @Override - public void bindInput(InputBinding binding) { + public void bindInput() { } @Override @@ -69,7 +68,7 @@ public abstract class AccessibilityManagerInternal { } @Override - public void startInput(IBinder startInputToken, IInputContext inputContext, + public void startInput(IRemoteAccessibilityInputConnection remoteAccessibility, EditorInfo editorInfo, boolean restarting) { } }; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index a2d3588f0e68..b978131e175c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -24,9 +24,9 @@ import android.os.IBinder; import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputMethodInfo; +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.view.IInlineSuggestionsRequestCallback; -import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InlineSuggestionsRequestInfo; import com.android.server.LocalServices; @@ -164,7 +164,7 @@ public abstract class InputMethodManagerInternal { * @param session The session passed back from the accessibility service. */ public abstract void onSessionForAccessibilityCreated(int accessibilityConnectionId, - IInputMethodSession session); + IAccessibilityInputMethodSession session); /** * Unbind the accessibility service with the specified accessibilityConnectionId from current @@ -240,7 +240,7 @@ public abstract class InputMethodManagerInternal { @Override public void onSessionForAccessibilityCreated(int accessibilityConnectionId, - IInputMethodSession session) { + IAccessibilityInputMethodSession session) { } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3c3140551bc3..5c7b185bcf13 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -155,8 +155,10 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AndroidFuture; import com.android.internal.inputmethod.DirectBootAwareness; +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodDebug; @@ -398,7 +400,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Id of the accessibility service. final int mId; - public IInputMethodSession mSession; + public IAccessibilityInputMethodSession mSession; @Override public String toString() { @@ -410,7 +412,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } AccessibilitySessionState(ClientState client, int id, - IInputMethodSession session) { + IAccessibilityInputMethodSession session) { mClient = client; mId = id; mSession = session; @@ -590,6 +592,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub IInputContext mCurInputContext; /** + * The {@link IRemoteAccessibilityInputConnection} last provided by the current client. + */ + @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection; + + /** * The attributes last provided by the current client. */ EditorInfo mCurAttribute; @@ -2567,7 +2574,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final InputMethodInfo curInputMethodInfo = mMethodMap.get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); - final SparseArray<IInputMethodSession> accessibilityInputMethodSessions = + final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, session.session, accessibilityInputMethodSessions, @@ -2606,7 +2613,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub InputBindResult attachNewAccessibilityLocked(@StartInputReason int startInputReason, boolean initial, int id) { if (!mBoundToAccessibility) { - AccessibilityManagerInternal.get().bindInput(mCurClient.binding); + AccessibilityManagerInternal.get().bindInput(); mBoundToAccessibility = true; } @@ -2620,14 +2627,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (startInputReason != StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY) { final Binder startInputToken = new Binder(); setEnabledSessionForAccessibilityLocked(mCurClient.mAccessibilitySessions); - AccessibilityManagerInternal.get().startInput(startInputToken, mCurInputContext, + AccessibilityManagerInternal.get().startInput(mCurRemoteAccessibilityInputConnection, mCurAttribute, !initial /* restarting */); } if (accessibilitySession != null) { final SessionState session = mCurClient.curSession; IInputMethodSession imeSession = session == null ? null : session.session; - final SparseArray<IInputMethodSession> accessibilityInputMethodSessions = + final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION, @@ -2638,9 +2645,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return null; } - private SparseArray<IInputMethodSession> createAccessibilityInputMethodSessions( + private SparseArray<IAccessibilityInputMethodSession> createAccessibilityInputMethodSessions( SparseArray<AccessibilitySessionState> accessibilitySessions) { - final SparseArray<IInputMethodSession> accessibilityInputMethodSessions = + final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = new SparseArray<>(); if (accessibilitySessions != null) { for (int i = 0; i < accessibilitySessions.size(); i++) { @@ -2662,8 +2669,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") @NonNull private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, - IInputContext inputContext, @NonNull EditorInfo attribute, - @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, + IInputContext inputContext, + @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, + @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags, + @StartInputReason int startInputReason, int unverifiedTargetSdkVersion) { // If no method is currently selected, do nothing. final String selectedMethodId = getSelectedMethodIdLocked(); @@ -2707,6 +2716,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub advanceSequenceNumberLocked(); mCurClient = cs; mCurInputContext = inputContext; + mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection; mCurVirtualDisplayToScreenMatrix = getVirtualDisplayToScreenMatrixLocked(cs.selfReportedDisplayId, mDisplayIdToShowIme); @@ -3709,10 +3719,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext, + IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion) { return startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, attribute, inputContext, - unverifiedTargetSdkVersion); + remoteAccessibilityInputConnection, unverifiedTargetSdkVersion); } @NonNull @@ -3720,6 +3731,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo attribute, @Nullable IInputContext inputContext, + @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion) { if (windowToken == null) { Slog.e(TAG, "windowToken cannot be null."); @@ -3756,7 +3768,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub try { result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, - attribute, inputContext, unverifiedTargetSdkVersion, userId); + attribute, inputContext, remoteAccessibilityInputConnection, + unverifiedTargetSdkVersion, userId); } finally { Binder.restoreCallingIdentity(ident); } @@ -3782,7 +3795,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @StartInputReason int startInputReason, IInputMethodClient client, @NonNull IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute, - IInputContext inputContext, int unverifiedTargetSdkVersion, @UserIdInt int userId) { + IInputContext inputContext, + @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, + int unverifiedTargetSdkVersion, @UserIdInt int userId) { if (DEBUG) { Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason=" + InputMethodDebug.startInputReasonToString(startInputReason) @@ -3875,7 +3890,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + InputMethodDebug.startInputReasonToString(startInputReason)); } if (attribute != null) { - return startInputUncheckedLocked(cs, inputContext, attribute, startInputFlags, + return startInputUncheckedLocked(cs, inputContext, + remoteAccessibilityInputConnection, attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion); } return new InputBindResult( @@ -3916,8 +3932,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // UI for input. if (isTextEditor && attribute != null && shouldRestoreImeVisibility(windowToken, softInputMode)) { - res = startInputUncheckedLocked(cs, inputContext, attribute, startInputFlags, - startInputReason, unverifiedTargetSdkVersion); + res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, + attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion); showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); return res; @@ -3955,8 +3971,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // is more room for the target window + IME. if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); if (attribute != null) { - res = startInputUncheckedLocked(cs, inputContext, attribute, - startInputFlags, startInputReason, unverifiedTargetSdkVersion); + res = startInputUncheckedLocked(cs, inputContext, + remoteAccessibilityInputConnection, attribute, startInputFlags, + startInputReason, unverifiedTargetSdkVersion); didStart = true; } showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, @@ -3986,8 +4003,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (isSoftInputModeStateVisibleAllowed( unverifiedTargetSdkVersion, startInputFlags)) { if (attribute != null) { - res = startInputUncheckedLocked(cs, inputContext, attribute, - startInputFlags, startInputReason, unverifiedTargetSdkVersion); + res = startInputUncheckedLocked(cs, inputContext, + remoteAccessibilityInputConnection, attribute, startInputFlags, + startInputReason, unverifiedTargetSdkVersion); didStart = true; } showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, @@ -4005,8 +4023,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub unverifiedTargetSdkVersion, startInputFlags)) { if (!sameWindowFocused) { if (attribute != null) { - res = startInputUncheckedLocked(cs, inputContext, attribute, - startInputFlags, startInputReason, unverifiedTargetSdkVersion); + res = startInputUncheckedLocked(cs, inputContext, + remoteAccessibilityInputConnection, attribute, startInputFlags, + startInputReason, unverifiedTargetSdkVersion); didStart = true; } showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, @@ -4034,7 +4053,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR); } } - res = startInputUncheckedLocked(cs, inputContext, attribute, startInputFlags, + res = startInputUncheckedLocked(cs, inputContext, + remoteAccessibilityInputConnection, attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion); } else { res = InputBindResult.NULL_EDITOR_INFO; @@ -4790,7 +4810,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub void setEnabledSessionForAccessibilityLocked( SparseArray<AccessibilitySessionState> accessibilitySessions) { // mEnabledAccessibilitySessions could the same object as accessibilitySessions. - SparseArray<IInputMethodSession> disabledSessions = new SparseArray<>(); + SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>(); for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) { if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) { AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i); @@ -4804,7 +4824,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub AccessibilityManagerInternal.get().setImeSessionEnabled(disabledSessions, false); } - SparseArray<IInputMethodSession> enabledSessions = new SparseArray<>(); + SparseArray<IAccessibilityInputMethodSession> enabledSessions = new SparseArray<>(); for (int i = 0; i < accessibilitySessions.size(); i++) { if (!mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) { AccessibilitySessionState sessionState = accessibilitySessions.valueAt(i); @@ -5649,7 +5669,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onSessionForAccessibilityCreated(int accessibilityConnectionId, - IInputMethodSession session) { + IAccessibilityInputMethodSession session) { synchronized (ImfLock.class) { if (mCurClient != null) { clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId); |