diff options
| author | 2024-01-30 05:19:02 +0000 | |
|---|---|---|
| committer | 2024-02-21 18:46:25 +0000 | |
| commit | 1b75fb868ae539d6cb67e172ebf84139f17aa930 (patch) | |
| tree | 04ff258c24be18604ad2f2a0b339e938c7c80854 | |
| parent | 77c6fd2c11f1b7166a8ca93b6243038e13192475 (diff) | |
Zero jank IMF 3/N
Introduce new async API method for accepting stylus delegation.
Bug: 293640003
Test: atest CtsInputMethodTestCase HandwritingInitiatorTest
Change-Id: I732ce7320a31223f3cb5abce5a05513b3a921138
8 files changed, 210 insertions, 16 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 9b89a656dbb1..973a24391246 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -56277,6 +56277,7 @@ package android.view.inputmethod { public final class InputMethodManager { method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View); method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String); + method @FlaggedApi("android.view.inputmethod.use_zero_jank_proxy") public void acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, int); method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent); method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]); diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 676b903472c5..00657ea35e02 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -38,6 +39,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.function.Consumer; /** * Initiates handwriting mode once it detects stylus movement in handwritable areas. @@ -415,27 +417,55 @@ public class HandwritingInitiator { */ @VisibleForTesting public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) { + if (Flags.useZeroJankProxy()) { + tryAcceptStylusHandwritingDelegationAsync(view); + } else { + return tryAcceptStylusHandwritingDelegationInternal(view); + } + return false; + } + + private boolean tryAcceptStylusHandwritingDelegationInternal(@NonNull View view) { String delegatorPackageName = view.getAllowedHandwritingDelegatorPackageName(); if (delegatorPackageName == null) { delegatorPackageName = view.getContext().getOpPackageName(); } if (mImm.acceptStylusHandwritingDelegation(view, delegatorPackageName)) { - if (mState != null) { - mState.mHasInitiatedHandwriting = true; - mState.mShouldInitHandwriting = false; - } - if (view instanceof TextView) { - ((TextView) view).hideHint(); - } - // A handwriting delegate view is accepted and handwriting starts; hide the - // hover icon. - mShowHoverIconForConnectedView = false; + onDelegationAccepted(view); return true; } return false; } + @FlaggedApi(Flags.FLAG_USE_ZERO_JANK_PROXY) + private void tryAcceptStylusHandwritingDelegationAsync(@NonNull View view) { + String delegatorPackageName = + view.getAllowedHandwritingDelegatorPackageName(); + if (delegatorPackageName == null) { + delegatorPackageName = view.getContext().getOpPackageName(); + } + Consumer<Boolean> consumer = delegationAccepted -> { + if (delegationAccepted) { + onDelegationAccepted(view); + } + }; + mImm.acceptStylusHandwritingDelegation(view, delegatorPackageName, view::post, consumer); + } + + private void onDelegationAccepted(View view) { + if (mState != null) { + mState.mHasInitiatedHandwriting = true; + mState.mShouldInitHandwriting = false; + } + if (view instanceof TextView) { + ((TextView) view).hideHint(); + } + // A handwriting delegate view is accepted and handwriting starts; hide the + // hover icon. + mShowHoverIconForConnectedView = false; + } + /** * Notify that the handwriting area for the given view might be updated. * @param view the view whose handwriting area might be updated. diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index 88ca2a48d2f4..491b0e349cde 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -34,6 +34,7 @@ import android.view.WindowManager; import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.DirectBootAwareness; +import com.android.internal.inputmethod.IBooleanListener; import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; @@ -587,6 +588,28 @@ final class IInputMethodManagerGlobalInvoker { } } + /** Returns {@code true} if method is invoked */ + @AnyThread + static boolean acceptStylusHandwritingDelegationAsync( + @NonNull IInputMethodClient client, + @UserIdInt int userId, + @NonNull String delegatePackageName, + @NonNull String delegatorPackageName, + @InputMethodManager.HandwritingDelegateFlags int flags, + @NonNull IBooleanListener callback) { + final IInputMethodManager service = getService(); + if (service == null) { + return false; + } + try { + service.acceptStylusHandwritingDelegationAsync( + client, userId, delegatePackageName, delegatorPackageName, flags, callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return true; + } + @AnyThread @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static boolean isStylusHandwritingAvailableAsUser( diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 72125ba999ad..52384791f74b 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -109,6 +109,7 @@ import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.DirectBootAwareness; +import com.android.internal.inputmethod.IBooleanListener; import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IInputMethodSession; @@ -2518,16 +2519,46 @@ public final class InputMethodManager { view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0); } + private void startStylusHandwritingInternalAsync( + @NonNull View view, @Nullable String delegatorPackageName, + @HandwritingDelegateFlags int handwritingDelegateFlags, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { + Objects.requireNonNull(view); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + startStylusHandwritingInternal( + view, delegatorPackageName, handwritingDelegateFlags, executor, callback); + } + + private void sendFailureCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<Boolean> callback) { + if (executor == null || callback == null) { + return; + } + executor.execute(() -> callback.accept(false)); + } + private boolean startStylusHandwritingInternal( @NonNull View view, @Nullable String delegatorPackageName, @HandwritingDelegateFlags int handwritingDelegateFlags) { + return startStylusHandwritingInternal( + view, delegatorPackageName, handwritingDelegateFlags, + null /* executor */, null /* callback */); + } + + private boolean startStylusHandwritingInternal( + @NonNull View view, @Nullable String delegatorPackageName, + @HandwritingDelegateFlags int handwritingDelegateFlags, Executor executor, + Consumer<Boolean> callback) { Objects.requireNonNull(view); + boolean useCallback = callback != null; // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { fallbackImm.startStylusHandwritingInternal( - view, delegatorPackageName, handwritingDelegateFlags); + view, delegatorPackageName, handwritingDelegateFlags, executor, callback); } boolean useDelegation = !TextUtils.isEmpty(delegatorPackageName); @@ -2537,21 +2568,40 @@ public final class InputMethodManager { if (!hasServedByInputMethodLocked(view)) { Log.w(TAG, "Ignoring startStylusHandwriting as view=" + view + " is not served."); + sendFailureCallback(executor, callback); return false; } if (view.getViewRootImpl() != mCurRootView) { Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus."); + sendFailureCallback(executor, callback); return false; } if (useDelegation) { - return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation( - mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(), - delegatorPackageName, handwritingDelegateFlags); + if (useCallback) { + IBooleanListener listener = new IBooleanListener.Stub() { + @Override + public void onResult(boolean value) { + executor.execute(() -> { + callback.accept(value); + }); + } + }; + if (!IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegationAsync( + mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(), + delegatorPackageName, handwritingDelegateFlags, listener)) { + sendFailureCallback(executor, callback); + } + return true; + } else { + return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation( + mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(), + delegatorPackageName, handwritingDelegateFlags); + } } else { IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient); + return false; } - return false; } } @@ -2788,6 +2838,7 @@ public final class InputMethodManager { * #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted * @see #prepareStylusHandwritingDelegation(View, String) * @see #acceptStylusHandwritingDelegation(View) + * TODO (b/293640003): deprecate this method once flag is enabled. */ // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add: // <p>Otherwise, if the delegator view previously started delegation using {@link @@ -2805,6 +2856,36 @@ public final class InputMethodManager { /** * Accepts and starts a stylus handwriting session on the delegate view, if handwriting + * initiation delegation was previously requested using + * {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view + * belongs to a specified delegate package. + * + * @param delegateView delegate view capable of receiving input via {@link InputConnection} + * on which {@link #startStylusHandwriting(View)} will be called. + * @param delegatorPackageName package name of the delegator that handled initial stylus stroke. + * @param executor The executor to run the callback on. + * @param callback Consumer callback that provides {@code true} if view belongs to allowed + * delegate package declared in + * {@link #prepareStylusHandwritingDelegation(View, String)} and handwriting + * session can start. + * @see #prepareStylusHandwritingDelegation(View, String) + * @see #acceptStylusHandwritingDelegation(View) + */ + @FlaggedApi(Flags.FLAG_USE_ZERO_JANK_PROXY) + public void acceptStylusHandwritingDelegation( + @NonNull View delegateView, @NonNull String delegatorPackageName, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { + Objects.requireNonNull(delegatorPackageName); + int flags = 0; + if (Flags.homeScreenHandwritingDelegator()) { + flags = delegateView.getHandwritingDelegateFlags(); + } + startStylusHandwritingInternalAsync( + delegateView, delegatorPackageName, flags, executor, callback); + } + + /** + * Accepts and starts a stylus handwriting session on the delegate view, if handwriting * initiation delegation was previously requested using {@link * #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view belongs to * a specified delegate package. diff --git a/core/java/com/android/internal/inputmethod/IBooleanListener.aidl b/core/java/com/android/internal/inputmethod/IBooleanListener.aidl new file mode 100644 index 000000000000..8830b1c2f21a --- /dev/null +++ b/core/java/com/android/internal/inputmethod/IBooleanListener.aidl @@ -0,0 +1,25 @@ +/* + * 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.internal.inputmethod; + +/** + * Interface for providing a Boolean result. + */ +oneway interface IBooleanListener +{ + void onResult(boolean value); +}
\ No newline at end of file diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index b90f8bf5ea00..1f4503a69428 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.InputMethodSubtype; import android.view.inputmethod.EditorInfo; import android.window.ImeOnBackInvokedDispatcher; +import com.android.internal.inputmethod.IBooleanListener; import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; @@ -173,11 +174,16 @@ 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); + /** Accepts and starts a stylus handwriting session for the delegate view and provides result + * async **/ + oneway void acceptStylusHandwritingDelegationAsync(in IInputMethodClient client, in int userId, + in String delegatePackageName, in String delegatorPackageName, int flags, + in IBooleanListener callback); + /** Returns {@code true} if currently selected IME supports Stylus handwriting. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)") diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 5574d1868925..74c1d96ab321 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -148,6 +148,7 @@ 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.IBooleanListener; import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; @@ -3544,6 +3545,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override + public void acceptStylusHandwritingDelegationAsync( + @NonNull IInputMethodClient client, + @UserIdInt int userId, + @NonNull String delegatePackageName, + @NonNull String delegatorPackageName, + @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) + throws RemoteException { + boolean result = acceptStylusHandwritingDelegation( + client, userId, delegatePackageName, delegatorPackageName, flags); + callback.onResult(result); + } + + @Override public boolean acceptStylusHandwritingDelegation( @NonNull IInputMethodClient client, @UserIdInt int userId, diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java index 692fd7dcceae..62d44557ae4c 100644 --- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java +++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java @@ -58,6 +58,7 @@ import android.view.inputmethod.InputMethodSubtype; import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.DirectBootAwareness; +import com.android.internal.inputmethod.IBooleanListener; import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; @@ -182,6 +183,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub { return true; } + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) @Override public void startInputOrWindowGainedFocusAsync( @StartInputReason int startInputReason, @@ -346,6 +348,18 @@ public class ZeroJankProxy extends IInputMethodManager.Stub { } @Override + public void acceptStylusHandwritingDelegationAsync( + @NonNull IInputMethodClient client, + @UserIdInt int userId, + @NonNull String delegatePackageName, + @NonNull String delegatorPackageName, + @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) + throws RemoteException { + offload(() -> mInner.acceptStylusHandwritingDelegationAsync( + client, userId, delegatePackageName, delegatorPackageName, flags, callback)); + } + + @Override public void prepareStylusHandwritingDelegation( @NonNull IInputMethodClient client, @UserIdInt int userId, |