Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +21 −29 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); } Loading @@ -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( Loading Loading @@ -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); } } Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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); } Loading @@ -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) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorationGestureExclusionTracker.kt 0 → 100644 +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" } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +41 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -1339,4 +1376,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY, ) } private companion object { const val SECOND_DISPLAY = 2 } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +21 −29 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); } Loading @@ -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( Loading Loading @@ -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); } } Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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); } Loading @@ -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) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorationGestureExclusionTracker.kt 0 → 100644 +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" } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +41 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -1339,4 +1376,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY, ) } private companion object { const val SECOND_DISPLAY = 2 } }