diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index c3d3985b5ac38..8d2c2d96637fd 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -88,8 +88,10 @@ import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillManager; import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.Completable; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry; +import com.android.internal.inputmethod.ResultCallbacks; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.inputmethod.UnbindReason; @@ -666,6 +668,7 @@ public final class InputMethodManager { 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, @@ -673,7 +676,9 @@ public final class InputMethodManager { null, null, 0 /* missingMethodFlags */, - mCurRootView.mContext.getApplicationInfo().targetSdkVersion); + mCurRootView.mContext.getApplicationInfo().targetSdkVersion, + ResultCallbacks.of(value)); + Completable.getResult(value); // ignore the result } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2039,10 +2044,13 @@ public final class InputMethodManager { if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic=" + ic + " tba=" + tba + " startInputFlags=" + InputMethodDebug.startInputFlagsToString(startInputFlags)); - res = mService.startInputOrWindowGainedFocus( + final Completable.InputBindResult value = Completable.createInputBindResult(); + mService.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, startInputFlags, softInputMode, windowFlags, tba, servedContext, missingMethodFlags, - view.getContext().getApplicationInfo().targetSdkVersion); + view.getContext().getApplicationInfo().targetSdkVersion, + ResultCallbacks.of(value)); + res = Completable.getResult(value); if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); if (res == null) { Log.wtf(TAG, "startInputOrWindowGainedFocus must not return" diff --git a/core/java/com/android/internal/inputmethod/CallbackUtils.java b/core/java/com/android/internal/inputmethod/CallbackUtils.java new file mode 100644 index 0000000000000..ec6779216ae51 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/CallbackUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 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.os.RemoteException; + +import com.android.internal.view.InputBindResult; + +import java.util.function.Supplier; + +/** + * Defines a set of helper methods to callback corresponding results in {@link ResultCallbacks}. + */ +public final class CallbackUtils { + + /** + * Not intended to be instantiated. + */ + private CallbackUtils() { + } + + /** + * A utility method using given {@link IInputBindResultResultCallback} to callback the + * {@link InputBindResult}. + * + * @param callback {@link IInputBindResultResultCallback} to be called back. + * @param resultSupplier the supplier from which {@link InputBindResult} is provided. + */ + @AnyThread + public static void onResult(@NonNull IInputBindResultResultCallback callback, + @NonNull Supplier resultSupplier) { + try { + callback.onResult(resultSupplier.get()); + } catch (RemoteException ignored) { } + } +} diff --git a/core/java/com/android/internal/inputmethod/Completable.java b/core/java/com/android/internal/inputmethod/Completable.java index d8d1a7df6aa81..b9e1cf09dc070 100644 --- a/core/java/com/android/internal/inputmethod/Completable.java +++ b/core/java/com/android/internal/inputmethod/Completable.java @@ -124,6 +124,16 @@ public final class Completable { return true; } } + + /** + * Blocks the calling thread until this object becomes ready to return the value. + */ + @AnyThread + public void await() { + try { + mLatch.await(); + } catch (InterruptedException ignored) { } + } } /** @@ -249,6 +259,13 @@ public final class Completable { return new Completable.SurroundingText(); } + /** + * @return an instance of {@link Completable.InputBindResult}. + */ + public static Completable.InputBindResult createInputBindResult() { + return new Completable.InputBindResult(); + } + /** * Completable object of {@link java.lang.Boolean}. */ @@ -277,6 +294,18 @@ public final class Completable { public static final class InputBindResult extends Values { } + /** + * Await the result by the {@link Completable.Values}. + * + * @return the result once {@link ValueBase#onComplete()} + */ + @AnyThread + @Nullable + public static T getResult(@NonNull Completable.Values value) { + value.await(); + return value.getValue(); + } + /** * Await the result by the {@link Completable.Int}, and log it if there is no result after * given timeout. diff --git a/core/java/com/android/internal/inputmethod/IInputBindResultResultCallback.aidl b/core/java/com/android/internal/inputmethod/IInputBindResultResultCallback.aidl new file mode 100644 index 0000000000000..b52b3b100ed08 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/IInputBindResultResultCallback.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2020 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 com.android.internal.view.InputBindResult; + +oneway interface IInputBindResultResultCallback { + void onResult(in InputBindResult result); +} \ No newline at end of file diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java index 7131284e42df2..c59dcf4ce420e 100644 --- a/core/java/com/android/internal/inputmethod/ResultCallbacks.java +++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java @@ -21,6 +21,8 @@ import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.internal.view.InputBindResult; + import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicReference; @@ -154,4 +156,31 @@ public final class ResultCallbacks { } }; } + + /** + * Creates {@link IInputBindResultResultCallback.Stub} that is to set + * {@link Completable.InputBindResult} when receiving the result. + * + * @param value {@link Completable.InputBindResult} to be set when receiving the result. + * @return {@link IInputBindResultResultCallback.Stub} that can be passed as a binder IPC + * parameter. + */ + @AnyThread + public static IInputBindResultResultCallback.Stub of( + @NonNull Completable.InputBindResult value) { + final AtomicReference> + atomicRef = new AtomicReference<>(new WeakReference<>(value)); + + return new IInputBindResultResultCallback.Stub() { + @BinderThread + @Override + public void onResult(InputBindResult result) { + final Completable.InputBindResult value = unwrap(atomicRef); + if (value == null) { + return; + } + value.onComplete(result); + } + }; + } } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 33abbe82c109f..e78ed4e211a79 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -24,6 +24,7 @@ import android.view.inputmethod.EditorInfo; import com.android.internal.view.InputBindResult; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; +import com.android.internal.inputmethod.IInputBindResultResultCallback; /** * Public interface to the global input method manager, used by all client @@ -48,14 +49,15 @@ interface IInputMethodManager { // If windowToken is null, this just does startInput(). Otherwise this reports that a window // has gained focus, and if 'attribute' is non-null then also does startInput. // @NonNull - InputBindResult startInputOrWindowGainedFocus( + void startInputOrWindowGainedFocus( /* @StartInputReason */ int startInputReason, in IInputMethodClient client, in IBinder windowToken, /* @StartInputFlags */ int startInputFlags, /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode, int windowFlags, in EditorInfo attribute, IInputContext inputContext, /* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags, - int unverifiedTargetSdkVersion); + int unverifiedTargetSdkVersion, + in IInputBindResultResultCallback inputBindResult); void showInputMethodPickerFromClient(in IInputMethodClient client, int auxiliarySubtypeMode); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 5189945fc9ae2..a257cde185f3c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -158,6 +158,8 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; import com.android.internal.content.PackageMonitor; +import com.android.internal.inputmethod.CallbackUtils; +import com.android.internal.inputmethod.IInputBindResultResultCallback; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodDebug; @@ -208,6 +210,7 @@ import java.util.Objects; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; /** * This class provides a system service that manages input methods. @@ -3347,63 +3350,68 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @NonNull @Override - public InputBindResult startInputOrWindowGainedFocus( + public void startInputOrWindowGainedFocus( @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo attribute, 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); + @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion, + IInputBindResultResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, (Supplier) () -> { + 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; + } - 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 62d817c22ae61..6bdae63461b27 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -72,6 +72,8 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.inputmethod.CallbackUtils; +import com.android.internal.inputmethod.IInputBindResultResultCallback; import com.android.internal.inputmethod.IMultiClientInputMethod; import com.android.internal.inputmethod.IMultiClientInputMethodPrivilegedOperations; import com.android.internal.inputmethod.IMultiClientInputMethodSession; @@ -104,6 +106,7 @@ import java.lang.annotation.Retention; import java.util.Collections; import java.util.List; import java.util.WeakHashMap; +import java.util.function.Supplier; /** * Actual implementation of multi-client InputMethodManagerService. @@ -1588,7 +1591,26 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public InputBindResult startInputOrWindowGainedFocus( + public void startInputOrWindowGainedFocus( + @StartInputReason int startInputReason, + @Nullable IInputMethodClient client, + @Nullable IBinder windowToken, + @StartInputFlags int startInputFlags, + @SoftInputModeFlags int softInputMode, + int windowFlags, + @Nullable EditorInfo editorInfo, + @Nullable IInputContext inputContext, + @MissingMethodFlags int missingMethods, + int unverifiedTargetSdkVersion, + IInputBindResultResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, (Supplier) () -> + startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken, + startInputFlags, softInputMode, windowFlags, editorInfo, inputContext, + missingMethods, unverifiedTargetSdkVersion)); + } + + @BinderThread + private InputBindResult startInputOrWindowGainedFocusInternal( @StartInputReason int startInputReason, @Nullable IInputMethodClient client, @Nullable IBinder windowToken, @@ -1676,8 +1698,7 @@ public final class MultiClientInputMethodManagerService { clientInfo.mMSInputMethodSession.startInputOrWindowGainedFocus( inputContext, missingMethods, editorInfo, startInputFlags, softInputMode, windowHandle); - } catch (RemoteException e) { - } + } catch (RemoteException ignored) { } break; } return InputBindResult.NULL_EDITOR_INFO; @@ -1708,8 +1729,7 @@ public final class MultiClientInputMethodManagerService { clientInfo.mMSInputMethodSession.startInputOrWindowGainedFocus( inputContext, missingMethods, editorInfo, startInputFlags, softInputMode, windowHandle); - } catch (RemoteException e) { - } + } catch (RemoteException ignored) { } clientInfo.mState = InputMethodClientState.ALREADY_SENT_BIND_RESULT; return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,