diff options
author | 2025-03-12 02:20:55 -0700 | |
---|---|---|
committer | 2025-03-12 02:20:55 -0700 | |
commit | c2a15c02d4c545dfa42c4c636353b621c7eec3f2 (patch) | |
tree | c0749ce82ea4b8619c0ad7e6f799985c30d8331f | |
parent | 371837b50305ec097ee6a46ac6cf1adbf9d0fbe0 (diff) | |
parent | ef3f9c55ccf5c4be643650a2126df89172510bc4 (diff) |
Merge "Prevent surface use after release in DragResizeInputListener" into main
2 files changed, 85 insertions, 4 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 40737120f364..0b86d1dbbc58 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -138,7 +138,11 @@ class DragResizeInputListener implements AutoCloseable { mHandler = handler; mChoreographer = choreographer; mDisplayId = displayId; - mDecorationSurface = decorationSurface; + // Creates a new SurfaceControl pointing the same underlying surface with decorationSurface + // to ensure that mDecorationSurface will not be released while it's used on the background + // thread. Note that the empty name will be overridden by the next copyFrom call. + mDecorationSurface = surfaceControlBuilderSupplier.get().setName("").build(); + mDecorationSurface.copyFrom(decorationSurface, "DragResizeInputListener"); mDragPositioningCallback = callback; mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier; mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; @@ -427,6 +431,9 @@ class DragResizeInputListener implements AutoCloseable { } catch (RemoteException e) { e.rethrowFromSystemServer(); } + // Removing this surface on the background thread to ensure that mInitInputChannels has + // already been finished. + mSurfaceControlTransactionSupplier.get().remove(mDecorationSurface).apply(); }); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt index e23d0ad55b04..360099777bde 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt @@ -40,11 +40,13 @@ import com.android.wm.shell.windowdecor.DragResizeInputListener.TaskResizeInputE import com.google.common.truth.Truth.assertThat import java.util.function.Consumer import java.util.function.Supplier +import org.junit.After import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.argThat import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -65,6 +67,13 @@ class DragResizeInputListenerTest : ShellTestCase() { private val mockInputEventReceiver = mock<TaskResizeInputEventReceiver>() private val inputChannel = mock<InputChannel>() private val sinkInputChannel = mock<InputChannel>() + private val decorationSurface = SurfaceControl.Builder().setName("decoration surface").build() + private val createdSurfaces = ArrayList<SurfaceControl>() + + @After + fun tearDown() { + decorationSurface.release() + } @Test fun testGrantInputChannelOffMainThread() { @@ -75,6 +84,35 @@ class DragResizeInputListenerTest : ShellTestCase() { } @Test + fun testGrantInputChannelAfterDecorSurfaceReleased() { + // Keep tracking the underlying surface that the decorationSurface points to. + val forVerification = SurfaceControl(decorationSurface, "forVerification") + try { + create() + decorationSurface.release() + testBgExecutor.flushAll() + + verify(mockWindowSession) + .grantInputChannel( + anyInt(), + argThat<SurfaceControl> { isValid && isSameSurface(forVerification) }, + any(), + anyOrNull(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyOrNull(), + any(), + any(), + any(), + ) + } finally { + forVerification.release() + } + } + + @Test fun testInitializationCallback_waitsForBgSetup() { val inputListener = create() @@ -155,6 +193,30 @@ class DragResizeInputListenerTest : ShellTestCase() { verify(sinkInputChannel).dispose() } + @Test + fun testClose_beforeBgSetup_releaseSurfaces() { + val inputListener = create() + inputListener.close() + testBgExecutor.flushAll() + testMainExecutor.flushAll() + + assertThat(createdSurfaces).hasSize(1) + assertThat(createdSurfaces[0].isValid).isFalse() + } + + @Test + fun testClose_afterBgSetup_releaseSurfaces() { + val inputListener = create() + testBgExecutor.flushAll() + inputListener.close() + testMainExecutor.flushAll() + testBgExecutor.flushAll() + + assertThat(createdSurfaces).hasSize(2) + assertThat(createdSurfaces[0].isValid).isFalse() + assertThat(createdSurfaces[1].isValid).isFalse() + } + private fun verifyNoInputChannelGrantRequests() { verify(mockWindowSession, never()) .grantInputChannel( @@ -184,10 +246,22 @@ class DragResizeInputListenerTest : ShellTestCase() { TestHandler(Looper.getMainLooper()), mock<Choreographer>(), Display.DEFAULT_DISPLAY, - mock<SurfaceControl>(), + decorationSurface, mock<DragPositioningCallback>(), - { SurfaceControl.Builder() }, - { StubTransaction() }, + { + object : SurfaceControl.Builder() { + override fun build(): SurfaceControl { + return super.build().also { createdSurfaces.add(it) } + } + } + }, + { + object : StubTransaction() { + override fun remove(sc: SurfaceControl): SurfaceControl.Transaction { + return super.remove(sc).also { sc.release() } + } + } + }, mock<DisplayController>(), mock<DesktopModeEventLogger>(), inputChannel, |