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

Commit 9be8717f authored by Jorge Gil's avatar Jorge Gil Committed by Android (Google) Code Review
Browse files

Merge "Fix calculation of the caption's customizableRegion" into main

parents 1f9c2f40 ed778d54
Loading
Loading
Loading
Loading
+19 −123
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
import com.android.wm.shell.windowdecor.caption.OccludingElement;
import com.android.wm.shell.windowdecor.common.CaptionRegionHelper;
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.InsetsStateKt;
@@ -304,6 +305,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
        outResult.mCaptionY = 0;
        outResult.mCaptionTopPadding = params.mCaptionTopPadding;
        final Rect localCaptionBounds = new Rect(
                outResult.mCaptionX,
                outResult.mCaptionY,
                outResult.mCaptionX + outResult.mCaptionWidth,
                outResult.mCaptionY + outResult.mCaptionHeight);
        outResult.mCustomizableCaptionRegion.set(
                CaptionRegionHelper.calculateCustomizableRegion(mDecorWindowContext,
                        mTaskInfo, params.mOccludingElementsCalculator.get(), localCaptionBounds));

        if (params.mBorderSettingsId != Resources.ID_NULL) {
            outResult.mBorderSettings = BoxShadowHelper.getBorderSettings(mDecorWindowContext,
@@ -332,7 +341,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        final SurfaceControl captionSurface = mViewHost.getSurfaceControl();
        updateDecorationContainerSurface(startT, outResult);
        updateCaptionContainerSurface(captionSurface, startT, outResult);
        updateCaptionInsets(params, wct, outResult, taskBounds);
        updateCaptionInsets(params, wct, outResult, taskBounds, localCaptionBounds,
                mDecorWindowContext);
        updateTaskSurface(params, startT, finishT, outResult);
        Trace.endSection();

@@ -342,14 +352,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
                params.mCaptionTopPadding,
                outResult.mRootView.getPaddingRight(),
                outResult.mRootView.getPaddingBottom());
        final Rect localCaptionBounds = new Rect(
                outResult.mCaptionX,
                outResult.mCaptionY,
                outResult.mCaptionX + outResult.mCaptionWidth,
                outResult.mCaptionY + outResult.mCaptionHeight);
        final Region touchableRegion = params.mLimitTouchRegionToSystemAreas
                ? calculateLimitedTouchableRegion(params, localCaptionBounds)
                : null;
        final Region touchableRegion = CaptionRegionHelper.calculateLimitedTouchableRegion(
                mDecorWindowContext, mTaskInfo, params.mDisplayExclusionRegion,
                params.mOccludingElementsCalculator.get(), localCaptionBounds);
        updateViewHierarchy(params, outResult, startT, touchableRegion);
        Trace.endSection();

@@ -474,7 +479,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
    }

    private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct,
            RelayoutResult<T> outResult, Rect taskBounds) {
            RelayoutResult<T> outResult, Rect taskBounds, Rect localCaptionBounds,
            Context decorWindowContext) {
        if (!mIsCaptionVisible || !params.mIsInsetSource) {
            if (mWindowDecorationInsets != null) {
                mWindowDecorationInsets.remove(wct);
@@ -490,25 +496,9 @@ 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 List<Rect> boundingRects = new ArrayList<>();
        final List<OccludingElement> elements = params.mOccludingElementsCalculator.get();
        if (!elements.isEmpty()) {
            // The customizable region can at most be equal to the caption bar.
            if (params.hasInputFeatureSpy()) {
                outResult.mCustomizableCaptionRegion.set(captionInsetsRect);
            }
            for (int i = 0; i < elements.size(); i++) {
                final OccludingElement element = elements.get(i);
                final Rect boundingRect = calculateBoundingRectLocal(element, captionInsetsRect);
                boundingRects.add(boundingRect);
                // Subtract the regions used by the caption elements, the rest is
                // customizable.
                if (params.hasInputFeatureSpy()) {
                    outResult.mCustomizableCaptionRegion.op(boundingRect, Region.Op.DIFFERENCE);
                }
            }
        }
        // These are in coordinates relative to the caption frame.
        final List<Rect> boundingRects = CaptionRegionHelper.calculateBoundingRectsInsets(
                decorWindowContext, localCaptionBounds, params.mOccludingElementsCalculator.get());

        final WindowDecorationInsets newInsets = new WindowDecorationInsets(
                mTaskInfo.token, mOwner, captionInsetsRect, taskBounds, boundingRects,
@@ -592,80 +582,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        }
    }

    @NonNull
    private Region calculateLimitedTouchableRegion(
            RelayoutParams params,
            @NonNull Rect localCaptionBounds) {
        // Make caption bounds relative to display to align with exclusion region.
        final Point positionInParent = params.mRunningTaskInfo.positionInParent;
        final Rect captionBoundsInDisplay = new Rect(localCaptionBounds);
        captionBoundsInDisplay.offsetTo(positionInParent.x, positionInParent.y);

        final Region boundingRects = calculateBoundingRectsRegion(params, captionBoundsInDisplay);

        final Region customizedRegion = Region.obtain();
        customizedRegion.set(captionBoundsInDisplay);
        customizedRegion.op(boundingRects, Region.Op.DIFFERENCE);
        customizedRegion.op(params.mDisplayExclusionRegion, Region.Op.INTERSECT);

        final Region touchableRegion = Region.obtain();
        touchableRegion.set(captionBoundsInDisplay);
        touchableRegion.op(customizedRegion, Region.Op.DIFFERENCE);
        // Return resulting region back to window coordinates.
        touchableRegion.translate(-positionInParent.x, -positionInParent.y);

        boundingRects.recycle();
        customizedRegion.recycle();
        return touchableRegion;
    }

    @NonNull
    private Region calculateBoundingRectsRegion(
            @NonNull RelayoutParams params,
            @NonNull Rect captionBoundsInDisplay) {
        final List<OccludingElement> elements = params.mOccludingElementsCalculator.get();
        final Region region = Region.obtain();
        if (elements.isEmpty()) {
            // The entire caption is a bounding rect.
            region.set(captionBoundsInDisplay);
            return region;
        }
        for (OccludingElement e: elements) {
            final Rect boundingRect = calculateBoundingRectLocal(e, captionBoundsInDisplay);
            // Bounding rect is initially calculated relative to the caption, so offset it to make
            // it relative to the display.
            boundingRect.offset(captionBoundsInDisplay.left, captionBoundsInDisplay.top);
            region.union(boundingRect);
        }
        return region;
    }

    private Rect calculateBoundingRectLocal(@NonNull OccludingElement element,
            @NonNull Rect captionRect) {
        final boolean isRtl =
                mDecorWindowContext.getResources().getConfiguration().getLayoutDirection()
                        == View.LAYOUT_DIRECTION_RTL;
        switch (element.getAlignment()) {
            case START -> {
                if (isRtl) {
                    return new Rect(captionRect.width() - element.getWidth(), 0,
                            captionRect.width(), captionRect.height());
                } else {
                    return new Rect(0, 0, element.getWidth(), captionRect.height());
                }
            }
            case END -> {
                if (isRtl) {
                    return new Rect(0, 0, element.getWidth(), captionRect.height());
                } else {
                    return new Rect(captionRect.width() - element.getWidth(), 0,
                            captionRect.width(), captionRect.height());
                }
            }
        }
        throw new IllegalArgumentException("Unexpected alignment " + element.getAlignment());
    }

    void onKeyguardStateChanged(boolean visible, boolean occluded) {
        final boolean prevVisAndOccluded = mIsKeyguardVisibleAndOccluded;
        mIsKeyguardVisibleAndOccluded = visible && occluded;
@@ -792,13 +708,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        return resources.getDimensionPixelSize(resourceId);
    }

    static float loadDimension(Resources resources, int resourceId) {
        if (resourceId == Resources.ID_NULL) {
            return 0;
        }
        return resources.getDimension(resourceId);
    }

    private static SurfaceControl cloneSurfaceControl(SurfaceControl sc,
            Supplier<SurfaceControl> surfaceControlSupplier) {
        final SurfaceControl copy = surfaceControlSupplier.get();
@@ -997,19 +906,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        }
    }

    private static class CaptionWindowlessWindowManager extends WindowlessWindowManager {
        CaptionWindowlessWindowManager(
                @NonNull Configuration configuration,
                @NonNull SurfaceControl rootSurface) {
            super(configuration, rootSurface, /* hostInputToken= */ null);
        }

        /** Set the view host's touchable region. */
        void setTouchRegion(@NonNull SurfaceControlViewHost viewHost, @NonNull Region region) {
            setTouchRegion(viewHost.getWindowToken().asBinder(), region);
        }
    }

    public interface SurfaceControlViewHostFactory {
        default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
            return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
+47 −153
Original line number Diff line number Diff line
@@ -39,11 +39,10 @@ import com.android.wm.shell.windowdecor.TaskFocusStateConsumer
import com.android.wm.shell.windowdecor.WindowDecoration2.RelayoutParams
import com.android.wm.shell.windowdecor.WindowDecoration2.SurfaceControlViewHostFactory
import com.android.wm.shell.windowdecor.WindowDecorationInsets
import com.android.wm.shell.windowdecor.common.CaptionRegionHelper
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.identityHashCode
import com.android.wm.shell.windowdecor.extension.isRtl
import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance
import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder

/**
@@ -139,6 +138,38 @@ abstract class CaptionController<T>(
            val captionX = (taskBounds.width() - captionWidth) / 2
            val captionY = 0
            val elements = occludingElements
            val localCaptionBounds =
                Rect(captionX, captionY, captionX + captionWidth, captionY + captionHeight)
            val customizableCaptionRegion =
                CaptionRegionHelper.calculateCustomizableRegion(
                    context = decorWindowContext,
                    taskInfo = taskInfo,
                    elements = elements,
                    localCaptionBounds = localCaptionBounds,
                )
            val touchableRegion =
                CaptionRegionHelper.calculateLimitedTouchableRegion(
                    context = decorWindowContext,
                    taskInfo = taskInfo,
                    displayExclusionRegion = params.displayExclusionRegion,
                    elements = elements,
                    localCaptionBounds = localCaptionBounds,
                )

            logD(
                "relayout with taskBounds=%s captionSize=%dx%d captionTopPadding=%d " +
                    "captionX=%d captionY=%d elements=%s customizableCaptionRegion=%s " +
                    "touchableRegion=%s",
                taskBounds,
                captionHeight,
                captionWidth,
                captionTopPadding,
                captionX,
                captionY,
                elements,
                customizableCaptionRegion.toReadableString(),
                touchableRegion,
            )

            updateCaptionContainerSurface(
                parentContainer,
@@ -147,7 +178,6 @@ abstract class CaptionController<T>(
                captionHeight,
                captionX,
            )
            val customizableCaptionRegion =
            updateCaptionInsets(
                params = params,
                elements = elements,
@@ -155,18 +185,7 @@ abstract class CaptionController<T>(
                wct = wct,
                captionHeight = captionHeight,
                taskBounds = taskBounds,
                )
            logD(
                "relayout with taskBounds=%s captionSize=%dx%d captionTopPadding=%d " +
                    "captionX=%d captionY=%d elements=%s customCaptionRegion=%s",
                taskBounds,
                captionHeight,
                captionWidth,
                captionTopPadding,
                captionX,
                captionY,
                elements,
                customizableCaptionRegion.toReadableString(),
                localCaptionBounds = localCaptionBounds,
            )

            traceSection(
@@ -175,15 +194,6 @@ abstract class CaptionController<T>(
            ) {
                viewHolder.setTopPadding(captionTopPadding)
                viewHolder.setTaskFocusState(params.hasGlobalFocus)
                val localCaptionBounds =
                    Rect(captionX, captionY, captionX + captionWidth, captionY + captionHeight)
                val touchableRegion =
                    calculateLimitedTouchableRegion(
                        params,
                        elements,
                        decorWindowContext,
                        localCaptionBounds,
                    )
                updateViewHierarchy(
                    params,
                    viewHost,
@@ -265,76 +275,6 @@ abstract class CaptionController<T>(
            }
        }

    /**
     * Calculates the touchable region of the caption to only the areas where input should be
     * handled by the system (i.e. non custom-excluded areas). The region will be calculated based
     * on occluding caption elements and exclusion areas reported by the app.
     *
     * If app is not requesting to customize caption bar, returns [null] signifying that the
     * touchable region is not limited.
     */
    private fun calculateLimitedTouchableRegion(
        params: RelayoutParams,
        elements: List<OccludingElement>,
        decorWindowContext: Context,
        localCaptionBounds: Rect,
    ): Region? {
        if (!taskInfo.isTransparentCaptionBarAppearance) {
            // App is not requesting custom caption, touchable region is not limited so return null.
            return null
        }

        val taskPositionInParent = taskInfo.positionInParent
        val captionBoundsInDisplay =
            Rect(localCaptionBounds).apply {
                offsetTo(taskPositionInParent.x, taskPositionInParent.y)
            }

        val boundingRects =
            calculateBoundingRectsRegion(elements, decorWindowContext, captionBoundsInDisplay)

        val customizedRegion =
            Region.obtain().apply {
                set(captionBoundsInDisplay)
                op(boundingRects, Region.Op.DIFFERENCE)
                op(params.displayExclusionRegion, Region.Op.INTERSECT)
            }

        val touchableRegion =
            Region.obtain().apply {
                set(captionBoundsInDisplay)
                op(customizedRegion, Region.Op.DIFFERENCE)
                // Return resulting region back to window coordinates.
                translate(-taskPositionInParent.x, -taskPositionInParent.y)
            }

        boundingRects.recycle()
        customizedRegion.recycle()
        return touchableRegion
    }

    private fun calculateBoundingRectsRegion(
        elements: List<OccludingElement>,
        decorWindowContext: Context,
        captionBoundsInDisplay: Rect,
    ): Region {
        val region = Region.obtain()
        if (elements.isEmpty()) {
            // The entire caption is a bounding rect.
            region.set(captionBoundsInDisplay)
            return region
        }
        elements.forEach { element ->
            val boundingRect =
                calculateBoundingRectLocal(element, captionBoundsInDisplay, decorWindowContext)
            // Bounding rect is initially calculated relative to the caption, so offset it to make
            // it relative to the display.
            boundingRect.offset(captionBoundsInDisplay.left, captionBoundsInDisplay.top)
            region.union(boundingRect)
        }
        return region
    }

    private fun updateCaptionContainerSurface(
        parentContainer: SurfaceControl,
        startT: SurfaceControl.Transaction,
@@ -382,11 +322,11 @@ abstract class CaptionController<T>(
        wct: WindowContainerTransaction,
        captionHeight: Int,
        taskBounds: Rect,
    ): Region {
        localCaptionBounds: Rect,
    ) {
        if (!isCaptionVisible || !params.isInsetSource) {
            captionInsets?.remove(wct)
            captionInsets = null
            return Region.obtain()
        }
        // Caption inset is the full width of the task with the |captionHeight| and
        // positioned at the top of the task bounds, also in absolute coordinates.
@@ -396,25 +336,13 @@ abstract class CaptionController<T>(

        // 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.
        val customizableCaptionRegion = Region.obtain()
        val boundingRects = mutableListOf<Rect>()
        if (elements.isNotEmpty()) {
            // The customizable region can at most be equal to the caption bar.
            if (params.hasInputFeatureSpy()) {
                customizableCaptionRegion.set(captionInsetsRect)
            }
            for (element in elements) {
                val boundingRect =
                    calculateBoundingRectLocal(element, captionInsetsRect, decorWindowContext)
                boundingRects.add(boundingRect)
                // Subtract the regions used by the caption elements, the rest is
                // customizable.
                if (params.hasInputFeatureSpy()) {
                    customizableCaptionRegion.op(boundingRect, Region.Op.DIFFERENCE)
                }
            }
        }
        // These are in coordinates relative to the caption frame.
        val boundingRects =
            CaptionRegionHelper.calculateBoundingRectsInsets(
                context = decorWindowContext,
                captionFrame = localCaptionBounds,
                elements = elements,
            )

        val newInsets =
            WindowDecorationInsets(
@@ -432,42 +360,6 @@ abstract class CaptionController<T>(
            captionInsets = newInsets
            newInsets.update(wct)
        }

        return customizableCaptionRegion
    }

    private fun calculateBoundingRectLocal(
        element: OccludingElement,
        captionRect: Rect,
        decorWindowContext: Context,
    ): Rect {
        val isRtl = decorWindowContext.isRtl
        return when (element.alignment) {
            OccludingElement.Alignment.START -> {
                if (isRtl) {
                    Rect(
                        captionRect.width() - element.width,
                        0,
                        captionRect.width(),
                        captionRect.height(),
                    )
                } else {
                    Rect(0, 0, element.width, captionRect.height())
                }
            }
            OccludingElement.Alignment.END -> {
                if (isRtl) {
                    Rect(0, 0, element.width, captionRect.height())
                } else {
                    Rect(
                        captionRect.width() - element.width,
                        0,
                        captionRect.width(),
                        captionRect.height(),
                    )
                }
            }
        }
    }

    /** Checks whether the touch event falls inside the customizable caption region. */
@@ -551,6 +443,8 @@ abstract class CaptionController<T>(
        // The caption y position relative to its parent task
        val captionY: Int,
        val captionTopPadding: Int,
        // The region within the caption that is allowed to be customized by the app, in display
        // coordinates.
        val customizableCaptionRegion: Region,
    )

+224 −0

File added.

Preview size limit exceeded, changes collapsed.

+6 −1
Original line number Diff line number Diff line
@@ -52,7 +52,12 @@ object DesktopTestHelpers {
            .setLastActiveTime(100)
            .setUserId(userId)
            .apply { taskId?.let { setTaskId(it) } }
            .apply { bounds?.let { setBounds(it) } }
            .apply {
                bounds?.let { b ->
                    setBounds(b)
                    setPositionInParent(b.left, b.top)
                }
            }
            .build()

    fun createPinnedTask(displayId: Int = DEFAULT_DISPLAY, bounds: Rect? = null): RunningTaskInfo =
+26 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.windowdecor

import android.annotation.IdRes
import android.app.ActivityManager
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.graphics.Bitmap
@@ -27,6 +28,7 @@ import android.os.Handler
import android.view.Display
import android.view.SurfaceControl
import android.view.View
import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
import android.view.WindowManager
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
@@ -63,6 +65,30 @@ object WindowDecorationTestHelper {
        return createFreeformTask(bounds = bounds).apply { isVisible = true }
    }

    /** Creates a task that should be decorated with an opaque (not customizable) app header. */
    fun createOpaqueAppHeaderTask(bounds: Rect = Rect(200, 200, 800, 600)): RunningTaskInfo {
        return createAppHeaderTask(bounds = bounds).apply {
            taskDescription =
                (taskDescription ?: ActivityManager.TaskDescription()).apply {
                    topOpaqueSystemBarsAppearance =
                        topOpaqueSystemBarsAppearance and
                            APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND.inv()
                }
        }
    }

    /** Creates a task that should be decorated with an app header and is being customized. */
    fun createCustomAppHeaderTask(bounds: Rect = Rect(200, 200, 800, 600)): RunningTaskInfo {
        return createAppHeaderTask(bounds = bounds).apply {
            taskDescription =
                (taskDescription ?: ActivityManager.TaskDescription()).apply {
                    topOpaqueSystemBarsAppearance =
                        topOpaqueSystemBarsAppearance or
                            APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
                }
        }
    }

    /** Creates a window decoration. */
    fun createWindowDecoration(
        context: Context,
Loading