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

Commit 124c7176 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update drag resize input listener in bg thread" into main

parents 439f8966 f6c88eef
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -339,6 +339,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
                        taskInfo,
                        taskSurface,
                        mMainHandler,
                        mMainExecutor,
                        mBgExecutor,
                        mMainChoreographer,
                        mSyncQueue,
+22 −8
Original line number Diff line number Diff line
@@ -48,6 +48,8 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.DesktopModeFlags;
import android.window.WindowContainerTransaction;

import com.android.internal.annotations.VisibleForTesting;
@@ -58,6 +60,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
@@ -69,6 +72,7 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 */
public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
    private final Handler mHandler;
    private final @ShellMainThread ShellExecutor mMainExecutor;
    private final @ShellBackgroundThread ShellExecutor mBgExecutor;
    private final Choreographer mChoreographer;
    private final SyncTransactionQueue mSyncQueue;
@@ -90,6 +94,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
            RunningTaskInfo taskInfo,
            SurfaceControl taskSurface,
            Handler handler,
            @ShellMainThread ShellExecutor mainExecutor,
            @ShellBackgroundThread ShellExecutor bgExecutor,
            Choreographer choreographer,
            SyncTransactionQueue syncQueue,
@@ -97,6 +102,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
        super(context, userContext, displayController, taskOrganizer, taskInfo,
                taskSurface, windowDecorViewHostSupplier);
        mHandler = handler;
        mMainExecutor = mainExecutor;
        mBgExecutor = bgExecutor;
        mChoreographer = choreographer;
        mSyncQueue = syncQueue;
