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

Commit cdd7c766 authored by Ben Lin's avatar Ben Lin Committed by Android (Google) Code Review
Browse files

Merge "WindowDecor: Make exclusion region display-specific." into main

parents cc94ba45 f20c4804
Loading
Loading
Loading
Loading
+21 −29
Original line number Diff line number Diff line
@@ -61,11 +61,9 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.GestureDetector;
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
@@ -149,6 +147,7 @@ import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
import com.android.wm.shell.windowdecor.common.AppHandleAndHeaderVisibilityHelper;
import com.android.wm.shell.windowdecor.common.WindowDecorationGestureExclusionTracker;
import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
@@ -242,27 +241,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
    private final AppToWebGenericLinksParser mGenericLinksParser;
    private final DisplayInsetsController mDisplayInsetsController;
    private final Region mExclusionRegion = Region.obtain();

    private final WindowDecorationGestureExclusionTracker mGestureExclusionTracker;
    private boolean mInImmersiveMode;
    private final String mSysUIPackageName;
    private final AssistContentRequester mAssistContentRequester;
    private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;

    private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener;
    private final ISystemGestureExclusionListener mGestureExclusionListener =
            new ISystemGestureExclusionListener.Stub() {
                @Override
                public void onSystemGestureExclusionChanged(int displayId,
                        Region systemGestureExclusion, Region systemGestureExclusionUnrestricted) {
                    if (mContext.getDisplayId() != displayId) {
                        return;
                    }
                    mMainExecutor.execute(() -> {
                        mExclusionRegion.set(systemGestureExclusion);
                        onExclusionRegionChanged(displayId, mExclusionRegion);
                    });
                }
            };
    private final WindowDecorationActions mWindowDecorationActions;
    private final TaskPositionerFactory mTaskPositionerFactory;
    private final FocusTransitionObserver mFocusTransitionObserver;
@@ -511,6 +497,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        mWindowDecorationActions =
                new DefaultWindowDecorationActions(this, mDesktopTasksController,
                        mContext, mDesktopModeUiEventLogger, mCompatUI);
        mGestureExclusionTracker =
                new WindowDecorationGestureExclusionTracker(mContext, mWindowManager,
                        mDisplayController, mMainExecutor, shellInit, (displayId, region) -> {
                    onExclusionRegionChanged(displayId, region);
                    return Unit.INSTANCE;
                });
        shellInit.addInitCallback(this::onInit, this);
    }

@@ -530,12 +522,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                    new DesktopModeRecentsTransitionStateListener());
        }
        mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
        try {
            mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
                    mContext.getDisplayId());
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to register window manager callbacks", e);
        }
        if (mDesktopState.canEnterDesktopModeOrShowAppHandle()
                && Flags.enableDesktopWindowingAppHandleEducation()) {
            mAppHandleEducationController.setAppHandleEducationTooltipCallbacks(
@@ -645,7 +631,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        } else {
            decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
                    false /* shouldSetTaskPositionAndCrop */,
                    mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion,
                    mFocusTransitionObserver.hasGlobalFocus(taskInfo),
                    mGestureExclusionTracker.getExclusionRegion(taskInfo.displayId),
                    /* inSyncWithTransition= */ true);
        }
    }
@@ -689,7 +676,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
                false /* shouldSetTaskPositionAndCrop */,
                mFocusTransitionObserver.hasGlobalFocus(taskInfo),
                mExclusionRegion, /* inSyncWithTransition= */ true);
                mGestureExclusionTracker.getExclusionRegion(taskInfo.displayId),
                /* inSyncWithTransition= */ true);
    }

    @Override
@@ -1187,8 +1175,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,

                final boolean downInCustomizableCaptionRegion =
                        decoration.checkTouchEventInCustomizableRegion(e);
                final boolean downInExclusionRegion = mExclusionRegion.contains(
                        (int) e.getRawX(), (int) e.getRawY());
                final Region exclusionRegion = mGestureExclusionTracker
                        .getExclusionRegion(e.getDisplayId());
                final boolean downInExclusionRegion =
                        exclusionRegion.contains((int) e.getRawX(), (int) e.getRawY());
                final boolean isTransparentCaption =
                        TaskInfoKt.isTransparentCaptionBarAppearance(decoration.mTaskInfo);
                // MotionEvent's coordinates are relative to view, we want location in window
@@ -1915,7 +1905,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        windowDecoration.relayout(taskInfo, startT, finishT,
                false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
                mFocusTransitionObserver.hasGlobalFocus(taskInfo),
                mExclusionRegion, /* inSyncWithTransition= */ true);
                mGestureExclusionTracker.getExclusionRegion(taskInfo.displayId),
                /* inSyncWithTransition= */ true);
        if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
            incrementEventReceiverTasks(taskInfo.displayId);
        }
