Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ff35ca73 authored by Adrian Roos's avatar Adrian Roos Committed by Taran Singh
Browse files

Use CancellationSignalBeamer in InsertMode, preview

Implement CancellationSignalBeamer in InsertModeGesture and preview
HandwritingGesture

Bug: 254727073
Test: atest InputConnectionEndToEndTest

Change-Id: I0ddcac59cfb6d200f6eab71180106f3545a7d608
parent add110fe
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -3518,6 +3518,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();
+51 −10
Original line number Diff line number Diff line
@@ -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;
@@ -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) {
@@ -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) {
@@ -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(
@@ -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)}.
+6 −5
Original line number Diff line number Diff line
@@ -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;
@@ -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
+61 −5
Original line number Diff line number Diff line
@@ -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;

/**
@@ -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;
@@ -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
        }
+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