Loading services/core/java/com/android/server/wm/RenderingPrioritizationPolicy.java 0 → 100644 +64 −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.server.wm; import com.android.internal.annotations.VisibleForTesting; import java.util.List; /** * The policy that determines the rendering priority of a window. This policy primarily considers * user interaction history to assign priority, influencing the value of * {@link WindowState#mSystemContentPriority}, which gets sent to SurfaceFlinger to prioritize * rendering tasks. */ class RenderingPrioritizationPolicy { /** * A list of weights used to assign content priority to windows based on their * position in the interaction history. The first window in the history (most recent) * gets the first weight, the second gets the second weight, and so on. * Higher values typically indicate higher priority. * * <p> The last weight must be 0. So that the oldest window in the history get reset the * priority to 0. Effectively, weights {4, 2, 0} means giving priority 4 and 2 to the last two * interacted windows. */ @VisibleForTesting static final List<Integer> INTERACTION_WEIGHTS = List.of(4, 2, 0); /** * Updates the content priority of windows based on their recent interaction history. * * @param interactionHistory A list of {@link WindowState} objects representing * the windows in order of their interaction, from most recent to * least recent. */ static void updatePriorityByInteraction(List<WindowState> interactionHistory) { if (interactionHistory.isEmpty()) { return; } for (int i = 0; i < Math.min(interactionHistory.size(), INTERACTION_WEIGHTS.size()); i++) { WindowState window = interactionHistory.get(i); if (window == null) { continue; } window.mInteractionPriorityScore = INTERACTION_WEIGHTS.get(i); } } } services/core/java/com/android/server/wm/WindowInteractionTracker.java 0 → 100644 +86 −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.server.wm; import android.annotation.Nullable; import com.android.internal.util.Preconditions; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; /** * Keeps track of the recently interacted windows and stores their {@link WindowState} objects. * The tracker maintains a fixed-size history of window interactions. When the history * is full, the oldest interaction is removed to make space for new ones. * * <p> So far, the interaction is defined as the focus changes. When a new window gains focus, * regardless of the display it's on, it gets added to the interaction history, assuming that it's * recently interacted by the user. */ class WindowInteractionTracker { private final int mSize; /** * A queue holding the {@link WindowState} objects of the recently interacted windows. * The order is maintained from newest to oldest interaction. */ private final Deque<WindowState> mInteractionHistory; /** * Constructs a new WindowInteractionTracker with a specified history size. * * @param size The maximum number of interacted windows to track. */ WindowInteractionTracker(int size) { Preconditions.checkArgument(size > 0, "WindowInteractionTracker size must be greater than 0"); mSize = size; mInteractionHistory = new ArrayDeque<>(size); } /** * Records a window interaction by adding its {@link WindowState} to the history. * * @param windowState The {@link WindowState} of the window that was interacted with. */ void add(WindowState windowState) { if (mInteractionHistory.size() == mSize) { mInteractionHistory.removeLast(); } mInteractionHistory.addFirst(windowState); } /** * Returns the newest element in the history. If the history is empty, returns {@code null}. */ @Nullable WindowState peek() { return mInteractionHistory.peekFirst(); } /** * Returns a new {@link List} containing the {@link WindowState} objects of the recently * interacted windows, sorted from most recent to oldest. The list will be empty if no * interactions have been recorded. */ List<WindowState> getRecentlyInteractedWindows() { return new ArrayList<>(mInteractionHistory); } } services/core/java/com/android/server/wm/WindowManagerService.java +30 −0 Original line number Diff line number Diff line Loading @@ -503,6 +503,11 @@ public class WindowManagerService extends IWindowManager.Stub int mVr2dDisplayId = INVALID_DISPLAY; boolean mVrModeEnabled = false; private static final int WINDOW_INTERACTION_HISTORY_SIZE = 3; WindowInteractionTracker mInteractionTracker = new WindowInteractionTracker( WINDOW_INTERACTION_HISTORY_SIZE); /** * Tracks a map of input tokens to info that is used to decide whether to intercept * a key event. Loading Loading @@ -6769,6 +6774,12 @@ public class WindowManagerService extends IWindowManager.Stub boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus"); boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows); if (changed && Flags.systemContentPriority()) { DisplayContent dc = mRoot.getTopFocusedDisplayContent(); if (dc != null && updateWindowInteractionHistoryByFocus(dc.mCurrentFocus)) { scheduleAnimationLocked(); } } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return changed; } Loading Loading @@ -9494,6 +9505,25 @@ public class WindowManagerService extends IWindowManager.Stub return new WindowContainerInfo(targetTask.effectiveUid, taskWindowContainerToken); } /** * Update the interaction history based on focus changes if needed. It assumes that the new * focused window is recently interacted by the user. It returns {@code true} if the history is * updated, otherwise returns {@code false}. */ private boolean updateWindowInteractionHistoryByFocus(WindowState focusedWindow) { if (focusedWindow == null) { return false; } WindowState lastInteractedWindow = mInteractionTracker.peek(); if (lastInteractedWindow == null || lastInteractedWindow != focusedWindow) { mInteractionTracker.add(focusedWindow); RenderingPrioritizationPolicy.updatePriorityByInteraction( mInteractionTracker.getRecentlyInteractedWindows()); return true; } return false; } /** * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY. */ Loading services/core/java/com/android/server/wm/WindowState.java +23 −0 Original line number Diff line number Diff line Loading @@ -747,6 +747,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET; /** * A value representing the importance of the window from the system perspective. A higher * priority value means the window will get preferred access to the limited resource in * rendering. */ int mSystemContentPriority = 0; /** * A score contributing to the {@link mSystemContentPriority}. This score is calculated based on * the recent user interaction history. The newer interacted window will typically get a higher * score. */ int mInteractionPriorityScore = 0; /** * This is the frame rate which is passed to SurfaceFlinger if the window set a * preferredDisplayModeId or is part of the high refresh rate deny list. Loading Loading @@ -5190,6 +5204,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } private void updateSystemContentPriorityIfNeeded() { int newPriority = mInteractionPriorityScore; if (newPriority != mSystemContentPriority) { getPendingTransaction().setSystemContentPriority(mSurfaceControl, newPriority); mSystemContentPriority = newPriority; } } @Override void prepareSurfaces() { mIsDimming = false; Loading @@ -5197,6 +5219,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP updateSurfacePositionNonOrganized(); // Send information to SurfaceFlinger about the priority of the current window. updateFrameRateSelectionPriorityIfNeeded(); updateSystemContentPriorityIfNeeded(); if (isVisibleRequested()) { updateScaleIfNeeded(); } Loading services/tests/wmtests/src/com/android/server/wm/RenderingPrioritizationPolicyTests.java 0 → 100644 +67 −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.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; @Presubmit @RunWith(WindowTestRunner.class) public class RenderingPrioritizationPolicyTests extends WindowTestsBase { @Test public void testUpdatePriorityByInteraction_normalCase() { if (RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.isEmpty()) { return; } List<WindowState> interactionHistory = new ArrayList<>(); for (int i = 0; i < RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.size(); i++) { interactionHistory.add(newWindowBuilder("window " + i, TYPE_APPLICATION).build()); } RenderingPrioritizationPolicy.updatePriorityByInteraction(interactionHistory); for (int i = 0; i < RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.size(); i++) { assertEquals(RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.get(i).intValue(), interactionHistory.get(i).mInteractionPriorityScore); } } @Test public void testUpdatePriorityByInteraction_historyLessThanWeights() { if (RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.size() < 2) { return; } WindowState window1 = newWindowBuilder("window 1", TYPE_APPLICATION).build(); List<WindowState> interactionHistory = List.of(window1); RenderingPrioritizationPolicy.updatePriorityByInteraction(interactionHistory); assertEquals(RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.get(0).intValue(), window1.mInteractionPriorityScore); } } Loading
services/core/java/com/android/server/wm/RenderingPrioritizationPolicy.java 0 → 100644 +64 −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.server.wm; import com.android.internal.annotations.VisibleForTesting; import java.util.List; /** * The policy that determines the rendering priority of a window. This policy primarily considers * user interaction history to assign priority, influencing the value of * {@link WindowState#mSystemContentPriority}, which gets sent to SurfaceFlinger to prioritize * rendering tasks. */ class RenderingPrioritizationPolicy { /** * A list of weights used to assign content priority to windows based on their * position in the interaction history. The first window in the history (most recent) * gets the first weight, the second gets the second weight, and so on. * Higher values typically indicate higher priority. * * <p> The last weight must be 0. So that the oldest window in the history get reset the * priority to 0. Effectively, weights {4, 2, 0} means giving priority 4 and 2 to the last two * interacted windows. */ @VisibleForTesting static final List<Integer> INTERACTION_WEIGHTS = List.of(4, 2, 0); /** * Updates the content priority of windows based on their recent interaction history. * * @param interactionHistory A list of {@link WindowState} objects representing * the windows in order of their interaction, from most recent to * least recent. */ static void updatePriorityByInteraction(List<WindowState> interactionHistory) { if (interactionHistory.isEmpty()) { return; } for (int i = 0; i < Math.min(interactionHistory.size(), INTERACTION_WEIGHTS.size()); i++) { WindowState window = interactionHistory.get(i); if (window == null) { continue; } window.mInteractionPriorityScore = INTERACTION_WEIGHTS.get(i); } } }
services/core/java/com/android/server/wm/WindowInteractionTracker.java 0 → 100644 +86 −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.server.wm; import android.annotation.Nullable; import com.android.internal.util.Preconditions; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; /** * Keeps track of the recently interacted windows and stores their {@link WindowState} objects. * The tracker maintains a fixed-size history of window interactions. When the history * is full, the oldest interaction is removed to make space for new ones. * * <p> So far, the interaction is defined as the focus changes. When a new window gains focus, * regardless of the display it's on, it gets added to the interaction history, assuming that it's * recently interacted by the user. */ class WindowInteractionTracker { private final int mSize; /** * A queue holding the {@link WindowState} objects of the recently interacted windows. * The order is maintained from newest to oldest interaction. */ private final Deque<WindowState> mInteractionHistory; /** * Constructs a new WindowInteractionTracker with a specified history size. * * @param size The maximum number of interacted windows to track. */ WindowInteractionTracker(int size) { Preconditions.checkArgument(size > 0, "WindowInteractionTracker size must be greater than 0"); mSize = size; mInteractionHistory = new ArrayDeque<>(size); } /** * Records a window interaction by adding its {@link WindowState} to the history. * * @param windowState The {@link WindowState} of the window that was interacted with. */ void add(WindowState windowState) { if (mInteractionHistory.size() == mSize) { mInteractionHistory.removeLast(); } mInteractionHistory.addFirst(windowState); } /** * Returns the newest element in the history. If the history is empty, returns {@code null}. */ @Nullable WindowState peek() { return mInteractionHistory.peekFirst(); } /** * Returns a new {@link List} containing the {@link WindowState} objects of the recently * interacted windows, sorted from most recent to oldest. The list will be empty if no * interactions have been recorded. */ List<WindowState> getRecentlyInteractedWindows() { return new ArrayList<>(mInteractionHistory); } }
services/core/java/com/android/server/wm/WindowManagerService.java +30 −0 Original line number Diff line number Diff line Loading @@ -503,6 +503,11 @@ public class WindowManagerService extends IWindowManager.Stub int mVr2dDisplayId = INVALID_DISPLAY; boolean mVrModeEnabled = false; private static final int WINDOW_INTERACTION_HISTORY_SIZE = 3; WindowInteractionTracker mInteractionTracker = new WindowInteractionTracker( WINDOW_INTERACTION_HISTORY_SIZE); /** * Tracks a map of input tokens to info that is used to decide whether to intercept * a key event. Loading Loading @@ -6769,6 +6774,12 @@ public class WindowManagerService extends IWindowManager.Stub boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus"); boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows); if (changed && Flags.systemContentPriority()) { DisplayContent dc = mRoot.getTopFocusedDisplayContent(); if (dc != null && updateWindowInteractionHistoryByFocus(dc.mCurrentFocus)) { scheduleAnimationLocked(); } } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return changed; } Loading Loading @@ -9494,6 +9505,25 @@ public class WindowManagerService extends IWindowManager.Stub return new WindowContainerInfo(targetTask.effectiveUid, taskWindowContainerToken); } /** * Update the interaction history based on focus changes if needed. It assumes that the new * focused window is recently interacted by the user. It returns {@code true} if the history is * updated, otherwise returns {@code false}. */ private boolean updateWindowInteractionHistoryByFocus(WindowState focusedWindow) { if (focusedWindow == null) { return false; } WindowState lastInteractedWindow = mInteractionTracker.peek(); if (lastInteractedWindow == null || lastInteractedWindow != focusedWindow) { mInteractionTracker.add(focusedWindow); RenderingPrioritizationPolicy.updatePriorityByInteraction( mInteractionTracker.getRecentlyInteractedWindows()); return true; } return false; } /** * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY. */ Loading
services/core/java/com/android/server/wm/WindowState.java +23 −0 Original line number Diff line number Diff line Loading @@ -747,6 +747,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET; /** * A value representing the importance of the window from the system perspective. A higher * priority value means the window will get preferred access to the limited resource in * rendering. */ int mSystemContentPriority = 0; /** * A score contributing to the {@link mSystemContentPriority}. This score is calculated based on * the recent user interaction history. The newer interacted window will typically get a higher * score. */ int mInteractionPriorityScore = 0; /** * This is the frame rate which is passed to SurfaceFlinger if the window set a * preferredDisplayModeId or is part of the high refresh rate deny list. Loading Loading @@ -5190,6 +5204,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } private void updateSystemContentPriorityIfNeeded() { int newPriority = mInteractionPriorityScore; if (newPriority != mSystemContentPriority) { getPendingTransaction().setSystemContentPriority(mSurfaceControl, newPriority); mSystemContentPriority = newPriority; } } @Override void prepareSurfaces() { mIsDimming = false; Loading @@ -5197,6 +5219,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP updateSurfacePositionNonOrganized(); // Send information to SurfaceFlinger about the priority of the current window. updateFrameRateSelectionPriorityIfNeeded(); updateSystemContentPriorityIfNeeded(); if (isVisibleRequested()) { updateScaleIfNeeded(); } Loading
services/tests/wmtests/src/com/android/server/wm/RenderingPrioritizationPolicyTests.java 0 → 100644 +67 −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.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; @Presubmit @RunWith(WindowTestRunner.class) public class RenderingPrioritizationPolicyTests extends WindowTestsBase { @Test public void testUpdatePriorityByInteraction_normalCase() { if (RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.isEmpty()) { return; } List<WindowState> interactionHistory = new ArrayList<>(); for (int i = 0; i < RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.size(); i++) { interactionHistory.add(newWindowBuilder("window " + i, TYPE_APPLICATION).build()); } RenderingPrioritizationPolicy.updatePriorityByInteraction(interactionHistory); for (int i = 0; i < RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.size(); i++) { assertEquals(RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.get(i).intValue(), interactionHistory.get(i).mInteractionPriorityScore); } } @Test public void testUpdatePriorityByInteraction_historyLessThanWeights() { if (RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.size() < 2) { return; } WindowState window1 = newWindowBuilder("window 1", TYPE_APPLICATION).build(); List<WindowState> interactionHistory = List.of(window1); RenderingPrioritizationPolicy.updatePriorityByInteraction(interactionHistory); assertEquals(RenderingPrioritizationPolicy.INTERACTION_WEIGHTS.get(0).intValue(), window1.mInteractionPriorityScore); } }