Loading core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -56278,6 +56278,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[]); core/java/android/view/HandwritingInitiator.java +40 −10 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.view; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; Loading @@ -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. Loading Loading @@ -415,12 +417,43 @@ 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)) { 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; Loading @@ -431,9 +464,6 @@ public class HandwritingInitiator { // A handwriting delegate view is accepted and handwriting starts; hide the // hover icon. mShowHoverIconForConnectedView = false; return true; } return false; } /** Loading core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +23 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading core/java/android/view/inputmethod/InputMethodManager.java +86 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -2537,23 +2568,42 @@ 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) { 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; } } } /** * Starts a connectionless stylus handwriting session. A connectionless session differs from a Loading Loading @@ -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 Loading @@ -2803,6 +2854,36 @@ public final class InputMethodManager { delegateView, delegatorPackageName, delegateView.getHandwritingDelegateFlags()); } /** * 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 Loading core/java/com/android/internal/inputmethod/IBooleanListener.aidl 0 → 100644 +25 −0 Original line number Diff line number Diff line /* * 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 Loading
core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -56278,6 +56278,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[]);
core/java/android/view/HandwritingInitiator.java +40 −10 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.view; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; Loading @@ -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. Loading Loading @@ -415,12 +417,43 @@ 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)) { 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; Loading @@ -431,9 +464,6 @@ public class HandwritingInitiator { // A handwriting delegate view is accepted and handwriting starts; hide the // hover icon. mShowHoverIconForConnectedView = false; return true; } return false; } /** Loading
core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +23 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading
core/java/android/view/inputmethod/InputMethodManager.java +86 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -2537,23 +2568,42 @@ 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) { 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; } } } /** * Starts a connectionless stylus handwriting session. A connectionless session differs from a Loading Loading @@ -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 Loading @@ -2803,6 +2854,36 @@ public final class InputMethodManager { delegateView, delegatorPackageName, delegateView.getHandwritingDelegateFlags()); } /** * 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 Loading
core/java/com/android/internal/inputmethod/IBooleanListener.aidl 0 → 100644 +25 −0 Original line number Diff line number Diff line /* * 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