diff options
8 files changed, 641 insertions, 9 deletions
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index dc5e0e5c62aa..88ca2a48d2f4 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -66,6 +66,7 @@ final class IInputMethodManagerGlobalInvoker { @Nullable private static volatile IImeTracker sTrackerServiceCache = null; + private static int sCurStartInputSeq = 0; /** * @return {@code true} if {@link IInputMethodManager} is available. @@ -327,6 +328,7 @@ final class IInputMethodManagerGlobalInvoker { } } + // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled. @AnyThread @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) @@ -353,6 +355,41 @@ final class IInputMethodManagerGlobalInvoker { } } + /** + * Returns a sequence number for startInput. + */ + @AnyThread + @NonNull + @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) + static int startInputOrWindowGainedFocusAsync(@StartInputReason int startInputReason, + @NonNull IInputMethodClient client, @Nullable IBinder windowToken, + @StartInputFlags int startInputFlags, + @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, + @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo, + @Nullable IRemoteInputConnection remoteInputConnection, + @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, + int unverifiedTargetSdkVersion, @UserIdInt int userId, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { + final IInputMethodManager service = getService(); + if (service == null) { + return -1; + } + try { + service.startInputOrWindowGainedFocusAsync(startInputReason, client, windowToken, + startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection, + remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId, + imeDispatcher, advanceAngGetStartInputSequenceNumber()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return sCurStartInputSeq; + } + + private static int advanceAngGetStartInputSequenceNumber() { + return ++sCurStartInputSeq; + } + + @AnyThread static void showInputMethodPickerFromClient(@NonNull IInputMethodClient client, int auxiliarySubtypeMode) { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index f4b09df35705..72125ba999ad 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -321,6 +321,22 @@ public final class InputMethodManager { }; /** + * A runnable that reports {@link InputConnection} opened event for calls to + * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync}. + */ + private abstract static class ReportInputConnectionOpenedRunner implements Runnable { + /** + * Sequence number to track startInput requests to + * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync} + */ + int mSequenceNum; + ReportInputConnectionOpenedRunner(int sequenceNum) { + this.mSequenceNum = sequenceNum; + } + } + private ReportInputConnectionOpenedRunner mReportInputConnectionOpenedRunner; + + /** * Ensures that {@link #sInstance} becomes non-{@code null} for application that have directly * or indirectly relied on {@link #sInstance} via reflection or something like that. * @@ -691,6 +707,7 @@ public final class InputMethodManager { private static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12; private static final int MSG_SET_INTERACTIVE = 13; private static final int MSG_ON_SHOW_REQUESTED = 31; + private static final int MSG_START_INPUT_RESULT = 40; /** * Calling this will invalidate Local stylus handwriting availability Cache which @@ -1045,7 +1062,7 @@ public final class InputMethodManager { return; } case MSG_BIND: { - final InputBindResult res = (InputBindResult)msg.obj; + final InputBindResult res = (InputBindResult) msg.obj; if (DEBUG) { Log.i(TAG, "handleMessage: MSG_BIND " + res.sequence + "," + res.id); } @@ -1071,6 +1088,60 @@ public final class InputMethodManager { startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0); return; } + + case MSG_START_INPUT_RESULT: { + final InputBindResult res = (InputBindResult) msg.obj; + final int startInputSeq = msg.arg1; + if (res == null) { + // IMMS logs .wtf already. + return; + } + if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); + synchronized (mH) { + if (res.id != null) { + updateInputChannelLocked(res.channel); + mCurMethod = res.method; // for @UnsupportedAppUsage + mCurBindState = new BindState(res); + mAccessibilityInputMethodSession.clear(); + if (res.accessibilitySessions != null) { + for (int i = 0; i < res.accessibilitySessions.size(); i++) { + IAccessibilityInputMethodSessionInvoker wrapper = + IAccessibilityInputMethodSessionInvoker.createOrNull( + res.accessibilitySessions.valueAt(i)); + if (wrapper != null) { + mAccessibilityInputMethodSession.append( + res.accessibilitySessions.keyAt(i), wrapper); + } + } + } + mCurId = res.id; // for @UnsupportedAppUsage + } else if (res.channel != null && res.channel != mCurChannel) { + res.channel.dispose(); + } + switch (res.result) { + case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW: + mRestartOnNextWindowFocus = true; + mServedView = null; + break; + } + if (mCompletions != null) { + if (isImeSessionAvailableLocked()) { + mCurBindState.mImeSession.displayCompletions(mCompletions); + } + } + + if (res != null + && res.method != null + && mServedView != null + && mReportInputConnectionOpenedRunner != null + && mReportInputConnectionOpenedRunner.mSequenceNum + == startInputSeq) { + mReportInputConnectionOpenedRunner.run(); + } + mReportInputConnectionOpenedRunner = null; + } + return; + } case MSG_UNBIND: { final int sequence = msg.arg1; @UnbindReason @@ -1322,6 +1393,12 @@ public final class InputMethodManager { } @Override + public void onStartInputResult(InputBindResult res, int startInputSeq) { + mH.obtainMessage(MSG_START_INPUT_RESULT, startInputSeq, -1 /* unused */, res) + .sendToTarget(); + } + + @Override public void onBindAccessibilityService(InputBindResult res, int id) { mH.obtainMessage(MSG_BIND_ACCESSIBILITY_SERVICE, id, 0, res).sendToTarget(); } @@ -2010,6 +2087,7 @@ public final class InputMethodManager { mServedConnecting = false; clearConnectionLocked(); } + mReportInputConnectionOpenedRunner = null; // Clear the back callbacks held by the ime dispatcher to avoid memory leaks. mImeDispatcher.clear(); } @@ -3080,14 +3158,52 @@ public final class InputMethodManager { final int targetUserId = editorInfo.targetInputMethodUser != null ? editorInfo.targetInputMethodUser.getIdentifier() : UserHandle.myUserId(); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus"); - res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus( - startInputReason, mClient, windowGainingFocus, startInputFlags, - softInputMode, windowFlags, editorInfo, servedInputConnection, - servedInputConnection == null ? null - : servedInputConnection.asIRemoteAccessibilityInputConnection(), - view.getContext().getApplicationInfo().targetSdkVersion, targetUserId, - mImeDispatcher); + + int startInputSeq = -1; + if (Flags.useZeroJankProxy()) { + // async result delivered via MSG_START_INPUT_RESULT. + startInputSeq = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocusAsync( + startInputReason, mClient, windowGainingFocus, startInputFlags, + softInputMode, windowFlags, editorInfo, servedInputConnection, + servedInputConnection == null ? null + : servedInputConnection.asIRemoteAccessibilityInputConnection(), + view.getContext().getApplicationInfo().targetSdkVersion, targetUserId, + mImeDispatcher); + } else { + res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus( + startInputReason, mClient, windowGainingFocus, startInputFlags, + softInputMode, windowFlags, editorInfo, servedInputConnection, + servedInputConnection == null ? null + : servedInputConnection.asIRemoteAccessibilityInputConnection(), + view.getContext().getApplicationInfo().targetSdkVersion, targetUserId, + mImeDispatcher); + } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + if (Flags.useZeroJankProxy()) { + // Create a runnable for delayed notification to the app that the InputConnection is + // initialized and ready for use. + if (ic != null) { + final int seqId = startInputSeq; + mReportInputConnectionOpenedRunner = + new ReportInputConnectionOpenedRunner(startInputSeq) { + @Override + public void run() { + if (DEBUG) { + Log.v(TAG, "Calling View.onInputConnectionOpened: view= " + + view + + ", ic=" + ic + ", editorInfo=" + editorInfo + + ", handler=" + + icHandler + ", startInputSeq=" + seqId); + } + reportInputConnectionOpened(ic, editorInfo, icHandler, view); + } + }; + } else { + mReportInputConnectionOpenedRunner = null; + } + return true; + } + if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); if (res == null) { Log.wtf(TAG, "startInputOrWindowGainedFocus must not return" @@ -3118,6 +3234,7 @@ public final class InputMethodManager { } else if (res.channel != null && res.channel != mCurChannel) { res.channel.dispose(); } + switch (res.result) { case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW: mRestartOnNextWindowFocus = true; diff --git a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl index 9251d2d5dc31..babd9a0950fd 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl @@ -24,6 +24,7 @@ import com.android.internal.inputmethod.InputBindResult; */ oneway interface IInputMethodClient { void onBindMethod(in InputBindResult res); + void onStartInputResult(in InputBindResult res, int startInputSeq); void onBindAccessibilityService(in InputBindResult res, int id); void onUnbindMethod(int sequence, int unbindReason); void onUnbindAccessibilityService(int sequence, int id); diff --git a/core/java/com/android/internal/inputmethod/InputBindResult.java b/core/java/com/android/internal/inputmethod/InputBindResult.java index b6eca07a0858..243b1031bd4b 100644 --- a/core/java/com/android/internal/inputmethod/InputBindResult.java +++ b/core/java/com/android/internal/inputmethod/InputBindResult.java @@ -271,6 +271,7 @@ public final class InputBindResult implements Parcelable { public String toString() { return "InputBindResult{result=" + getResultString() + " method=" + method + " id=" + id + " sequence=" + sequence + + " result=" + result + " isInputMethodSuppressingSpellChecker=" + isInputMethodSuppressingSpellChecker + "}"; } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index e95127be8543..b90f8bf5ea00 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -69,6 +69,8 @@ interface IInputMethodManager { boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, in @nullable ImeTracker.Token statsToken, int flags, in @nullable ResultReceiver resultReceiver, int reason); + + // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled. // If windowToken is null, this just does startInput(). Otherwise this reports that a window // has gained focus, and if 'editorInfo' is non-null then also does startInput. // @NonNull @@ -85,6 +87,21 @@ interface IInputMethodManager { int unverifiedTargetSdkVersion, int userId, in ImeOnBackInvokedDispatcher imeDispatcher); + // If windowToken is null, this just does startInput(). Otherwise this reports that a window + // has gained focus, and if 'editorInfo' is non-null then also does startInput. + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)") + void startInputOrWindowGainedFocusAsync( + /* @StartInputReason */ int startInputReason, + in IInputMethodClient client, in @nullable IBinder windowToken, + /* @StartInputFlags */ int startInputFlags, + /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode, + /* @android.view.WindowManager.LayoutParams.Flags */ int windowFlags, + in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection, + in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, + int unverifiedTargetSdkVersion, int userId, + in ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq); + void showInputMethodPickerFromClient(in IInputMethodClient client, int auxiliarySubtypeMode); @@ -156,6 +173,7 @@ interface IInputMethodManager { in String delegatePackageName, in String delegatorPackageName); + // TODO(b/293640003): introduce a new API method to provide async way to return boolean. /** Accepts and starts a stylus handwriting session for the delegate view **/ boolean acceptStylusHandwritingDelegation(in IInputMethodClient client, in int userId, in String delegatePackageName, in String delegatorPackageName, int flags); diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java index 977dbff0b02e..84a59b4d28e4 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java @@ -117,6 +117,30 @@ final class IInputMethodClientInvoker { } @AnyThread + void onStartInputResult(@NonNull InputBindResult res, int startInputSeq) { + if (mIsProxy) { + onStartInputResultInternal(res, startInputSeq); + } else { + mHandler.post(() -> onStartInputResultInternal(res, startInputSeq)); + } + } + + @AnyThread + private void onStartInputResultInternal(@NonNull InputBindResult res, int startInputSeq) { + try { + mTarget.onStartInputResult(res, startInputSeq); + } catch (RemoteException e) { + logRemoteException(e); + } finally { + // Dispose the channel if the input method is not local to this process + // because the remote proxy will get its own copy when unparceled. + if (res.channel != null && mIsProxy) { + res.channel.dispose(); + } + } + } + + @AnyThread void onBindAccessibilityService(@NonNull InputBindResult res, int id) { if (mIsProxy) { onBindAccessibilityServiceInternal(res, id); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index bc169ca40117..5574d1868925 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1539,7 +1539,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onStart() { mService.publishLocalService(); - publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/, + IInputMethodManager.Stub service; + if (Flags.useZeroJankProxy()) { + service = new ZeroJankProxy(mService.mHandler::post, mService); + } else { + service = mService; + } + publishBinderService(Context.INPUT_METHOD_SERVICE, service, false /*allowIsolated*/, DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO); } @@ -2216,6 +2222,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } + @Nullable + ClientState getClientState(IInputMethodClient client) { + synchronized (ImfLock.class) { + return mClientController.getClient(client.asBinder()); + } + } + + // TODO(b/314150112): Move this to ClientController. @GuardedBy("ImfLock.class") void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) { if (mCurClient != null) { @@ -3741,6 +3755,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS; } + //TODO(b/293640003): merge with startInputOrWindowGainedFocus once Flags.useZeroJankProxy() + // is enabled. + @Override + public void startInputOrWindowGainedFocusAsync( + @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, + @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, + int windowFlags, @Nullable EditorInfo editorInfo, + IRemoteInputConnection inputConnection, + IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, + int unverifiedTargetSdkVersion, @UserIdInt int userId, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) { + // implemented by ZeroJankProxy + } + @NonNull @Override public InputBindResult startInputOrWindowGainedFocus( diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java new file mode 100644 index 000000000000..692fd7dcceae --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2024 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. + */ + +/* + * Copyright (C) 2024 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.server.inputmethod; + +import static com.android.server.inputmethod.InputMethodManagerService.TAG; + +import android.Manifest; +import android.annotation.BinderThread; +import android.annotation.EnforcePermission; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.UserIdInt; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.util.ExceptionUtils; +import android.util.Slog; +import android.view.WindowManager; +import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ImeTracker; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; +import android.window.ImeOnBackInvokedDispatcher; + +import com.android.internal.inputmethod.DirectBootAwareness; +import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; +import com.android.internal.inputmethod.IImeTracker; +import com.android.internal.inputmethod.IInputMethodClient; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +import com.android.internal.inputmethod.IRemoteInputConnection; +import com.android.internal.inputmethod.InputBindResult; +import com.android.internal.inputmethod.SoftInputShowHideReason; +import com.android.internal.inputmethod.StartInputFlags; +import com.android.internal.inputmethod.StartInputReason; +import com.android.internal.util.FunctionalUtils.ThrowingRunnable; +import com.android.internal.view.IInputMethodManager; + +import java.io.FileDescriptor; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +/** + * A proxy that processes all {@link IInputMethodManager} calls asynchronously. + * @hide + */ +public class ZeroJankProxy extends IInputMethodManager.Stub { + + private final IInputMethodManager mInner; + private final Executor mExecutor; + + ZeroJankProxy(Executor executor, IInputMethodManager inner) { + mInner = inner; + mExecutor = executor; + } + + private void offload(ThrowingRunnable r) { + offloadInner(r); + } + + private void offload(Runnable r) { + offloadInner(r); + } + + private void offloadInner(Runnable r) { + boolean useThrowingRunnable = r instanceof ThrowingRunnable; + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + final long inner = Binder.clearCallingIdentity(); + // Restoring calling identity, so we can still do permission checks on caller. + Binder.restoreCallingIdentity(identity); + try { + try { + if (useThrowingRunnable) { + ((ThrowingRunnable) r).runOrThrow(); + } else { + r.run(); + } + } catch (Exception e) { + Slog.e(TAG, "Error in async call", e); + throw ExceptionUtils.propagate(e); + } + } finally { + Binder.restoreCallingIdentity(inner); + } + }); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection, + int selfReportedDisplayId) throws RemoteException { + offload(() -> mInner.addClient(client, inputConnection, selfReportedDisplayId)); + } + + @Override + public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) throws RemoteException { + return mInner.getCurrentInputMethodInfoAsUser(userId); + } + + @Override + public List<InputMethodInfo> getInputMethodList( + int userId, @DirectBootAwareness int directBootAwareness) throws RemoteException { + return mInner.getInputMethodList(userId, directBootAwareness); + } + + @Override + public List<InputMethodInfo> getEnabledInputMethodList(int userId) throws RemoteException { + return mInner.getEnabledInputMethodList(userId); + } + + @Override + public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, + boolean allowsImplicitlyEnabledSubtypes, int userId) + throws RemoteException { + return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes, + userId); + } + + @Override + public InputMethodSubtype getLastInputMethodSubtype(int userId) throws RemoteException { + return mInner.getLastInputMethodSubtype(userId); + } + + @Override + public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, + @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + int lastClickTooType, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) + throws RemoteException { + offload(() -> mInner.showSoftInput(client, windowToken, statsToken, flags, lastClickTooType, + resultReceiver, reason)); + return true; + } + + @Override + public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, + @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, + ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) + throws RemoteException { + offload(() -> mInner.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver, + reason)); + return true; + } + + @Override + public void startInputOrWindowGainedFocusAsync( + @StartInputReason int startInputReason, + IInputMethodClient client, IBinder windowToken, + @StartInputFlags int startInputFlags, + @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, + int windowFlags, @Nullable EditorInfo editorInfo, + IRemoteInputConnection inputConnection, + IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, + int unverifiedTargetSdkVersion, @UserIdInt int userId, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) + throws RemoteException { + offload(() -> { + InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client, + windowToken, startInputFlags, softInputMode, windowFlags, + editorInfo, + inputConnection, remoteAccessibilityInputConnection, + unverifiedTargetSdkVersion, + userId, imeDispatcher); + sendOnStartInputResult(client, result, startInputSeq); + }); + } + + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + @Override + public InputBindResult startInputOrWindowGainedFocus( + @StartInputReason int startInputReason, + IInputMethodClient client, IBinder windowToken, + @StartInputFlags int startInputFlags, + @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, + int windowFlags, @Nullable EditorInfo editorInfo, + IRemoteInputConnection inputConnection, + IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, + int unverifiedTargetSdkVersion, @UserIdInt int userId, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) + throws RemoteException { + // Should never be called when flag is enabled i.e. when this proxy is used. + return null; + } + + @Override + public void showInputMethodPickerFromClient(IInputMethodClient client, + int auxiliarySubtypeMode) + throws RemoteException { + offload(() -> mInner.showInputMethodPickerFromClient(client, auxiliarySubtypeMode)); + } + + @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS) + @Override + public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) + throws RemoteException { + mInner.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId); + } + + @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD) + @Override + public boolean isInputMethodPickerShownForTest() throws RemoteException { + super.isInputMethodPickerShownForTest_enforcePermission(); + return mInner.isInputMethodPickerShownForTest(); + } + + @Override + public InputMethodSubtype getCurrentInputMethodSubtype(int userId) throws RemoteException { + return mInner.getCurrentInputMethodSubtype(userId); + } + + @Override + public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes, + @UserIdInt int userId) throws RemoteException { + mInner.setAdditionalInputMethodSubtypes(imiId, subtypes, userId); + } + + @Override + public void setExplicitlyEnabledInputMethodSubtypes(String imeId, + @NonNull int[] subtypeHashCodes, @UserIdInt int userId) throws RemoteException { + mInner.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId); + } + + @Override + public int getInputMethodWindowVisibleHeight(IInputMethodClient client) + throws RemoteException { + return mInner.getInputMethodWindowVisibleHeight(client); + } + + @Override + public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) + throws RemoteException { + // Already async TODO(b/293640003): ordering issues? + mInner.reportPerceptibleAsync(windowToken, perceptible); + } + + @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW) + @Override + public void removeImeSurface() throws RemoteException { + mInner.removeImeSurface(); + } + + @Override + public void removeImeSurfaceFromWindowAsync(IBinder windowToken) throws RemoteException { + mInner.removeImeSurfaceFromWindowAsync(windowToken); + } + + @Override + public void startProtoDump(byte[] bytes, int i, String s) throws RemoteException { + mInner.startProtoDump(bytes, i, s); + } + + @Override + public boolean isImeTraceEnabled() throws RemoteException { + return mInner.isImeTraceEnabled(); + } + + @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING) + @Override + public void startImeTrace() throws RemoteException { + mInner.startImeTrace(); + } + + @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING) + @Override + public void stopImeTrace() throws RemoteException { + mInner.stopImeTrace(); + } + + @Override + public void startStylusHandwriting(IInputMethodClient client) + throws RemoteException { + offload(() -> mInner.startStylusHandwriting(client)); + } + + @Override + public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId, + @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName, + @Nullable String delegatorPackageName, + @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException { + offload(() -> mInner.startConnectionlessStylusHandwriting( + client, userId, cursorAnchorInfo, delegatePackageName, delegatorPackageName, + callback)); + } + + @Override + public boolean acceptStylusHandwritingDelegation( + @NonNull IInputMethodClient client, + @UserIdInt int userId, + @NonNull String delegatePackageName, + @NonNull String delegatorPackageName, + @InputMethodManager.HandwritingDelegateFlags int flags) { + try { + return CompletableFuture.supplyAsync(() -> { + try { + return mInner.acceptStylusHandwritingDelegation( + client, userId, delegatePackageName, delegatorPackageName, flags); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + }, this::offload).get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public void prepareStylusHandwritingDelegation( + @NonNull IInputMethodClient client, + @UserIdInt int userId, + @NonNull String delegatePackageName, + @NonNull String delegatorPackageName) { + offload(() -> mInner.prepareStylusHandwritingDelegation( + client, userId, delegatePackageName, delegatorPackageName)); + } + + @Override + public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless) + throws RemoteException { + return mInner.isStylusHandwritingAvailableAsUser(userId, connectionless); + } + + @EnforcePermission("android.permission.TEST_INPUT_METHOD") + @Override + public void addVirtualStylusIdForTestSession(IInputMethodClient client) + throws RemoteException { + mInner.addVirtualStylusIdForTestSession(client); + } + + @EnforcePermission("android.permission.TEST_INPUT_METHOD") + @Override + public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout) + throws RemoteException { + mInner.setStylusWindowIdleTimeoutForTest(client, timeout); + } + + @Override + public IImeTracker getImeTrackerService() throws RemoteException { + return mInner.getImeTrackerService(); + } + + @BinderThread + @Override + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + ((InputMethodManagerService) mInner).onShellCommand( + in, out, err, args, callback, resultReceiver); + } + + private void sendOnStartInputResult( + IInputMethodClient client, InputBindResult res, int startInputSeq) { + InputMethodManagerService service = (InputMethodManagerService) mInner; + final ClientState cs = service.getClientState(client); + if (cs != null && cs.mClient != null) { + cs.mClient.onStartInputResult(res, startInputSeq); + } else { + // client is unbound. + Slog.i(TAG, "Client that requested startInputOrWindowGainedFocus is no longer" + + " bound. InputBindResult: " + res + " for startInputSeq: " + startInputSeq); + } + } +} + |