From e345e90a0197c01b638cfc538ccea3757e7a92ed Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Thu, 16 Jun 2022 09:47:12 -0700 Subject: [PATCH] Make IInputMethodSession async even for in-proc cases This CL applies the same approach I used for IInputMethodClient recently [1], that is, to use a dedicated worker thread to invoke binder IPCs to achieve the async semantics even if IInputMethodSession is not a BinderProxy, which happens when both the IME and IME client are running in the same process. When the IME and IME client are running in different processes, this CL literally changes nothing. [1]: I0a7dd5162c4b94599b896420b8427913fa772414 1112d366b6aaca4ac8996ddfed178e65adfa303a Fix: 236147652 Test: atest CtsInputMethodTestCases:InProcessImeTest Change-Id: I56f3198e54f689adc9541f2c2644ee19bcc6deed --- .../IInputMethodSessionInvoker.java | 129 +++++++++++++++++- 1 file changed, 126 insertions(+), 3 deletions(-) diff --git a/core/java/android/view/inputmethod/IInputMethodSessionInvoker.java b/core/java/android/view/inputmethod/IInputMethodSessionInvoker.java index 1fbe2d4a0a3a0..db597e118f2ca 100644 --- a/core/java/android/view/inputmethod/IInputMethodSessionInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodSessionInvoker.java @@ -20,10 +20,14 @@ import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; +import android.os.Binder; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; import android.os.RemoteException; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInputMethodSession; import com.android.internal.inputmethod.IRemoteInputConnection; @@ -42,8 +46,25 @@ final class IInputMethodSessionInvoker { @NonNull private final IInputMethodSession mSession; - private IInputMethodSessionInvoker(@NonNull IInputMethodSession inputMethodSession) { + /** + * An optional {@link Handler} to dispatch {@link IInputMethodSession} method invocations to + * a background thread to emulate async (one-way) {@link Binder} call. + * + * {@code null} if {@code Binder.isProxy(mSession)} is {@code true}. + */ + @Nullable + private final Handler mCustomHandler; + + private static final Object sAsyncBinderEmulationHandlerLock = new Object(); + + @GuardedBy("sAsyncBinderEmulationHandlerLock") + @Nullable + private static Handler sAsyncBinderEmulationHandler; + + private IInputMethodSessionInvoker(@NonNull IInputMethodSession inputMethodSession, + @Nullable Handler customHandler) { mSession = inputMethodSession; + mCustomHandler = customHandler; } /** @@ -56,12 +77,37 @@ final class IInputMethodSessionInvoker { @Nullable public static IInputMethodSessionInvoker createOrNull( @NonNull IInputMethodSession inputMethodSession) { - return inputMethodSession != null ? new IInputMethodSessionInvoker(inputMethodSession) - : null; + + final Handler customHandler; + if (inputMethodSession != null && !Binder.isProxy(inputMethodSession)) { + synchronized (sAsyncBinderEmulationHandlerLock) { + if (sAsyncBinderEmulationHandler == null) { + final HandlerThread thread = new HandlerThread("IMM.binder-emu"); + thread.start(); + // Use an async handler instead of Handler#getThreadHandler(). + sAsyncBinderEmulationHandler = Handler.createAsync(thread.getLooper()); + } + customHandler = sAsyncBinderEmulationHandler; + } + } else { + customHandler = null; + } + + return inputMethodSession != null + ? new IInputMethodSessionInvoker(inputMethodSession, customHandler) : null; } @AnyThread void finishInput() { + if (mCustomHandler == null) { + finishInputInternal(); + } else { + mCustomHandler.post(this::finishInputInternal); + } + } + + @AnyThread + private void finishInputInternal() { try { mSession.finishInput(); } catch (RemoteException e) { @@ -71,6 +117,15 @@ final class IInputMethodSessionInvoker { @AnyThread void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { + if (mCustomHandler == null) { + updateCursorAnchorInfoInternal(cursorAnchorInfo); + } else { + mCustomHandler.post(() -> updateCursorAnchorInfoInternal(cursorAnchorInfo)); + } + } + + @AnyThread + private void updateCursorAnchorInfoInternal(CursorAnchorInfo cursorAnchorInfo) { try { mSession.updateCursorAnchorInfo(cursorAnchorInfo); } catch (RemoteException e) { @@ -80,6 +135,15 @@ final class IInputMethodSessionInvoker { @AnyThread void displayCompletions(CompletionInfo[] completions) { + if (mCustomHandler == null) { + displayCompletionsInternal(completions); + } else { + mCustomHandler.post(() -> displayCompletionsInternal(completions)); + } + } + + @AnyThread + void displayCompletionsInternal(CompletionInfo[] completions) { try { mSession.displayCompletions(completions); } catch (RemoteException e) { @@ -89,6 +153,15 @@ final class IInputMethodSessionInvoker { @AnyThread void updateExtractedText(int token, ExtractedText text) { + if (mCustomHandler == null) { + updateExtractedTextInternal(token, text); + } else { + mCustomHandler.post(() -> updateExtractedTextInternal(token, text)); + } + } + + @AnyThread + private void updateExtractedTextInternal(int token, ExtractedText text) { try { mSession.updateExtractedText(token, text); } catch (RemoteException e) { @@ -98,6 +171,15 @@ final class IInputMethodSessionInvoker { @AnyThread void appPrivateCommand(String action, Bundle data) { + if (mCustomHandler == null) { + appPrivateCommandInternal(action, data); + } else { + mCustomHandler.post(() -> appPrivateCommandInternal(action, data)); + } + } + + @AnyThread + private void appPrivateCommandInternal(String action, Bundle data) { try { mSession.appPrivateCommand(action, data); } catch (RemoteException e) { @@ -107,6 +189,15 @@ final class IInputMethodSessionInvoker { @AnyThread void viewClicked(boolean focusChanged) { + if (mCustomHandler == null) { + viewClickedInternal(focusChanged); + } else { + mCustomHandler.post(() -> viewClickedInternal(focusChanged)); + } + } + + @AnyThread + private void viewClickedInternal(boolean focusChanged) { try { mSession.viewClicked(focusChanged); } catch (RemoteException e) { @@ -116,6 +207,15 @@ final class IInputMethodSessionInvoker { @AnyThread void updateCursor(Rect newCursor) { + if (mCustomHandler == null) { + updateCursorInternal(newCursor); + } else { + mCustomHandler.post(() -> updateCursorInternal(newCursor)); + } + } + + @AnyThread + private void updateCursorInternal(Rect newCursor) { try { mSession.updateCursor(newCursor); } catch (RemoteException e) { @@ -126,6 +226,18 @@ final class IInputMethodSessionInvoker { @AnyThread void updateSelection(int oldSelStart, int oldSelEnd, int selStart, int selEnd, int candidatesStart, int candidatesEnd) { + if (mCustomHandler == null) { + updateSelectionInternal( + oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd); + } else { + mCustomHandler.post(() -> updateSelectionInternal( + oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd)); + } + } + + @AnyThread + private void updateSelectionInternal(int oldSelStart, int oldSelEnd, int selStart, int selEnd, + int candidatesStart, int candidatesEnd) { try { mSession.updateSelection( oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd); @@ -137,6 +249,17 @@ final class IInputMethodSessionInvoker { @AnyThread void invalidateInput(EditorInfo editorInfo, IRemoteInputConnection inputConnection, int sessionId) { + if (mCustomHandler == null) { + invalidateInputInternal(editorInfo, inputConnection, sessionId); + } else { + mCustomHandler.post(() -> invalidateInputInternal(editorInfo, inputConnection, + sessionId)); + } + } + + @AnyThread + private void invalidateInputInternal(EditorInfo editorInfo, + IRemoteInputConnection inputConnection, int sessionId) { try { mSession.invalidateInput(editorInfo, inputConnection, sessionId); } catch (RemoteException e) {