@@ -287,8 +293,14 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL

        if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
            closeDragResizeListener();
            final ShellExecutor bgExecutor =
                    DesktopModeFlags.ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD.isTrue()
                            ? mBgExecutor : mMainExecutor;
            mDragResizeListener = new DragResizeInputListener(
                    mContext,
                    WindowManagerGlobal.getWindowSession(),
                    mMainExecutor,
                    bgExecutor,
                    mTaskInfo,
                    mHandler,
                    mChoreographer,
@@ -299,17 +311,19 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
                    mSurfaceControlTransactionSupplier,
                    mDisplayController);
        }

        final DragResizeInputListener newListener = mDragResizeListener;
        final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
                .getScaledTouchSlop();

        final Resources res = mResult.mRootView.getResources();
        mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
        final DragResizeWindowGeometry newGeometry = new DragResizeWindowGeometry(
                0 /* taskCornerRadius */,
                new Size(mResult.mWidth, mResult.mHeight),
                getResizeEdgeHandleSize(res),
                getResizeHandleEdgeInset(res), getFineResizeCornerSize(res),
                        getLargeResizeCornerSize(res), DragResizeWindowGeometry.DisabledEdge.NONE),
                touchSlop);
                getLargeResizeCornerSize(res), DragResizeWindowGeometry.DisabledEdge.NONE);
        newListener.addInitializedCallback(() -> {
            mDragResizeListener.setGeometry(newGeometry, touchSlop);
        });
    }

    /**
+39 −21
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.widget.ImageButton;
import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
@@ -479,7 +480,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        if (shouldDelayUpdate) {
            return;
        }
        updateDragResizeListener(mDecorationContainerSurface, inFullImmersive);
        updateDragResizeListenerIfNeeded(mDecorationContainerSurface, inFullImmersive);
    }


@@ -587,7 +588,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
            closeMaximizeMenu();
            notifyNoCaptionHandle();
        }
        updateDragResizeListener(oldDecorationSurface, inFullImmersive);
        updateDragResizeListenerIfNeeded(oldDecorationSurface, inFullImmersive);
        updateMaximizeMenu(startT, inFullImmersive);
        Trace.endSection(); // DesktopModeWindowDecoration#relayout
    }
@@ -665,22 +666,42 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        return mUserContext.getUser();
    }

    private void updateDragResizeListener(SurfaceControl oldDecorationSurface,
    private void updateDragResizeListenerIfNeeded(@Nullable SurfaceControl containerSurface,
            boolean inFullImmersive) {
        final boolean taskPositionChanged = !mTaskInfo.positionInParent.equals(mPositionInParent);
        if (!isDragResizable(mTaskInfo, inFullImmersive)) {
            if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
            if (taskPositionChanged) {
                // We still want to track caption bar's exclusion region on a non-resizeable task.
                updateExclusionRegion(inFullImmersive);
            }
            closeDragResizeListener();
            return;
        }
        updateDragResizeListener(containerSurface,
                (geometryChanged) -> {
                    if (geometryChanged || taskPositionChanged) {
                        updateExclusionRegion(inFullImmersive);
                    }
                });
    }

        if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
    private void updateDragResizeListener(@Nullable SurfaceControl containerSurface,
            Consumer<Boolean> onUpdateFinished) {
        final boolean containerSurfaceChanged = containerSurface != mDecorationContainerSurface;
        final boolean isFirstDragResizeListener = mDragResizeListener == null;
        final boolean shouldCreateListener = containerSurfaceChanged || isFirstDragResizeListener;
        if (containerSurfaceChanged) {
            closeDragResizeListener();
            Trace.beginSection("DesktopModeWindowDecoration#relayout-DragResizeInputListener");
        }
        if (shouldCreateListener) {
            final ShellExecutor bgExecutor =
                    DesktopModeFlags.ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD.isTrue()
                            ? mBgExecutor : mMainExecutor;
            mDragResizeListener = new DragResizeInputListener(
                    mContext,
                    WindowManagerGlobal.getWindowSession(),
                    mMainExecutor,
                    bgExecutor,
                    mTaskInfo,
                    mHandler,
                    mChoreographer,
@@ -691,24 +712,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                    mSurfaceControlTransactionSupplier,
                    mDisplayController,
                    mDesktopModeEventLogger);
            Trace.endSection();
        }

        final DragResizeInputListener newListener = mDragResizeListener;
        final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
                .getScaledTouchSlop();

        // If either task geometry or position have changed, update this task's
        // exclusion region listener
        final Resources res = mResult.mRootView.getResources();
        if (mDragResizeListener.setGeometry(
                new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
        final DragResizeWindowGeometry newGeometry = new DragResizeWindowGeometry(
                mRelayoutParams.mCornerRadius,
                new Size(mResult.mWidth, mResult.mHeight),
                getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res),
                getFineResizeCornerSize(res), getLargeResizeCornerSize(res),
                        mDisabledResizingEdge), touchSlop)
                || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
            updateExclusionRegion(inFullImmersive);
        }
                mDisabledResizingEdge);
        newListener.addInitializedCallback(() -> {
            onUpdateFinished.accept(newListener.setGeometry(newGeometry, touchSlop));
        });
    }

    private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo,
@@ -1711,7 +1728,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
     */
    private Region getGlobalExclusionRegion(boolean inFullImmersive) {
        Region exclusionRegion;
        if (mDragResizeListener != null && isDragResizable(mTaskInfo, inFullImmersive)) {
        if (mDragResizeListener != null
                && isDragResizable(mTaskInfo, inFullImmersive)) {
            exclusionRegion = mDragResizeListener.getCornersRegion();
        } else {
            exclusionRegion = new Region();
+253 −70

File changed.

Preview size limit exceeded, changes collapsed.

+207 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.wm.shell.windowdecor

import android.app.ActivityManager
import android.content.Context
import android.graphics.Region
import android.os.Handler
import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.Size
import android.view.Choreographer
import android.view.Display
import android.view.IWindowSession
import android.view.InputChannel
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestHandler
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.util.StubTransaction
import com.android.wm.shell.windowdecor.DragResizeInputListener.TaskResizeInputEventReceiver
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
import java.util.function.Supplier
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.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify

/**
 * Tests for [DragResizeInputListener].
 *
 * Build/Install/Run:
 *   atest WMShellUnitTests:DragResizeInputListenerTest
 */
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner::class)
class DragResizeInputListenerTest : ShellTestCase() {
    private val testMainExecutor = TestShellExecutor()
    private val testBgExecutor = TestShellExecutor()
    private val mockWindowSession = mock<IWindowSession>()
    private val mockInputEventReceiver = mock<TaskResizeInputEventReceiver>()

    @Test
    fun testGrantInputChannelOffMainThread() {
        create()
        testMainExecutor.flushAll()

        verifyNoInputChannelGrantRequests()
    }

    @Test
    fun testInitializationCallback_waitsForBgSetup() {
        val inputListener = create()

        val callback = TestInitializationCallback()
        inputListener.addInitializedCallback(callback)
        assertThat(callback.initialized).isFalse()

        testBgExecutor.flushAll()
        testMainExecutor.flushAll()

        assertThat(callback.initialized).isTrue()
    }

    @Test
    fun testInitializationCallback_alreadyInitialized_callsBackImmediately() {
        val inputListener = create()
        testBgExecutor.flushAll()
        testMainExecutor.flushAll()

        val callback = TestInitializationCallback()
        inputListener.addInitializedCallback(callback)

        assertThat(callback.initialized).isTrue()
    }

    @Test
    fun testClose_beforeBgSetup_cancelsBgSetup() {
        val inputListener = create()

        inputListener.close()
        testBgExecutor.flushAll()

        verifyNoInputChannelGrantRequests()
    }

    @Test
    fun testClose_beforeBgSetupResultSet_cancelsInit() {
        val inputListener = create()
        val callback = TestInitializationCallback()
        inputListener.addInitializedCallback(callback)

        testBgExecutor.flushAll()
        inputListener.close()
        testMainExecutor.flushAll()

        assertThat(callback.initialized).isFalse()
    }

    @Test
    fun testClose_afterInit_disposesOfReceiver() {
        val inputListener = create()

        testBgExecutor.flushAll()
        testMainExecutor.flushAll()
        inputListener.close()

        verify(mockInputEventReceiver).dispose()
    }

    @Test
    fun testClose_afterInit_removesTokens() {
        val inputListener = create()

        inputListener.close()
        testBgExecutor.flushAll()

        verify(mockWindowSession).remove(inputListener.mClientToken)
        verify(mockWindowSession).remove(inputListener.mSinkClientToken)
    }

    private fun verifyNoInputChannelGrantRequests() {
        verify(mockWindowSession, never())
            .grantInputChannel(
                anyInt(),
                any(),
                any(),
                anyOrNull(),
                anyInt(),
                anyInt(),
                anyInt(),
                anyInt(),
                anyOrNull(),
                any(),
                any(),
                any(),
            )
    }

    private fun create(): DragResizeInputListener =
        DragResizeInputListener(
            context,
            mockWindowSession,
            testMainExecutor,
            testBgExecutor,
            TestTaskResizeInputEventReceiverFactory(mockInputEventReceiver),
            TestRunningTaskInfoBuilder().build(),
            TestHandler(Looper.getMainLooper()),
            mock<Choreographer>(),
            Display.DEFAULT_DISPLAY,
            mock<SurfaceControl>(),
            mock<DragPositioningCallback>(),
            { SurfaceControl.Builder() },
            { StubTransaction() },
            mock<DisplayController>(),
            mock<DesktopModeEventLogger>(),
        )

    private class TestInitializationCallback : Runnable {
        var initialized: Boolean = false
            private set

        override fun run() {
            initialized = true
        }
    }

    private class TestTaskResizeInputEventReceiverFactory(
        private val mockInputEventReceiver: TaskResizeInputEventReceiver
    ) : DragResizeInputListener.TaskResizeInputEventReceiverFactory {
        override fun create(
            context: Context,
            taskInfo: ActivityManager.RunningTaskInfo,
            inputChannel: InputChannel,
            callback: DragPositioningCallback,
            handler: Handler,
            choreographer: Choreographer,
            displayLayoutSizeSupplier: Supplier<Size?>,
            touchRegionConsumer: Consumer<Region?>,
            desktopModeEventLogger: DesktopModeEventLogger,
        ): TaskResizeInputEventReceiver = mockInputEventReceiver
    }
}