Loading core/api/test-current.txt +5 −0 Original line number Diff line number Diff line Loading @@ -3601,6 +3601,11 @@ package android.view.displayhash { package android.view.inputmethod { public abstract class CancellableHandwritingGesture extends android.view.inputmethod.HandwritingGesture { ctor public CancellableHandwritingGesture(); method public void setCancellationSignal(@NonNull android.os.CancellationSignal); } public abstract class HandwritingGesture { method @NonNull public static android.view.inputmethod.HandwritingGesture fromByteArray(@NonNull byte[]); method public final int getGestureType(); Loading core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java +51 −10 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ import android.annotation.Nullable; import android.graphics.RectF; import android.os.Bundle; import android.os.CancellationSignal; import android.os.CancellationSignalBeamer; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.view.KeyEvent; Loading Loading @@ -59,6 +61,7 @@ final class IRemoteInputConnectionInvoker { @NonNull private final IRemoteInputConnection mConnection; private final int mSessionId; private CancellationSignalBeamer.Sender mBeamer; private IRemoteInputConnectionInvoker(@NonNull IRemoteInputConnection inputConnection, int sessionId) { Loading Loading @@ -681,7 +684,7 @@ final class IRemoteInputConnectionInvoker { * InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}. */ @AnyThread public void performHandwritingGesture(@NonNull ParcelableHandwritingGesture gesture, public void performHandwritingGesture(@NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) { ResultReceiver resultReceiver = null; if (consumer != null) { Loading @@ -689,7 +692,11 @@ final class IRemoteInputConnectionInvoker { resultReceiver = new IntResultReceiver(executor, consumer); } try { mConnection.performHandwritingGesture(createHeader(), gesture, resultReceiver); try (var ignored = getCancellationSignalBeamer().beamScopeIfNeeded(gesture)) { mConnection.performHandwritingGesture(createHeader(), ParcelableHandwritingGesture.of(gesture), resultReceiver); } } catch (RemoteException e) { if (consumer != null && executor != null) { executor.execute(() -> consumer.accept( Loading @@ -700,25 +707,59 @@ final class IRemoteInputConnectionInvoker { /** * Invokes one of {@link IRemoteInputConnection#previewHandwritingGesture( * InputConnectionCommandHeader, ParcelableHandwritingGesture, CancellationSignal)} * InputConnectionCommandHeader, HandwritingGesture, IBinder)} */ @AnyThread public boolean previewHandwritingGesture( @NonNull ParcelableHandwritingGesture gesture, @NonNull HandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal) { if (cancellationSignal != null && cancellationSignal.isCanceled()) { return false; // cancelled. } // TODO(b/254727073): Implement CancellationSignal try { mConnection.previewHandwritingGesture(createHeader(), gesture, null); try (var csToken = beam(cancellationSignal)) { mConnection.previewHandwritingGesture(createHeader(), ParcelableHandwritingGesture.of(gesture), csToken); } return true; } catch (RemoteException e) { return false; } } @Nullable CancellationSignalBeamer.Sender.CloseableToken beam(CancellationSignal cs) { if (cs == null) { return null; } return getCancellationSignalBeamer().beam(cs); } private CancellationSignalBeamer.Sender getCancellationSignalBeamer() { if (mBeamer != null) { return mBeamer; } mBeamer = new CancellationSignalBeamer.Sender() { @Override public void onCancel(IBinder token) { try { mConnection.cancelCancellationSignal(token); } catch (RemoteException e) { // Remote process likely died, ignore. } } @Override public void onForget(IBinder token) { try { mConnection.forgetCancellationSignal(token); } catch (RemoteException e) { // Remote process likely died, ignore. } } }; return mBeamer; } /** * Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int, * int, AndroidFuture)}. Loading core/java/android/inputmethodservice/RemoteInputConnection.java +6 −5 Original line number Diff line number Diff line Loading @@ -34,7 +34,6 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.ParcelableHandwritingGesture; import android.view.inputmethod.PreviewableHandwritingGesture; import android.view.inputmethod.SurroundingText; import android.view.inputmethod.TextAttribute; Loading Loading @@ -424,16 +423,18 @@ final class RemoteInputConnection implements InputConnection { public void performHandwritingGesture( @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) { mInvoker.performHandwritingGesture(ParcelableHandwritingGesture.of(gesture), executor, consumer); mInvoker.performHandwritingGesture(gesture, executor, consumer); } @AnyThread public boolean previewHandwritingGesture( @NonNull PreviewableHandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal) { return mInvoker.previewHandwritingGesture(ParcelableHandwritingGesture.of(gesture), cancellationSignal); if (cancellationSignal != null && cancellationSignal.isCanceled()) { return false; // cancelled. } return mInvoker.previewHandwritingGesture(gesture, cancellationSignal); } @AnyThread Loading core/java/android/os/CancellationSignalBeamer.java +61 −5 Original line number Diff line number Diff line Loading @@ -19,9 +19,13 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.system.SystemCleaner; import android.util.Pair; import android.view.inputmethod.CancellableHandwritingGesture; import android.view.inputmethod.HandwritingGesture; import java.lang.ref.Cleaner; import java.lang.ref.Reference; import java.util.ArrayList; import java.util.HashMap; /** Loading Loading @@ -143,6 +147,58 @@ public class CancellationSignalBeamer { */ public abstract void onForget(IBinder token); private static final ThreadLocal<Pair<Sender, ArrayList<CloseableToken>>> sScope = new ThreadLocal<>(); /** * Beams a {@link CancellationSignal} through an existing Binder interface. * @param gesture {@link HandwritingGesture} that supports * {@link CancellableHandwritingGesture cancellation} requesting cancellation token. * @return {@link IBinder} token. MUST be {@link MustClose#close}d <em>after</em> * the binder call transporting it to the remote process, best with * try-with-resources. {@code null} if {@code cs} was {@code null} or if * {@link HandwritingGesture} isn't {@link CancellableHandwritingGesture cancellable}. */ public MustClose beamScopeIfNeeded(HandwritingGesture gesture) { if (!(gesture instanceof CancellableHandwritingGesture)) { return null; } sScope.set(Pair.create(this, new ArrayList<>())); return () -> { var tokens = sScope.get().second; sScope.remove(); for (int i = tokens.size() - 1; i >= 0; i--) { if (tokens.get(i) != null) { tokens.get(i).close(); } } }; } /** * An {@link AutoCloseable} interface with {@link AutoCloseable#close()} callback. */ public interface MustClose extends AutoCloseable { @Override void close(); } /** * Beams a {@link CancellationSignal} token from existing scope created by previous call to * {@link #beamScopeIfNeeded()} * @param cs {@link CancellationSignal} for which token should be returned. * @return {@link IBinder} token. */ public static IBinder beamFromScope(CancellationSignal cs) { var state = sScope.get(); if (state != null) { var token = state.first.beam(cs); state.second.add(token); return token; } return null; } private static class Token extends Binder implements CloseableToken, Runnable { private final Sender mSender; Loading Loading @@ -200,7 +256,7 @@ public class CancellationSignalBeamer { * * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources. */ public interface CloseableToken extends IBinder, AutoCloseable { public interface CloseableToken extends IBinder, MustClose { @Override void close(); // No throws } Loading core/java/android/view/inputmethod/CancellableHandwritingGesture.java 0 → 100644 +53 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 android.view.inputmethod; import android.annotation.NonNull; import android.annotation.TestApi; import android.os.CancellationSignal; import android.os.CancellationSignalBeamer; import android.os.IBinder; /** * A {@link HandwritingGesture} that can be {@link CancellationSignal#cancel() cancelled}. * @hide */ @TestApi public abstract class CancellableHandwritingGesture extends HandwritingGesture { CancellationSignal mCancellationSignal; IBinder mCancellationSignalToken; /** * Set {@link CancellationSignal} for testing only. * @hide */ @TestApi public void setCancellationSignal(@NonNull CancellationSignal cancellationSignal) { mCancellationSignal = cancellationSignal; } CancellationSignal getCancellationSignal() { return mCancellationSignal; } void unbeamCancellationSignal(CancellationSignalBeamer.Receiver receiver) { mCancellationSignal = receiver.unbeam(mCancellationSignalToken); mCancellationSignalToken = null; } } Loading
core/api/test-current.txt +5 −0 Original line number Diff line number Diff line Loading @@ -3601,6 +3601,11 @@ package android.view.displayhash { package android.view.inputmethod { public abstract class CancellableHandwritingGesture extends android.view.inputmethod.HandwritingGesture { ctor public CancellableHandwritingGesture(); method public void setCancellationSignal(@NonNull android.os.CancellationSignal); } public abstract class HandwritingGesture { method @NonNull public static android.view.inputmethod.HandwritingGesture fromByteArray(@NonNull byte[]); method public final int getGestureType(); Loading
core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java +51 −10 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ import android.annotation.Nullable; import android.graphics.RectF; import android.os.Bundle; import android.os.CancellationSignal; import android.os.CancellationSignalBeamer; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.view.KeyEvent; Loading Loading @@ -59,6 +61,7 @@ final class IRemoteInputConnectionInvoker { @NonNull private final IRemoteInputConnection mConnection; private final int mSessionId; private CancellationSignalBeamer.Sender mBeamer; private IRemoteInputConnectionInvoker(@NonNull IRemoteInputConnection inputConnection, int sessionId) { Loading Loading @@ -681,7 +684,7 @@ final class IRemoteInputConnectionInvoker { * InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}. */ @AnyThread public void performHandwritingGesture(@NonNull ParcelableHandwritingGesture gesture, public void performHandwritingGesture(@NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) { ResultReceiver resultReceiver = null; if (consumer != null) { Loading @@ -689,7 +692,11 @@ final class IRemoteInputConnectionInvoker { resultReceiver = new IntResultReceiver(executor, consumer); } try { mConnection.performHandwritingGesture(createHeader(), gesture, resultReceiver); try (var ignored = getCancellationSignalBeamer().beamScopeIfNeeded(gesture)) { mConnection.performHandwritingGesture(createHeader(), ParcelableHandwritingGesture.of(gesture), resultReceiver); } } catch (RemoteException e) { if (consumer != null && executor != null) { executor.execute(() -> consumer.accept( Loading @@ -700,25 +707,59 @@ final class IRemoteInputConnectionInvoker { /** * Invokes one of {@link IRemoteInputConnection#previewHandwritingGesture( * InputConnectionCommandHeader, ParcelableHandwritingGesture, CancellationSignal)} * InputConnectionCommandHeader, HandwritingGesture, IBinder)} */ @AnyThread public boolean previewHandwritingGesture( @NonNull ParcelableHandwritingGesture gesture, @NonNull HandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal) { if (cancellationSignal != null && cancellationSignal.isCanceled()) { return false; // cancelled. } // TODO(b/254727073): Implement CancellationSignal try { mConnection.previewHandwritingGesture(createHeader(), gesture, null); try (var csToken = beam(cancellationSignal)) { mConnection.previewHandwritingGesture(createHeader(), ParcelableHandwritingGesture.of(gesture), csToken); } return true; } catch (RemoteException e) { return false; } } @Nullable CancellationSignalBeamer.Sender.CloseableToken beam(CancellationSignal cs) { if (cs == null) { return null; } return getCancellationSignalBeamer().beam(cs); } private CancellationSignalBeamer.Sender getCancellationSignalBeamer() { if (mBeamer != null) { return mBeamer; } mBeamer = new CancellationSignalBeamer.Sender() { @Override public void onCancel(IBinder token) { try { mConnection.cancelCancellationSignal(token); } catch (RemoteException e) { // Remote process likely died, ignore. } } @Override public void onForget(IBinder token) { try { mConnection.forgetCancellationSignal(token); } catch (RemoteException e) { // Remote process likely died, ignore. } } }; return mBeamer; } /** * Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int, * int, AndroidFuture)}. Loading
core/java/android/inputmethodservice/RemoteInputConnection.java +6 −5 Original line number Diff line number Diff line Loading @@ -34,7 +34,6 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.ParcelableHandwritingGesture; import android.view.inputmethod.PreviewableHandwritingGesture; import android.view.inputmethod.SurroundingText; import android.view.inputmethod.TextAttribute; Loading Loading @@ -424,16 +423,18 @@ final class RemoteInputConnection implements InputConnection { public void performHandwritingGesture( @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) { mInvoker.performHandwritingGesture(ParcelableHandwritingGesture.of(gesture), executor, consumer); mInvoker.performHandwritingGesture(gesture, executor, consumer); } @AnyThread public boolean previewHandwritingGesture( @NonNull PreviewableHandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal) { return mInvoker.previewHandwritingGesture(ParcelableHandwritingGesture.of(gesture), cancellationSignal); if (cancellationSignal != null && cancellationSignal.isCanceled()) { return false; // cancelled. } return mInvoker.previewHandwritingGesture(gesture, cancellationSignal); } @AnyThread Loading
core/java/android/os/CancellationSignalBeamer.java +61 −5 Original line number Diff line number Diff line Loading @@ -19,9 +19,13 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.system.SystemCleaner; import android.util.Pair; import android.view.inputmethod.CancellableHandwritingGesture; import android.view.inputmethod.HandwritingGesture; import java.lang.ref.Cleaner; import java.lang.ref.Reference; import java.util.ArrayList; import java.util.HashMap; /** Loading Loading @@ -143,6 +147,58 @@ public class CancellationSignalBeamer { */ public abstract void onForget(IBinder token); private static final ThreadLocal<Pair<Sender, ArrayList<CloseableToken>>> sScope = new ThreadLocal<>(); /** * Beams a {@link CancellationSignal} through an existing Binder interface. * @param gesture {@link HandwritingGesture} that supports * {@link CancellableHandwritingGesture cancellation} requesting cancellation token. * @return {@link IBinder} token. MUST be {@link MustClose#close}d <em>after</em> * the binder call transporting it to the remote process, best with * try-with-resources. {@code null} if {@code cs} was {@code null} or if * {@link HandwritingGesture} isn't {@link CancellableHandwritingGesture cancellable}. */ public MustClose beamScopeIfNeeded(HandwritingGesture gesture) { if (!(gesture instanceof CancellableHandwritingGesture)) { return null; } sScope.set(Pair.create(this, new ArrayList<>())); return () -> { var tokens = sScope.get().second; sScope.remove(); for (int i = tokens.size() - 1; i >= 0; i--) { if (tokens.get(i) != null) { tokens.get(i).close(); } } }; } /** * An {@link AutoCloseable} interface with {@link AutoCloseable#close()} callback. */ public interface MustClose extends AutoCloseable { @Override void close(); } /** * Beams a {@link CancellationSignal} token from existing scope created by previous call to * {@link #beamScopeIfNeeded()} * @param cs {@link CancellationSignal} for which token should be returned. * @return {@link IBinder} token. */ public static IBinder beamFromScope(CancellationSignal cs) { var state = sScope.get(); if (state != null) { var token = state.first.beam(cs); state.second.add(token); return token; } return null; } private static class Token extends Binder implements CloseableToken, Runnable { private final Sender mSender; Loading Loading @@ -200,7 +256,7 @@ public class CancellationSignalBeamer { * * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources. */ public interface CloseableToken extends IBinder, AutoCloseable { public interface CloseableToken extends IBinder, MustClose { @Override void close(); // No throws } Loading
core/java/android/view/inputmethod/CancellableHandwritingGesture.java 0 → 100644 +53 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 android.view.inputmethod; import android.annotation.NonNull; import android.annotation.TestApi; import android.os.CancellationSignal; import android.os.CancellationSignalBeamer; import android.os.IBinder; /** * A {@link HandwritingGesture} that can be {@link CancellationSignal#cancel() cancelled}. * @hide */ @TestApi public abstract class CancellableHandwritingGesture extends HandwritingGesture { CancellationSignal mCancellationSignal; IBinder mCancellationSignalToken; /** * Set {@link CancellationSignal} for testing only. * @hide */ @TestApi public void setCancellationSignal(@NonNull CancellationSignal cancellationSignal) { mCancellationSignal = cancellationSignal; } CancellationSignal getCancellationSignal() { return mCancellationSignal; } void unbeamCancellationSignal(CancellationSignalBeamer.Receiver receiver) { mCancellationSignal = receiver.unbeam(mCancellationSignalToken); mCancellationSignalToken = null; } }