@@ -1940,7 +1931,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        pw.println(innerPrefix + "DesktopModeStatus=" + mDesktopState.canEnterDesktopMode());
        pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
        pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
        pw.println(innerPrefix + "mExclusionRegion=" + mExclusionRegion);
        pw.println(innerPrefix + "mGestureExclusionTracker="
                + mGestureExclusionTracker);
        for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
            if (decor != null) {
+145 −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.common

import android.content.Context
import android.graphics.Region
import android.os.RemoteException
import android.util.Slog
import android.view.ISystemGestureExclusionListener
import android.view.IWindowManager
import android.window.DesktopExperienceFlags
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.sysui.ShellInit

/**
 * A tracking class that helps listening for display add/removed callbacks, and subsequently
 * starts tracking system gesture exclusion region reported for each display. Used by
 * WindowDecorationViewModel so it can update individual WindowDecorations regarding regions.
 */
class WindowDecorationGestureExclusionTracker(
    private val context: Context,
    private val windowManager: IWindowManager,
    private val displayController: DisplayController,
    @ShellMainThread private val mainExecutor: ShellExecutor,
    shellInit: ShellInit,
    exclusionRegionChangedCallback: (Int, Region) -> Unit,

) : DisplayController.OnDisplaysChangedListener {
    private val exclusionRegion = Region()
    private val exclusionRegions = object : HashMap<Int, Region>() {
        override operator fun get(displayId: Int): Region {
            return if (DesktopExperienceFlags.ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY.isTrue()) {
                super.get(displayId) ?: Region()
            } else {
                exclusionRegion
            }
        }
    }

    private val exclusionListener = object : ISystemGestureExclusionListener.Stub() {
        override fun onSystemGestureExclusionChanged(
            displayId: Int,
            systemGestureExclusion: Region,
            systemGestureExclusionUnrestricted: Region?
        ) {
            if (DesktopExperienceFlags.ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY.isTrue()) {
                mainExecutor.execute {
                    exclusionRegions[displayId].set(systemGestureExclusion)
                    exclusionRegionChangedCallback(displayId, exclusionRegions[displayId])
                }
            } else {
                if (context.getDisplayId() != displayId) {
                    return
                }
                mainExecutor.execute {
                    exclusionRegion.set(systemGestureExclusion)
                    exclusionRegionChangedCallback(displayId, exclusionRegion)
                }
            }
        }
    }

    init {
        shellInit.addInitCallback(::onShellInit, this)
    }

    private fun onShellInit() {
        if (DesktopExperienceFlags.ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY.isTrue()) {
            displayController.addDisplayWindowListener(this)
        } else {
            try {
                windowManager.registerSystemGestureExclusionListener(
                    exclusionListener,
                    context.displayId,
                )
            } catch (ex: RemoteException) {
                Slog.e(TAG, "Failed to register window manager callbacks for display: "
                        + context.displayId, ex)
            }
        }
    }


    /**
     * Returns the current exclusion region for the given display id.
     */
    fun getExclusionRegion(displayId: Int): Region {
        return exclusionRegions[displayId]
    }

    override fun onDisplayAdded(displayId: Int) {
        try {
            windowManager.registerSystemGestureExclusionListener(
                exclusionListener,
                displayId
            )
            exclusionRegions[displayId] = Region()
        } catch (ex: RemoteException) {
            Slog.e(TAG, "Failed to register window manager callbacks for display: $displayId", ex)
        }
    }

    override fun onDisplayRemoved(displayId: Int) {
        try {
            windowManager.unregisterSystemGestureExclusionListener(
                exclusionListener,
                displayId
            )
        } catch (ex: Exception) {
            when (ex) {
                is IllegalArgumentException, is RemoteException -> {
                    // Catching both IllegalArgumentException and RemoteException with Exception
                    Slog.e(TAG,
                        "Failed to unregister window manager callbacks for display: $displayId", ex)
                }
                else -> throw ex
            }
            exclusionRegions.remove(displayId)
        }
    }

    override fun toString(): String {
        return exclusionRegions.toString()
    }

    private companion object {
        private const val TAG = "WindowDecorGestureExclTracker"
    }
}
+41 −0
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
@@ -1132,6 +1133,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY)
    fun testGestureExclusionChanged_updatesDecorations() {
        val captor = argumentCaptor<ISystemGestureExclusionListener>()
        verify(mockWindowManager)
@@ -1156,6 +1158,41 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY)
    fun testGestureExclusionChanged_updatesDecorations_onlyOnItsDisplayId() {
        val gestureExclusionCaptor = argumentCaptor<ISystemGestureExclusionListener>()
        val displayListenerCaptor = argumentCaptor<DisplayController.OnDisplaysChangedListener>()
        verify(mockDisplayController).addDisplayWindowListener(displayListenerCaptor.capture())
        displayListenerCaptor.firstValue.onDisplayAdded(DEFAULT_DISPLAY)
        displayListenerCaptor.firstValue.onDisplayAdded(SECOND_DISPLAY)
        verify(mockWindowManager)
            .registerSystemGestureExclusionListener(gestureExclusionCaptor.capture(),
                eq(DEFAULT_DISPLAY))
        verify(mockWindowManager)
            .registerSystemGestureExclusionListener(gestureExclusionCaptor.capture(),
                eq(SECOND_DISPLAY))
        val task = createOpenTaskDecoration(
            windowingMode = WINDOWING_MODE_FREEFORM,
            displayId = DEFAULT_DISPLAY,
        )
        val task2 = createOpenTaskDecoration(
            windowingMode = WINDOWING_MODE_FREEFORM,
            displayId = SECOND_DISPLAY,
        )
        val newRegion = Region.obtain().apply {
            set(Rect(0, 0, 1600, 80))
        }

        gestureExclusionCaptor.firstValue.onSystemGestureExclusionChanged(SECOND_DISPLAY, newRegion,
            newRegion)
        testShellExecutor.flushAll()

        verify(task, never()).onExclusionRegionChanged(newRegion)
        verify(task2).onExclusionRegionChanged(newRegion)
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY)
    fun testGestureExclusionChanged_otherDisplay_skipsDecorationUpdate() {
        val captor = argumentCaptor<ISystemGestureExclusionListener>()
        verify(mockWindowManager)
@@ -1339,4 +1376,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY,
        )
    }

    private companion object {
        const val SECOND_DISPLAY = 2
    }
}