diff options
author | 2023-11-10 22:30:04 +0000 | |
---|---|---|
committer | 2024-01-11 16:34:20 +0000 | |
commit | f29c06a9cb189057ddd5d3aa5fdb7428e48126d8 (patch) | |
tree | 933292535a46c87f3c638f94dd6c55280429442b | |
parent | 469ee63bff1aa700f2e6deae488057765d779983 (diff) |
Add SurfaceControlInputReceiver
Allow SurfaceControls to be registered to receive input without
needing a window.
Test: SurfaceControlInputReceiverTests
Bug: 278757236
Change-Id: Icb9c7cd518bddfa3b5569d59b79610fd7505a2e4
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); + } + } +} |