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

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

Merge "Fix WindowDecorationInsets equals() comparison" into main

parents da353650 a7f66443
Loading
Loading
Loading
Loading
+9 −10
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSuppl
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Supplier;
@@ -483,29 +484,26 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        // Caption bounding rectangles: these are optional, and are used to present finer
        // insets than traditional |Insets| to apps about where their content is occluded.
        // These are also in absolute coordinates.
        final Rect[] boundingRects;
        final List<Rect> boundingRects = new ArrayList<>();
        final int numOfElements = params.mOccludingCaptionElements.size();
        if (numOfElements == 0) {
            boundingRects = null;
        } else {
        if (numOfElements != 0) {
            // The customizable region can at most be equal to the caption bar.
            if (params.hasInputFeatureSpy()) {
                outResult.mCustomizableCaptionRegion.set(captionInsetsRect);
            }
            final Resources resources = mDecorWindowContext.getResources();
            boundingRects = new Rect[numOfElements];
            for (int i = 0; i < numOfElements; i++) {
                final OccludingCaptionElement element =
                        params.mOccludingCaptionElements.get(i);
                final int elementWidthPx =
                        resources.getDimensionPixelSize(element.mWidthResId);
                boundingRects[i] =
                final Rect boundingRect =
                        calculateBoundingRectLocal(element, elementWidthPx, captionInsetsRect);
                boundingRects.add(boundingRect);
                // Subtract the regions used by the caption elements, the rest is
                // customizable.
                if (params.hasInputFeatureSpy()) {
                    outResult.mCustomizableCaptionRegion.op(boundingRects[i],
                            Region.Op.DIFFERENCE);
                    outResult.mCustomizableCaptionRegion.op(boundingRect, Region.Op.DIFFERENCE);
                }
            }
        }
@@ -889,8 +887,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>

        final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
        final WindowDecorationInsets newInsets = new WindowDecorationInsets(mTaskInfo.token,
                mOwner, captionInsets, null  /* taskFrame */,  null /* boundingRects */,
                0 /* flags */, true /* shouldAddCaptionInset */, false /* excludedFromAppBounds */);
                mOwner, captionInsets, null  /* taskFrame */,
                Collections.emptyList() /* boundingRects */, 0 /* flags */,
                true /* shouldAddCaptionInset */, false /* excludedFromAppBounds */);
        if (!newInsets.equals(mWindowDecorationInsets)) {
            mWindowDecorationInsets = newInsets;
            mWindowDecorationInsets.update(wct);
+97 −48
Original line number Diff line number Diff line
@@ -23,31 +23,71 @@ import android.view.InsetsSource
import android.view.WindowInsets
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_WINDOW_DECORATION

/** Adds, removes, and updates caption insets. */
data class WindowDecorationInsets(
data class WindowDecorationInsets private constructor(
    private val token: WindowContainerToken,
    private val owner: Binder,
    private val frame: Rect,
    private val taskFrame: Rect? = null,
    private val boundingRects: Array<Rect>? = null,
    private val frame: Frame,
    private val boundingRects: List<Rect> = emptyList(),
    @InsetsSource.Flags private val flags: Int = 0,
    private val shouldAddCaptionInset: Boolean = false,
    private val excludedFromAppBounds: Boolean = false,
    private val appBoundsExclusion: AppBoundsExclusion? = null,
) {
    private sealed class Frame {
        abstract val height: Int

        data class Absolute(val rect: Rect, override val height: Int = rect.height()) : Frame()
        data class Relative(override val height: Int) : Frame()
    }
    private data class AppBoundsExclusion(val taskFrame: Rect)

    constructor(
        token: WindowContainerToken,
        owner: Binder,
        frame: Rect,
        taskFrame: Rect? = null,
        boundingRects: List<Rect> = emptyList(),
        @InsetsSource.Flags flags: Int = 0,
        shouldAddCaptionInset: Boolean = false,
        excludedFromAppBounds: Boolean = false,
    ) : this(
        token,
        owner,
        if (Flags.relativeInsets()) {
            Frame.Relative(frame.height())
        } else {
            Frame.Absolute(frame)
        },
        boundingRects,
        flags,
        shouldAddCaptionInset,
        if (excludedFromAppBounds) AppBoundsExclusion(checkNotNull(taskFrame)) else null
    )

    /** Updates the caption insets. */
    fun update(wct: WindowContainerTransaction) {
        if (!shouldAddCaptionInset) return
        if (com.android.window.flags.Flags.relativeInsets()) {
            val insets = Insets.of(0, frame.height(), 0, 0)
        logD(
            "update insets for wc=%s with frame=%s, rects=%s, appBoundsExclusion=%s",
            token,
            frame,
            boundingRects,
            appBoundsExclusion,
        )
        val rects = if (boundingRects.isEmpty()) null else boundingRects.toTypedArray()
        when (frame) {
            is Frame.Absolute -> {
                wct.addInsetsSource(
                    token,
                    owner,
                    INDEX,
                    WindowInsets.Type.captionBar(),
                insets,
                boundingRects,
                    frame.rect,
                    rects,
                    flags,
                )
                wct.addInsetsSource(
@@ -55,18 +95,20 @@ data class WindowDecorationInsets(
                    owner,
                    INDEX,
                    WindowInsets.Type.mandatorySystemGestures(),
                insets,
                boundingRects,
                    frame.rect,
                    rects,
                    /* flags= */ 0,
                )
        } else {
            }
            is Frame.Relative -> {
                val insets = Insets.of(0, frame.height, 0, 0)
                wct.addInsetsSource(
                    token,
                    owner,
                    INDEX,
                    WindowInsets.Type.captionBar(),
                frame,
                boundingRects,
                    insets,
                    rects,
                    flags,
                )
                wct.addInsetsSource(
@@ -74,14 +116,16 @@ data class WindowDecorationInsets(
                    owner,
                    INDEX,
                    WindowInsets.Type.mandatorySystemGestures(),
                frame,
                boundingRects,
                    insets,
                    rects,
                    /* flags= */ 0,
                )
            }
        if (excludedFromAppBounds) {
            val appBounds = Rect(taskFrame)
            appBounds.top += frame.height()
        }
        appBoundsExclusion?.let { exclusion ->
            val appBounds = Rect(exclusion.taskFrame).apply {
                top += frame.height
            }
            wct.setAppBounds(token, appBounds)
        }
    }
@@ -95,12 +139,17 @@ data class WindowDecorationInsets(
            INDEX,
            WindowInsets.Type.mandatorySystemGestures()
        )
        if (excludedFromAppBounds) {
        appBoundsExclusion?.let {
            wct.setAppBounds(token, Rect())
        }
    }

    private fun logD(msg: String, vararg arguments: Any?) {
        ProtoLog.d(WM_SHELL_WINDOW_DECORATION, "%s: $msg", TAG, *arguments)
    }

    companion object {
        private const val TAG = "WindowDecorationInsets"
        /** Index for caption insets source. */
        private const val INDEX = 0
    }
+10 −17
Original line number Diff line number Diff line
@@ -355,7 +355,7 @@ abstract class CaptionController<T>(
            owner = insetsOwner,
            frame = captionInsets,
            taskFrame = null,
            boundingRects = null,
            boundingRects = emptyList(),
            flags = 0,
            shouldAddCaptionInset = true,
            excludedFromAppBounds = false
@@ -388,35 +388,28 @@ abstract class CaptionController<T>(
        // These are also in absolute coordinates.
        val numOfElements = params.occludingCaptionElements.size
        val customizableCaptionRegion = Region.obtain()
        val boundingRects: Array<Rect>?
        if (numOfElements == 0) {
            boundingRects = null
        } else {
        val boundingRects = mutableListOf<Rect>()
        if (numOfElements != 0) {
            // The customizable region can at most be equal to the caption bar.
            if (params.hasInputFeatureSpy()) {
                customizableCaptionRegion.set(captionInsetsRect)
            }
            val resources = decorWindowContext.resources
            boundingRects = Array(numOfElements) { Rect() }

            for (i in 0 until numOfElements) {
                val element = params.occludingCaptionElements[i]
                val elementWidthPx = resources.getDimensionPixelSize(element.widthResId)
                boundingRects[i].set(
                    calculateBoundingRectLocal(
                val boundingRect = calculateBoundingRectLocal(
                    element,
                    elementWidthPx,
                    captionInsetsRect,
                    decorWindowContext
                )
                )
                boundingRects.add(boundingRect)
                // Subtract the regions used by the caption elements, the rest is
                // customizable.
                if (params.hasInputFeatureSpy()) {
                    customizableCaptionRegion.op(
                        boundingRects[i],
                        Region.Op.DIFFERENCE
                    )
                    customizableCaptionRegion.op(boundingRect, Region.Op.DIFFERENCE)
                }
            }
        }
+157 −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.graphics.Rect
import android.os.Binder
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

/**
 * Tests for [WindowDecorationInsets].
 *
 * Build/Install/Run:
 * atest WMShellUnitTests:WindowDecorationInsetsTest
 */
@SmallTest
@RunWith(AndroidTestingRunner::class)
class WindowDecorationInsetsTest {
    @JvmField @Rule val setFlagsRule = SetFlagsRule()

    private val token: WindowContainerToken = mock()
    private val owner = Binder()

    @Test
    fun `equals`() {
        val frame = Rect(0, 0, 1000, 80)
        val taskFrame = Rect(0, 0, 1000, 600)
        val insets1 = WindowDecorationInsets(
            token = token,
            owner = owner,
            frame = frame,
            taskFrame = taskFrame,
            boundingRects = emptyList(),
            flags = 0,
            shouldAddCaptionInset = true,
            excludedFromAppBounds = false
        )
        val insets2 = WindowDecorationInsets(
            token = token,
            owner = owner,
            frame = frame,
            taskFrame = taskFrame,
            boundingRects = emptyList(),
            flags = 0,
            shouldAddCaptionInset = true,
            excludedFromAppBounds = false
        )

        assertThat(insets1).isEqualTo(insets2)
    }

    @Test
    fun `equals with bounding rects`() {
        val frame = Rect(0, 0, 1000, 80)
        val taskFrame = Rect(0, 0, 1000, 600)
        val rects = listOf(Rect(0, 0, 300, 80), Rect(800, 0, 1000, 80))
        val insets1 = WindowDecorationInsets(
            token = token,
            owner = owner,
            frame = frame,
            taskFrame = taskFrame,
            boundingRects = rects,
            flags = 0,
            shouldAddCaptionInset = true,
            excludedFromAppBounds = false
        )
        val insets2 = WindowDecorationInsets(
            token = token,
            owner = owner,
            frame = frame,
            taskFrame = taskFrame,
            boundingRects = rects,
            flags = 0,
            shouldAddCaptionInset = true,
            excludedFromAppBounds = false
        )

        assertThat(insets1).isEqualTo(insets2)
    }

    @Test
    @EnableFlags(Flags.FLAG_RELATIVE_INSETS)
    fun `equals with different frame but same height`() {
        val taskFrame = Rect(0, 0, 1000, 600)
        val insets1 = WindowDecorationInsets(
            token = token,
            owner = owner,
            frame = Rect(0, 0, 1000, 80),
            taskFrame = taskFrame,
            boundingRects = emptyList(),
            flags = 0,
            shouldAddCaptionInset = true,
            excludedFromAppBounds = false
        )
        val insets2 = WindowDecorationInsets(
            token = token,
            owner = owner,
            frame = Rect(100, 0, 1000, 80),
            taskFrame = taskFrame,
            boundingRects = emptyList(),
            flags = 0,
            shouldAddCaptionInset = true,
            excludedFromAppBounds = false
        )

        assertThat(insets1).isEqualTo(insets2)
    }

    @Test
    fun `equals with different task frame but no app bounds exclusion`() {
        val frame = Rect(0, 0, 1000, 80)
        val insets1 = WindowDecorationInsets(
            token = token,
            owner = owner,
            frame = frame,
            taskFrame = Rect(0, 0, 1000, 600),
            boundingRects = emptyList(),
            flags = 0,
            shouldAddCaptionInset = true,
            excludedFromAppBounds = false
        )
        val insets2 = WindowDecorationInsets(
            token = token,
            owner = owner,
            frame = frame,
            taskFrame = Rect(10, 0, 1010, 600),
            boundingRects = emptyList(),
            flags = 0,
            shouldAddCaptionInset = true,
            excludedFromAppBounds = false
        )

        assertThat(insets1).isEqualTo(insets2)
    }
}
+33 −1
Original line number Diff line number Diff line
@@ -62,6 +62,8 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.os.LocaleList;
import android.os.Looper;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.UsesFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.util.DisplayMetrics;
@@ -907,7 +909,8 @@ public class WindowDecorationTests extends ShellTestCase {
    }

    @Test
    public void testRelayout_captionFrameChanged_insetsReapplied() {
    @DisableFlags(Flags.FLAG_RELATIVE_INSETS)
    public void testRelayout_taskFrameChanged_insetsReapplied() {
        final Display defaultDisplay = mock(Display.class);
        doReturn(defaultDisplay).when(mMockDisplayController)
                .getDisplay(Display.DEFAULT_DISPLAY);
@@ -932,6 +935,35 @@ public class WindowDecorationTests extends ShellTestCase {
        verifyAddedInsets(2 /* times */, token, 0 /* index */, mandatorySystemGestures());
    }

    @Test
    @EnableFlags(Flags.FLAG_RELATIVE_INSETS)
    public void testRelayout_captionFrameChanged_insetsReapplied() {
        final Display defaultDisplay = mock(Display.class);
        doReturn(defaultDisplay).when(mMockDisplayController)
                .getDisplay(Display.DEFAULT_DISPLAY);
        mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true);
        final WindowContainerToken token = new MockToken().token();
        final TestRunningTaskInfoBuilder builder = new TestRunningTaskInfoBuilder()
                .setDisplayId(Display.DEFAULT_DISPLAY)
                .setVisible(true);
        mRelayoutParams.mIsCaptionVisible = true;

        // Relayout twice with different caption heights.
        final ActivityManager.RunningTaskInfo firstTaskInfo =
                builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
        final TestWindowDecoration windowDecor = createWindowDecoration(firstTaskInfo);
        mRelayoutParams.mCaptionHeightCalculator = (context, display) -> 80;
        windowDecor.relayout(firstTaskInfo, true /* hasGlobalFocus */);
        final ActivityManager.RunningTaskInfo secondTaskInfo =
                builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
        mRelayoutParams.mCaptionHeightCalculator = (context, display) -> 100;
        windowDecor.relayout(secondTaskInfo, true /* hasGlobalFocus */);

        // Insets should be applied twice.
        verifyAddedInsets(2 /* times */, token, 0 /* index */, captionBar());
        verifyAddedInsets(2 /* times */, token, 0 /* index */, mandatorySystemGestures());
    }

    @Test
    public void testRelayout_captionFrameUnchanged_insetsNotApplied() {
        final Display defaultDisplay = mock(Display.class);