summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chavi Weingarten <chaviw@google.com> 2023-11-10 22:30:04 +0000
committer Chavi Weingarten <chaviw@google.com> 2024-01-11 16:34:20 +0000
commitf29c06a9cb189057ddd5d3aa5fdb7428e48126d8 (patch)
tree933292535a46c87f3c638f94dd6c55280429442b
parent469ee63bff1aa700f2e6deae488057765d779983 (diff)
Add SurfaceControlInputReceiver
Allow SurfaceControls to be registered to receive input without needing a window. Test: SurfaceControlInputReceiverTests Bug: 278757236 Change-Id: Icb9c7cd518bddfa3b5569d59b79610fd7505a2e4
-rw-r--r--core/api/current.txt7
-rw-r--r--core/java/android/view/Choreographer.java7
-rw-r--r--core/java/android/view/SurfaceControlInputReceiver.java43
-rw-r--r--core/java/android/view/WindowManager.java91
-rw-r--r--core/java/android/view/WindowManagerGlobal.java76
-rw-r--r--core/java/android/view/WindowManagerImpl.java23
-rw-r--r--core/java/android/window/flags/window_surfaces.aconfig8
-rw-r--r--tests/SurfaceControlViewHostTest/AndroidManifest.xml10
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java49
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl3
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java217
11 files changed, 534 insertions, 0 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index af821b6e7333..3f45c0093b05 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -51847,6 +51847,10 @@ package android.view {
ctor public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
}
+ @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public interface SurfaceControlInputReceiver {
+ method public boolean onInputEvent(@NonNull android.view.InputEvent);
+ }
+
public class SurfaceControlViewHost {
ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder);
method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage();
@@ -53970,10 +53974,13 @@ package android.view {
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
method public default boolean isCrossWindowBlurEnabled();
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
method public void removeViewImmediate(android.view.View);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.os.IBinder);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index a74cbe46b404..f0e673b3e3ac 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -378,6 +378,13 @@ public final class Choreographer {
}
/**
+ * @hide
+ */
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ /**
* The amount of time, in milliseconds, between each frame of the animation.
* <p>
* This is a requested time that the animation will attempt to honor, but the actual delay
diff --git a/core/java/android/view/SurfaceControlInputReceiver.java b/core/java/android/view/SurfaceControlInputReceiver.java
new file mode 100644
index 000000000000..81e444859b76
--- /dev/null
+++ b/core/java/android/view/SurfaceControlInputReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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 android.view;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Provides a mechanism for a SurfaceControl to receive input events.
+ */
+@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+public interface SurfaceControlInputReceiver {
+ /**
+ * When input events are batched, this is called at most once per frame. When non batched, this
+ * is called immediately for the input event.
+ *
+ * @param event The input event that was received. This input event object will become invalid
+ * and recycled after this method is invoked. If there is need to persist this
+ * object beyond the scope of this method, the overriding code should make a copy
+ * of this object. For example, using
+ * {@link MotionEvent#obtain(MotionEvent other)} or
+ * {@link KeyEvent#KeyEvent(KeyEvent)} }
+ * @return true if the event was handled, false otherwise.
+ */
+ boolean onInputEvent(@NonNull InputEvent event);
+
+}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d8fa41589f29..96aba4c352e6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -109,6 +109,7 @@ import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
@@ -6015,4 +6016,94 @@ public interface WindowManager extends ViewManager {
default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Registers a {@link SurfaceControlInputReceiver} for a {@link SurfaceControl} that will
+ * receive batched input event. For those events that are batched, the invocation will happen
+ * once per {@link Choreographer} frame, and other input events will be delivered immediately.
+ * This is different from
+ * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+ * SurfaceControlInputReceiver)} in that the input events are received batched. The caller must
+ * invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources when
+ * no longer needing to use the {@link SurfaceControlInputReceiver}
+ *
+ * @param displayId The display that the SurfaceControl will be placed on. Input will
+ * only work
+ * if SurfaceControl is on that display and that display was touched.
+ * @param surfaceControl The SurfaceControl to register the InputChannel for
+ * @param hostToken The host token to link the InputChannel for. This is primarily for ANRs
+ * to ensure the host receives the ANR if any issues with touch on the
+ * InputChannel
+ * @param choreographer The Choreographer used for batching. This should match the rendering
+ * Choreographer.
+ * @param receiver The SurfaceControlInputReceiver that will receive the input events
+ * @return an {@link IBinder} token that is used to unregister the input receiver via
+ * {@link #unregisterSurfaceControlInputReceiver(IBinder)}.
+ * @see #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+ * SurfaceControlInputReceiver)
+ */
+ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+ @NonNull
+ default IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
+ throw new UnsupportedOperationException(
+ "registerBatchedSurfaceControlInputReceiver is not implemented");
+ }
+
+ /**
+ * Registers a {@link SurfaceControlInputReceiver} for a {@link SurfaceControl} that will
+ * receive every input event. This is different than calling @link
+ * #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
+ * SurfaceControlInputReceiver)} in that the input events are received unbatched. The caller
+ * must invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources
+ * when no longer needing to use the {@link SurfaceControlInputReceiver}
+ *
+ * @param displayId The display that the SurfaceControl will be placed on. Input will only
+ * work if SurfaceControl is on that display and that display was
+ * touched.
+ * @param hostToken The host token to link the InputChannel for. This is primarily for ANRs
+ * to ensure the host receives the ANR if any issues with touch on the
+ * InputChannel
+ * @param surfaceControl The SurfaceControl to register the InputChannel for
+ * @param looper The looper to use when invoking callbacks.
+ * @param receiver The SurfaceControlInputReceiver that will receive the input events
+ * @return an {@link IBinder} token that is used to unregister the input receiver via
+ * {@link #unregisterSurfaceControlInputReceiver(IBinder)}.
+ * @see #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
+ * SurfaceControlInputReceiver)
+ **/
+ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+ @NonNull
+ default IBinder registerUnbatchedSurfaceControlInputReceiver(int displayId,
+ @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
+ throw new UnsupportedOperationException(
+ "registerUnbatchedSurfaceControlInputReceiver is not implemented");
+ }
+
+ /**
+ * Unregisters and cleans up the registered {@link SurfaceControlInputReceiver} for the
+ * specified token.
+ * <p>
+ * Must be called on the same {@link Looper} thread to which was passed to the
+ * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl,
+ * Choreographer,
+ * SurfaceControlInputReceiver)} or
+ * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+ * SurfaceControlInputReceiver)}
+ *
+ * @param token The token that was returned via
+ * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder,
+ * SurfaceControl,
+ * Choreographer, SurfaceControlInputReceiver)} or
+ * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder,
+ * SurfaceControl,
+ * Looper, SurfaceControlInputReceiver)}
+ */
+ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+ default void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) {
+ throw new UnsupportedOperationException(
+ "unregisterSurfaceControlInputReceiver is not implemented");
+ }
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index f1e406196abf..8d40f9a4f7b1 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -24,8 +26,10 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.HardwareRenderer;
+import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -46,6 +50,7 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -151,6 +156,9 @@ public final class WindowManagerGlobal {
private final TrustedPresentationListener mTrustedPresentationListener =
new TrustedPresentationListener();
+ private final ConcurrentHashMap<IBinder, InputEventReceiver> mSurfaceControlInputReceivers =
+ new ConcurrentHashMap<>();
+
private WindowManagerGlobal() {
}
@@ -808,6 +816,74 @@ public final class WindowManagerGlobal {
mTrustedPresentationListener.removeListener(listener);
}
+ IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
+ IBinder clientToken = new Binder();
+ InputChannel inputChannel = new InputChannel();
+ try {
+ WindowManagerGlobal.getWindowSession().grantInputChannel(displayId, surfaceControl,
+ clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, null,
+ surfaceControl.getName(), inputChannel);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to create input channel", e);
+ e.rethrowAsRuntimeException();
+ }
+
+ mSurfaceControlInputReceivers.put(clientToken,
+ new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(),
+ choreographer) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = receiver.onInputEvent(event);
+ finishInputEvent(event, handled);
+ }
+ });
+ return clientToken;
+ }
+
+ IBinder registerUnbatchedSurfaceControlInputReceiver(
+ int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
+ IBinder clientToken = new Binder();
+ InputChannel inputChannel = new InputChannel();
+ try {
+ WindowManagerGlobal.getWindowSession().grantInputChannel(displayId, surfaceControl,
+ clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, null,
+ surfaceControl.getName(), inputChannel);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to create input channel", e);
+ e.rethrowAsRuntimeException();
+ }
+
+ mSurfaceControlInputReceivers.put(clientToken,
+ new InputEventReceiver(inputChannel, looper) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = receiver.onInputEvent(event);
+ finishInputEvent(event, handled);
+ }
+ });
+
+ return clientToken;
+ }
+
+ void unregisterSurfaceControlInputReceiver(IBinder token) {
+ InputEventReceiver inputEventReceiver = mSurfaceControlInputReceivers.get(token);
+ if (inputEventReceiver == null) {
+ Log.w(TAG, "No registered input event receiver with token: " + token);
+ return;
+ }
+ try {
+ WindowManagerGlobal.getWindowSession().remove(token);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to remove input channel", e);
+ e.rethrowAsRuntimeException();
+ }
+
+ inputEventReceiver.dispose();
+ }
+
private final class TrustedPresentationListener extends
ITrustedPresentationListener.Stub {
private static int sId = 0;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b4b1fde89a46..aaf5fcc6f095 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -32,6 +32,7 @@ import android.graphics.Bitmap;
import android.graphics.Region;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.StrictMode;
import android.util.Log;
@@ -520,6 +521,28 @@ public final class WindowManagerImpl implements WindowManager {
@Override
public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
mGlobal.unregisterTrustedPresentationListener(listener);
+ }
+
+ @NonNull
+ @Override
+ public IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
+ return mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+ surfaceControl, choreographer, receiver);
+ }
+ @NonNull
+ @Override
+ public IBinder registerUnbatchedSurfaceControlInputReceiver(
+ int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+ @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
+ return mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken,
+ surfaceControl, looper, receiver);
+ }
+
+ @Override
+ public void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) {
+ mGlobal.unregisterSurfaceControlInputReceiver(token);
}
}
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 3794c5d647a4..3c3c8469b3a4 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -72,3 +72,11 @@ flag {
is_fixed_read_only: true
bug: "295038072"
}
+
+flag {
+ namespace: "window_surfaces"
+ name: "surface_control_input_receiver"
+ description: "Enable public API to register an InputReceiver for a SurfaceControl"
+ is_fixed_read_only: true
+ bug: "278757236"
+}
diff --git a/tests/SurfaceControlViewHostTest/AndroidManifest.xml b/tests/SurfaceControlViewHostTest/AndroidManifest.xml
index e50cbc52a5b8..71f01ac5ded1 100644
--- a/tests/SurfaceControlViewHostTest/AndroidManifest.xml
+++ b/tests/SurfaceControlViewHostTest/AndroidManifest.xml
@@ -32,6 +32,16 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+
+ <activity android:name="SurfaceInputTestActivity"
+ android:label="Surface Input Test"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
<service android:name=".EmbeddedWindowService"
android:process="com.android.test.viewembed.embedded_process"/>
</application>
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
index abc15b49ad98..5aaf30a5b3a7 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
@@ -23,15 +23,21 @@ import android.annotation.Nullable;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.Log;
+import android.view.Choreographer;
import android.view.Display;
import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -43,6 +49,9 @@ public class EmbeddedWindowService extends Service {
private Handler mHandler;
+ private IBinder mInputToken;
+ private SurfaceControl mSurfaceControl;
+
@Override
public void onCreate() {
super.onCreate();
@@ -101,9 +110,49 @@ public class EmbeddedWindowService extends Service {
}
});
}
+
@Override
public void relayout(WindowManager.LayoutParams lp) {
mHandler.post(() -> mVr.relayout(lp));
}
+
+ @Override
+ public void attachEmbeddedSurfaceControl(SurfaceControl parentSc, int displayId,
+ IBinder hostToken) {
+ mHandler.post(() -> {
+ Paint paint = new Paint();
+ paint.setTextSize(40);
+ paint.setColor(Color.WHITE);
+
+ mSurfaceControl = new SurfaceControl.Builder().setName("Child SurfaceControl")
+ .setParent(parentSc).setBufferSize(500, 500).build();
+ new SurfaceControl.Transaction().show(mSurfaceControl).apply();
+
+ Surface surface = new Surface(mSurfaceControl);
+ Canvas c = surface.lockCanvas(null);
+ c.drawColor(Color.BLUE);
+ c.drawText("Remote", 250, 250, paint);
+ surface.unlockCanvasAndPost(c);
+ WindowManager wm = getSystemService(WindowManager.class);
+ mInputToken = wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+ mSurfaceControl,
+ Choreographer.getInstance(), event -> {
+ Log.d(TAG, "onInputEvent-remote " + event);
+ return false;
+ });
+
+ });
+ }
+
+ @Override
+ public void tearDownEmbeddedSurfaceControl() {
+ if (mSurfaceControl != null) {
+ new SurfaceControl.Transaction().remove(mSurfaceControl);
+ }
+ if (mInputToken != null) {
+ WindowManager wm = getSystemService(WindowManager.class);
+ wm.unregisterSurfaceControlInputReceiver(mInputToken);
+ }
+ }
}
}
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
index 9e9faf03ba1c..6b65b40ef8c6 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
@@ -19,8 +19,11 @@ package com.android.test.viewembed;
import android.os.IBinder;
import com.android.test.viewembed.IAttachEmbeddedWindowCallback;
import android.view.WindowManager.LayoutParams;
+import android.view.SurfaceControl;
interface IAttachEmbeddedWindow {
void attachEmbedded(IBinder hostToken, int width, int height, in IAttachEmbeddedWindowCallback callback);
void relayout(in LayoutParams lp);
+ oneway void attachEmbeddedSurfaceControl(in SurfaceControl parentSurfaceControl, int displayId, IBinder hostToken);
+ oneway void tearDownEmbeddedSurfaceControl();
} \ No newline at end of file
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
new file mode 100644
index 000000000000..e5f8f47aeecd
--- /dev/null
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 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.test.viewembed;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.AttachedSurfaceControl;
+import android.view.Choreographer;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+/**
+ * Used to manually test that {@link android.view.SurfaceControlInputReceiver} API works.
+ */
+public class SurfaceInputTestActivity extends Activity {
+
+ private static final String TAG = "SurfaceInputTestActivity";
+ private SurfaceView mLocalSurfaceView;
+ private SurfaceView mRemoteSurfaceView;
+ private IBinder mInputToken;
+ private IAttachEmbeddedWindow mIAttachEmbeddedWindow;
+ private SurfaceControl mParentSurfaceControl;
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ // Called when the connection with the service is established
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "Service Connected");
+ mIAttachEmbeddedWindow = IAttachEmbeddedWindow.Stub.asInterface(service);
+ loadEmbedded();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "Service Disconnected");
+ mIAttachEmbeddedWindow = null;
+ }
+ };
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ ViewTreeObserver viewTreeObserver = getWindow().getDecorView().getViewTreeObserver();
+ viewTreeObserver.addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ addLocalChildSurfaceControl(getWindow().getRootSurfaceControl());
+ viewTreeObserver.removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout content = new LinearLayout(this);
+ mLocalSurfaceView = new SurfaceView(this);
+ content.addView(mLocalSurfaceView, new LinearLayout.LayoutParams(
+ 500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+
+ mRemoteSurfaceView = new SurfaceView(this);
+ content.addView(mRemoteSurfaceView, new LinearLayout.LayoutParams(
+ 500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+
+ setContentView(content);
+
+ mLocalSurfaceView.setZOrderOnTop(true);
+ mLocalSurfaceView.getHolder().addCallback(mLocalSurfaceViewCallback);
+
+ mRemoteSurfaceView.setZOrderOnTop(true);
+ mRemoteSurfaceView.getHolder().addCallback(mRemoteSurfaceViewHolder);
+
+ Intent intent = new Intent(this, EmbeddedWindowService.class);
+ intent.setAction(IAttachEmbeddedWindow.class.getName());
+ Log.d(TAG, "bindService");
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken);
+ }
+
+ private void addLocalChildSurfaceControl(AttachedSurfaceControl attachedSurfaceControl) {
+ SurfaceControl surfaceControl = new SurfaceControl.Builder().setName("LocalSC")
+ .setBufferSize(100, 100).build();
+ attachedSurfaceControl.buildReparentTransaction(surfaceControl)
+ .setVisibility(surfaceControl, true)
+ .setCrop(surfaceControl, new Rect(0, 0, 100, 100))
+ .setPosition(surfaceControl, 250, 1000)
+ .setLayer(surfaceControl, 1).apply();
+
+ Paint paint = new Paint();
+ paint.setColor(Color.WHITE);
+ paint.setTextSize(20);
+
+ Surface surface = new Surface(surfaceControl);
+ Canvas c = surface.lockCanvas(null);
+ c.drawColor(Color.GREEN);
+ c.drawText("Local SC", 0, 0, paint);
+ surface.unlockCanvasAndPost(c);
+ WindowManager wm = getSystemService(WindowManager.class);
+ mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+ attachedSurfaceControl.getHostToken(), surfaceControl,
+ Choreographer.getInstance(), event -> {
+ Log.d(TAG, "onInputEvent-sc " + event);
+ return false;
+ });
+ }
+
+ private final SurfaceHolder.Callback mLocalSurfaceViewCallback = new SurfaceHolder.Callback() {
+ private IBinder mInputToken;
+
+ @Override
+ public void surfaceCreated(@NonNull SurfaceHolder holder) {
+ Paint paint = new Paint();
+ paint.setColor(Color.WHITE);
+ paint.setTextSize(40);
+
+ Canvas c = holder.lockCanvas();
+ c.drawColor(Color.RED);
+ c.drawText("Local", 250, 250, paint);
+ holder.unlockCanvasAndPost(c);
+
+ WindowManager wm = getSystemService(WindowManager.class);
+ mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+ mLocalSurfaceView.getHostToken(), mLocalSurfaceView.getSurfaceControl(),
+ Choreographer.getInstance(), event -> {
+ Log.d(TAG, "onInputEvent-local " + event);
+ return false;
+ });
+ }
+
+ @Override
+ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+ int height) {
+
+ }
+
+ @Override
+ public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+ if (mInputToken != null) {
+ getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken);
+ }
+ }
+ };
+
+ private final SurfaceHolder.Callback mRemoteSurfaceViewHolder = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(@NonNull SurfaceHolder holder) {
+ mParentSurfaceControl = mRemoteSurfaceView.getSurfaceControl();
+ loadEmbedded();
+ }
+
+ @Override
+ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+ int height) {
+ }
+
+ @Override
+ public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+ if (mIAttachEmbeddedWindow != null) {
+ try {
+ mIAttachEmbeddedWindow.tearDownEmbeddedSurfaceControl();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to tear down embedded SurfaceControl", e);
+ }
+ }
+ }
+ };
+
+ private void loadEmbedded() {
+ if (mParentSurfaceControl == null || mIAttachEmbeddedWindow == null) {
+ return;
+ }
+ try {
+ mIAttachEmbeddedWindow.attachEmbeddedSurfaceControl(mParentSurfaceControl,
+ getDisplayId(), mRemoteSurfaceView.getHostToken());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to load embedded SurfaceControl", e);
+ }
+ }
+}