From 5e2d9f271d1f24fc946661579628fb9a817143dc Mon Sep 17 00:00:00 2001 From: Wilson Wu Date: Thu, 22 Apr 2021 18:18:11 +0800 Subject: [PATCH] Async startInputOrWindowGainedFocus We ignore the result if startInputReason is WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION or WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION in startInputOrWindowGainedFocus. Introduce USE_REPORT_WINDOW_GAINED_FOCUS_ASYNC flag to control following behavior: -. Don't wait the result if it only report the focus gain. Have another IPC for this case to prevent the UI thread be blocked. -. Throw the exception to the client side if it happened. Bug: 185928120 Test: atest CtsInputMethodTestCases Change-Id: Ic584203c1221fbae17f5e2d8f09e3992df061646 --- .../view/inputmethod/InputMethodManager.java | 50 +++++-- .../internal/view/IInputMethodClient.aidl | 1 + .../internal/view/IInputMethodManager.aidl | 6 + .../InputMethodManagerService.java | 138 +++++++++++------- .../MultiClientInputMethodManagerService.java | 31 +++- 5 files changed, 157 insertions(+), 69 deletions(-) diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index d6292caba3443..d15aee0941e81 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -265,6 +265,14 @@ public final class InputMethodManager { private static final int NOT_A_SUBTYPE_ID = -1; + /** + * {@code true} to try to avoid blocking apps' UI thread by sending + * {@link StartInputReason#WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION} and + * {@link StartInputReason#WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION} in a truly asynchronous + * way. {@code false} to go back to the previous synchronous semantics. + */ + private static final boolean USE_REPORT_WINDOW_GAINED_FOCUS_ASYNC = false; + /** * A constant that represents Voice IME. * @@ -689,20 +697,29 @@ public final class InputMethodManager { Log.v(TAG, "Reporting focus gain, without startInput" + ", nextFocusIsServedView=" + nextFocusHasConnection); } - final int startInputReason = - nextFocusHasConnection ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION - : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION; - final Completable.InputBindResult value = Completable.createInputBindResult(); - mService.startInputOrWindowGainedFocus( - startInputReason, mClient, - focusedView.getWindowToken(), startInputFlags, softInputMode, - windowFlags, - null, - null, - 0 /* missingMethodFlags */, - mCurRootView.mContext.getApplicationInfo().targetSdkVersion, - ResultCallbacks.of(value)); - Completable.getResult(value); // ignore the result + + if (USE_REPORT_WINDOW_GAINED_FOCUS_ASYNC) { + mService.reportWindowGainedFocusAsync( + nextFocusHasConnection, mClient, focusedView.getWindowToken(), + startInputFlags, softInputMode, windowFlags, + mCurRootView.mContext.getApplicationInfo().targetSdkVersion); + } else { + final int startInputReason = nextFocusHasConnection + ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION + : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION; + final Completable.InputBindResult value = + Completable.createInputBindResult(); + mService.startInputOrWindowGainedFocus( + startInputReason, mClient, + focusedView.getWindowToken(), startInputFlags, softInputMode, + windowFlags, + null, + null, + 0 /* missingMethodFlags */, + mCurRootView.mContext.getApplicationInfo().targetSdkVersion, + ResultCallbacks.of(value)); + Completable.getResult(value); // ignore the result + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1087,6 +1104,11 @@ public final class InputMethodManager { public void setImeTraceEnabled(boolean enabled) { ImeTracing.getInstance().setEnabled(enabled); } + + @Override + public void throwExceptionFromSystem(String message) { + throw new RuntimeException(message); + } }; final InputConnection mDummyInputConnection = new BaseInputConnection(this, false); diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index 49dbbaaa17f72..a61e86bcf2a65 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -30,4 +30,5 @@ oneway interface IInputMethodClient { void reportFullscreenMode(boolean fullscreen); void updateActivityViewToScreenMatrix(int bindSequence, in float[] matrixValues); void setImeTraceEnabled(boolean enabled); + void throwExceptionFromSystem(String message); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 93cd4e9046c63..772e3449355d9 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -68,6 +68,12 @@ interface IInputMethodManager { int unverifiedTargetSdkVersion, in IInputBindResultResultCallback inputBindResult); + oneway void reportWindowGainedFocusAsync( + boolean nextFocusHasConnection, in IInputMethodClient client, in IBinder windowToken, + /* @StartInputFlags */ int startInputFlags, + /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode, + int windowFlags, int unverifiedTargetSdkVersion); + oneway void showInputMethodPickerFromClient(in IInputMethodClient client, int auxiliarySubtypeMode, in IVoidResultCallback resultCallback); oneway void showInputMethodPickerFromSystem(in IInputMethodClient client, diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 94a5099b45dad..84988a21eeff2 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3346,6 +3346,28 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return res; } + @NonNull + @Override + public void reportWindowGainedFocusAsync( + boolean nextFocusHasConnection, IInputMethodClient client, IBinder windowToken, + @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, + int windowFlags, int unverifiedTargetSdkVersion) { + final int startInputReason = nextFocusHasConnection + ? StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION + : StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION; + try { + startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken, + startInputFlags, softInputMode, windowFlags, null /* attribute */, + null /* inputContext */, 0 /* missingMethods */, unverifiedTargetSdkVersion); + } catch (Throwable t) { + if (client != null) { + try { + client.throwExceptionFromSystem(t.getMessage()); + } catch (RemoteException ignore) { } + } + } + } + @NonNull @Override public void startInputOrWindowGainedFocus( @@ -3354,62 +3376,72 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext, @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion, IInputBindResultResultCallback resultCallback) { - CallbackUtils.onResult(resultCallback, (Supplier) () -> { - if (windowToken == null) { - Slog.e(TAG, "windowToken cannot be null."); + CallbackUtils.onResult(resultCallback, (Supplier) () -> + startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken, + startInputFlags, softInputMode, windowFlags, attribute, inputContext, + missingMethods, unverifiedTargetSdkVersion)); + } + + @NonNull + private InputBindResult startInputOrWindowGainedFocusInternal( + @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, + @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, + int windowFlags, @Nullable EditorInfo attribute, @Nullable IInputContext inputContext, + @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) { + if (windowToken == null) { + Slog.e(TAG, "windowToken cannot be null."); + return InputBindResult.NULL; + } + try { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, + "IMMS.startInputOrWindowGainedFocus"); + ImeTracing.getInstance().triggerManagerServiceDump( + "InputMethodManagerService#startInputOrWindowGainedFocus"); + final int callingUserId = UserHandle.getCallingUserId(); + final int userId; + if (attribute != null && attribute.targetInputMethodUser != null + && attribute.targetInputMethodUser.getIdentifier() != callingUserId) { + mContext.enforceCallingPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "Using EditorInfo.targetInputMethodUser requires" + + " INTERACT_ACROSS_USERS_FULL."); + userId = attribute.targetInputMethodUser.getIdentifier(); + if (!mUserManagerInternal.isUserRunning(userId)) { + // There is a chance that we hit here because of race condition. Let's just + // return an error code instead of crashing the caller process, which at + // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an + // important process. + Slog.e(TAG, "User #" + userId + " is not running."); + return InputBindResult.INVALID_USER; + } + } else { + userId = callingUserId; + } + final InputBindResult result; + synchronized (mMethodMap) { + final long ident = Binder.clearCallingIdentity(); + try { + result = startInputOrWindowGainedFocusInternalLocked(startInputReason, + client, windowToken, startInputFlags, softInputMode, windowFlags, + attribute, inputContext, missingMethods, unverifiedTargetSdkVersion, + userId); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + if (result == null) { + // This must never happen, but just in case. + Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason=" + + InputMethodDebug.startInputReasonToString(startInputReason) + + " windowFlags=#" + Integer.toHexString(windowFlags) + + " editorInfo=" + attribute); return InputBindResult.NULL; } - try { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, - "IMMS.startInputOrWindowGainedFocus"); - ImeTracing.getInstance().triggerManagerServiceDump( - "InputMethodManagerService#startInputOrWindowGainedFocus"); - final int callingUserId = UserHandle.getCallingUserId(); - final int userId; - if (attribute != null && attribute.targetInputMethodUser != null - && attribute.targetInputMethodUser.getIdentifier() != callingUserId) { - mContext.enforceCallingPermission( - Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "Using EditorInfo.targetInputMethodUser requires" - + " INTERACT_ACROSS_USERS_FULL."); - userId = attribute.targetInputMethodUser.getIdentifier(); - if (!mUserManagerInternal.isUserRunning(userId)) { - // There is a chance that we hit here because of race condition. Let's just - // return an error code instead of crashing the caller process, which at - // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an - // important process. - Slog.e(TAG, "User #" + userId + " is not running."); - return InputBindResult.INVALID_USER; - } - } else { - userId = callingUserId; - } - final InputBindResult result; - synchronized (mMethodMap) { - final long ident = Binder.clearCallingIdentity(); - try { - result = startInputOrWindowGainedFocusInternalLocked(startInputReason, - client, windowToken, startInputFlags, softInputMode, windowFlags, - attribute, inputContext, missingMethods, unverifiedTargetSdkVersion, - userId); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - if (result == null) { - // This must never happen, but just in case. - Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason=" - + InputMethodDebug.startInputReasonToString(startInputReason) - + " windowFlags=#" + Integer.toHexString(windowFlags) - + " editorInfo=" + attribute); - return InputBindResult.NULL; - } - return result; - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - }); + return result; + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } } @NonNull diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 885093d614868..679fe2ac58426 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1621,6 +1621,33 @@ public final class MultiClientInputMethodManagerService { } } + @BinderThread + @Override + public void reportWindowGainedFocusAsync( + boolean nextFocusHasConnection, + @Nullable IInputMethodClient client, + @Nullable IBinder windowToken, + @StartInputFlags int startInputFlags, + @SoftInputModeFlags int softInputMode, + int windowFlags, + int unverifiedTargetSdkVersion) { + final int startInputReason = nextFocusHasConnection + ? StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION + : StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION; + try { + startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken, + startInputFlags, softInputMode, windowFlags, null /* editorInfo */, + null /* inputContext */, 0 /* missingMethods */, + unverifiedTargetSdkVersion); + } catch (Throwable t) { + if (client != null) { + try { + client.throwExceptionFromSystem(t.getMessage()); + } catch (RemoteException ignore) { } + } + } + } + @BinderThread @Override public void startInputOrWindowGainedFocus( @@ -1637,8 +1664,8 @@ public final class MultiClientInputMethodManagerService { IInputBindResultResultCallback resultCallback) { CallbackUtils.onResult(resultCallback, (Supplier) () -> startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken, - startInputFlags, softInputMode, windowFlags, editorInfo, inputContext, - missingMethods, unverifiedTargetSdkVersion)); + startInputFlags, softInputMode, windowFlags, editorInfo, inputContext, + missingMethods, unverifiedTargetSdkVersion)); } @BinderThread