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

Commit 66ec497d authored by Yi Jiang's avatar Yi Jiang Committed by Android (Google) Code Review
Browse files

Merge "Sets system content priority based on interaction." into main

parents 50cc4f40 ace462e4
Loading
Loading
Loading
Loading
+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);
        }
    }
}
+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);
    }
}
+30 −0
Original line number Diff line number Diff line
@@ -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.
@@ -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;
    }
@@ -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.
     */
+23 −0
Original line number Diff line number Diff line
@@ -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.
@@ -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;
@@ -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();
            }
+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