From c823bd02b369781fb2038d0a10109a3a3a1a0de8 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 27 Sep 2023 21:49:31 +0000 Subject: Add mechanism to help manage multiple system perf hints from SysUI - Users can request high perf sessions for transitions and interactions with specific hints for SurfaceFlinger or ADPF. - Add injectable hinter on the Shell side for use within shell features Bug: 300019131 Bug: 297385713 Test: SystemPerformanceHinterTests Change-Id: I869689fc88106be9c55d8dd3243173f00749fc07 --- .../android/window/SystemPerformanceHinter.java | 327 +++++++++++++++++ .../window/SystemPerformanceHinterTests.java | 389 +++++++++++++++++++++ .../wm/shell/RootTaskDisplayAreaOrganizer.java | 14 + .../android/wm/shell/dagger/WMShellBaseModule.java | 13 + .../wm/shell/performance/PerfHintController.kt | 56 +++ 5 files changed, 799 insertions(+) create mode 100644 core/java/android/window/SystemPerformanceHinter.java create mode 100644 core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java new file mode 100644 index 000000000000..b2c977bb1e57 --- /dev/null +++ b/core/java/android/window/SystemPerformanceHinter.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2023 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 android.window; + +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.PerformanceHintManager; +import android.os.Trace; +import android.util.Log; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Random; +import java.util.function.Supplier; + +/** + * A helper class to manage performance related hints for a process. This helper is used for both + * long-lived and transient hints. + * + * @hide + */ +public class SystemPerformanceHinter { + private static final String TAG = "SystemPerformanceHinter"; + + // Change app and SF wakeup times to allow sf more time to composite a frame + public static final int HINT_SF_EARLY_WAKEUP = 1 << 0; + // Force max refresh rate + public static final int HINT_SF_FRAME_RATE = 1 << 1; + // Boost CPU & GPU clocks + public static final int HINT_ADPF = 1 << 2; + // Convenience constant for SF only flags + public static final int HINT_SF = HINT_SF_EARLY_WAKEUP | HINT_SF_FRAME_RATE; + // Convenience constant for all the flags + public static final int HINT_ALL = HINT_SF_EARLY_WAKEUP | HINT_SF_FRAME_RATE | HINT_ADPF; + + // Hints that are applied per-display and require a display root surface + private static final int HINT_PER_DISPLAY = HINT_SF_FRAME_RATE; + // Hints that are global (not per-display) + private static final int HINT_GLOBAL = HINT_SF_EARLY_WAKEUP | HINT_ADPF; + + @IntDef(prefix = {"HINT_"}, value = { + HINT_SF_EARLY_WAKEUP, + HINT_SF_FRAME_RATE, + HINT_ADPF, + }) + private @interface HintFlags {} + + /** + * A provider for the root to apply SurfaceControl hints which will be inherited by all children + * of that root. + * @hide + */ + public interface DisplayRootProvider { + /** + * @return the SurfaceControl to apply hints for the given displayId. + */ + @Nullable SurfaceControl getRootForDisplay(int displayId); + } + + /** + * A session where high performance is needed. + * @hide + */ + public class HighPerfSession implements AutoCloseable { + private final @HintFlags int hintFlags; + private final String reason; + private final int displayId; + private final int traceCookie; + + protected HighPerfSession(@HintFlags int hintFlags, int displayId, @NonNull String reason) { + this.hintFlags = hintFlags; + this.reason = reason; + this.displayId = displayId; + this.traceCookie = new Random().nextInt(); + if (hintFlags != 0) { + startSession(this); + } + } + + /** + * Closes this session. + */ + public void close() { + if (hintFlags != 0) { + endSession(this); + } + } + + public void finalize() { + close(); + } + } + + /** + * A no-op implementation of a session. + */ + private class NoOpHighPerfSession extends HighPerfSession { + public NoOpHighPerfSession() { + super(0 /* hintFlags */, -1 /* displayId */, ""); + } + + public void close() { + // Do nothing + } + } + + // The active sessions + private final ArrayList mActiveSessions = new ArrayList<>(); + private final SurfaceControl.Transaction mTransaction; + private final PerformanceHintManager mPerfHintManager; + private @Nullable PerformanceHintManager.Session mAdpfSession; + private @Nullable DisplayRootProvider mDisplayRootProvider; + + + /** + * Constructor for the hinter. + * @hide + */ + public SystemPerformanceHinter(@NonNull Context context, + @Nullable DisplayRootProvider displayRootProvider) { + this(context, displayRootProvider, null /* transactionSupplier */); + } + + /** + * Constructor for the hinter. + * @hide + */ + @VisibleForTesting + public SystemPerformanceHinter(@NonNull Context context, + @Nullable DisplayRootProvider displayRootProvider, + @Nullable Supplier transactionSupplier) { + mDisplayRootProvider = displayRootProvider; + mPerfHintManager = context.getSystemService(PerformanceHintManager.class); + mTransaction = transactionSupplier != null + ? transactionSupplier.get() + : new SurfaceControl.Transaction(); + } + + /** + * Sets the current ADPF session, required if you are using HINT_ADPF. It is the responsibility + * of the caller to manage up the ADPF session. + * @hide + */ + public void setAdpfSession(PerformanceHintManager.Session adpfSession) { + mAdpfSession = adpfSession; + } + + /** + * Starts a session that requires high performance. + * @hide + */ + public HighPerfSession startSession(@HintFlags int hintFlags, int displayId, + @NonNull String reason) { + if (mDisplayRootProvider == null && (hintFlags & HINT_SF_FRAME_RATE) != 0) { + throw new IllegalArgumentException( + "Using SF frame rate hints requires a valid display root provider"); + } + if (mAdpfSession == null && (hintFlags & HINT_ADPF) != 0) { + throw new IllegalArgumentException("Using ADPF hints requires an ADPF session"); + } + if ((hintFlags & HINT_PER_DISPLAY) != 0) { + if (mDisplayRootProvider.getRootForDisplay(displayId) == null) { + // Just log an error and return early if there is no root as there could be races + // between when a display root is removed and when a hint session is requested + Log.v(TAG, "No display root for displayId=" + displayId); + Trace.instant(TRACE_TAG_WINDOW_MANAGER, "PerfHint-NoDisplayRoot: " + displayId); + return new NoOpHighPerfSession(); + } + } + return new HighPerfSession(hintFlags, displayId, reason); + } + + /** + * Starts a session that requires high performance. + */ + private void startSession(HighPerfSession session) { + int oldGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL); + int oldPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY, + session.displayId); + mActiveSessions.add(session); + int newGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL); + int newPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY, + session.displayId); + + boolean transactionChanged = false; + // Per-display flags + if (nowEnabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) { + mTransaction.setFrameRateSelectionStrategy( + mDisplayRootProvider.getRootForDisplay(session.displayId), + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + transactionChanged = true; + Trace.beginAsyncSection("PerfHint-framerate-" + session.reason, session.traceCookie); + } + + // Global flags + if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) { + mTransaction.setEarlyWakeupStart(); + transactionChanged = true; + Trace.beginAsyncSection("PerfHint-early_wakeup-" + session.reason, session.traceCookie); + } + if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) { + mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_UP); + Trace.beginAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie); + } + if (transactionChanged) { + mTransaction.apply(); + } + } + + /** + * Ends a session that requires high performance. + */ + private void endSession(HighPerfSession session) { + int oldGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL); + int oldPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY, + session.displayId); + mActiveSessions.remove(session); + int newGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL); + int newPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY, + session.displayId); + + boolean transactionChanged = false; + // Per-display flags + if (nowDisabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) { + mTransaction.setFrameRateSelectionStrategy( + mDisplayRootProvider.getRootForDisplay(session.displayId), + FRAME_RATE_SELECTION_STRATEGY_SELF); + transactionChanged = true; + Trace.endAsyncSection("PerfHint-framerate-" + session.reason, session.traceCookie); + } + + // Global flags + if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) { + mTransaction.setEarlyWakeupEnd(); + transactionChanged = true; + Trace.endAsyncSection("PerfHint-early_wakeup" + session.reason, session.traceCookie); + } + if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) { + mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET); + Trace.endAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie); + } + if (transactionChanged) { + mTransaction.apply(); + } + } + + /** + * Checks if checkFlags was previously not set and is now set. + */ + private boolean nowEnabled(@HintFlags int oldFlags, @HintFlags int newFlags, + @HintFlags int checkFlags) { + return (oldFlags & checkFlags) == 0 && (newFlags & checkFlags) != 0; + } + + /** + * Checks if checkFlags was previously set and is now not set. + */ + private boolean nowDisabled(@HintFlags int oldFlags, @HintFlags int newFlags, + @HintFlags int checkFlags) { + return (oldFlags & checkFlags) != 0 && (newFlags & checkFlags) == 0; + } + + /** + * @return the combined hint flags for all active sessions, filtered by {@param filterFlags}. + */ + private @HintFlags int calculateActiveHintFlags(@HintFlags int filterFlags) { + int flags = 0; + for (int i = 0; i < mActiveSessions.size(); i++) { + flags |= mActiveSessions.get(i).hintFlags & filterFlags; + } + return flags; + } + + /** + * @return the combined hint flags for all active sessions for a given display, filtered by + * {@param filterFlags}. + */ + private @HintFlags int calculateActiveHintFlagsForDisplay(@HintFlags int filterFlags, + int displayId) { + int flags = 0; + for (int i = 0; i < mActiveSessions.size(); i++) { + final HighPerfSession session = mActiveSessions.get(i); + if (session.displayId == displayId) { + flags |= mActiveSessions.get(i).hintFlags & filterFlags; + } + } + return flags; + } + + /** + * Dumps the existing sessions. + */ + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG + ":"); + pw.println(innerPrefix + "Active sessions (" + mActiveSessions.size() + "):"); + for (int i = 0; i < mActiveSessions.size(); i++) { + final HighPerfSession s = mActiveSessions.get(i); + pw.println(innerPrefix + " reason=" + s.reason + + " flags=" + s.hintFlags + + " display=" + s.displayId); + } + } +} diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java new file mode 100644 index 000000000000..25f5819fb671 --- /dev/null +++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2023 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 android.window; + +import static android.os.PerformanceHintManager.Session.CPU_LOAD_RESET; +import static android.os.PerformanceHintManager.Session.CPU_LOAD_UP; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF; +import static android.window.SystemPerformanceHinter.HINT_ADPF; +import static android.window.SystemPerformanceHinter.HINT_ALL; +import static android.window.SystemPerformanceHinter.HINT_SF_EARLY_WAKEUP; +import static android.window.SystemPerformanceHinter.HINT_SF_FRAME_RATE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +import android.os.PerformanceHintManager; +import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashMap; + +/** + * Class for testing {@link android.window.SystemPerformanceHinter}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:android.window.SystemPerformanceHinterTests + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public class SystemPerformanceHinterTests { + + private static final int DEFAULT_DISPLAY_ID = android.view.Display.DEFAULT_DISPLAY; + private static final int SECONDARY_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1; + private static final int NO_ROOT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2; + private static final String TEST_REASON = "test"; + private static final String TEST_OTHER_REASON = "test_other"; + + private SystemPerformanceHinter mHinter; + private SystemPerformanceHinterTests.RootProvider mRootProvider; + + @Mock + private PerformanceHintManager.Session mAdpfSession; + + @Mock + private SurfaceControl.Transaction mTransaction; + private SurfaceControl mDefaultDisplayRoot; + private SurfaceControl mSecondaryDisplayRoot; + + + @Before + public void setUpOnce() { + MockitoAnnotations.initMocks(this); + + mDefaultDisplayRoot = new SurfaceControl(); + mSecondaryDisplayRoot = new SurfaceControl(); + mRootProvider = new SystemPerformanceHinterTests.RootProvider(); + mRootProvider.put(DEFAULT_DISPLAY_ID, mDefaultDisplayRoot); + mRootProvider.put(SECONDARY_DISPLAY_ID, mSecondaryDisplayRoot); + + mHinter = new SystemPerformanceHinter( + InstrumentationRegistry.getInstrumentation().getTargetContext(), + mRootProvider, + () -> mTransaction); + } + + @Test + public void testADPFHintWithoutADPFSession_expectThrows() { + assertThrows("Expected exception without ADPF session", + IllegalArgumentException.class, () -> { + mHinter.startSession(HINT_ADPF, DEFAULT_DISPLAY_ID, TEST_REASON); + }); + } + + @Test + public void testSFVRRHintWithoutDisplayRootProvider_expectThrows() { + assertThrows("Expected exception without display root", + IllegalArgumentException.class, () -> { + SystemPerformanceHinter hinter = new SystemPerformanceHinter( + InstrumentationRegistry.getInstrumentation().getTargetContext(), + null /* displayRootProvider */, + () -> mTransaction); + hinter.startSession(HINT_SF_FRAME_RATE, DEFAULT_DISPLAY_ID, TEST_REASON); + }); + } + + @Test + public void testGetDefaultDisplayRoot() { + mHinter.startSession(HINT_SF_FRAME_RATE, DEFAULT_DISPLAY_ID, TEST_REASON); + assertEquals(DEFAULT_DISPLAY_ID, mRootProvider.lastRequestedDisplayId); + assertEquals(mDefaultDisplayRoot, mRootProvider.lastReturnedRoot); + } + + @Test + public void testGetSecondaryDisplayRoot() { + mHinter.startSession(HINT_SF_FRAME_RATE, SECONDARY_DISPLAY_ID, TEST_REASON); + assertEquals(SECONDARY_DISPLAY_ID, mRootProvider.lastRequestedDisplayId); + assertEquals(mSecondaryDisplayRoot, mRootProvider.lastReturnedRoot); + } + + @Test + public void testOnlyCacheDisplayRoots() { + mHinter.startSession(HINT_SF_FRAME_RATE, DEFAULT_DISPLAY_ID, TEST_REASON); + mHinter.startSession(HINT_SF_FRAME_RATE, DEFAULT_DISPLAY_ID, TEST_REASON); + mHinter.startSession(HINT_SF_FRAME_RATE, DEFAULT_DISPLAY_ID, TEST_REASON); + assertEquals(DEFAULT_DISPLAY_ID, mRootProvider.lastRequestedDisplayId); + assertEquals(mDefaultDisplayRoot, mRootProvider.lastReturnedRoot); + } + + @Test + public void testVRRHint() { + final SystemPerformanceHinter.HighPerfSession session = + mHinter.startSession(HINT_SF_FRAME_RATE, DEFAULT_DISPLAY_ID, TEST_REASON); + + // Expect it to get a display root + assertEquals(DEFAULT_DISPLAY_ID, mRootProvider.lastRequestedDisplayId); + assertEquals(mDefaultDisplayRoot, mRootProvider.lastReturnedRoot); + + // Verify we call SF + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); + verify(mTransaction).apply(); + } + + @Test + public void testVRRHintCloseSession() { + final SystemPerformanceHinter.HighPerfSession session = + mHinter.startSession(HINT_SF_FRAME_RATE, DEFAULT_DISPLAY_ID, TEST_REASON); + reset(mTransaction); + session.close(); + + // Verify we call SF + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).apply(); + } + + @Test + public void testEarlyWakeupHint() { + final SystemPerformanceHinter.HighPerfSession session = + mHinter.startSession(HINT_SF_EARLY_WAKEUP, DEFAULT_DISPLAY_ID, TEST_REASON); + + // Expect that this hint does not require a display root + assertEquals(0, mRootProvider.getCount); + + // Verify we call SF + verify(mTransaction).setEarlyWakeupStart(); + verify(mTransaction).apply(); + } + + @Test + public void testEarlyWakeupHintCloseSession() { + final SystemPerformanceHinter.HighPerfSession session = + mHinter.startSession(HINT_SF_EARLY_WAKEUP, DEFAULT_DISPLAY_ID, TEST_REASON); + reset(mTransaction); + session.close(); + + // Verify we call SF + verify(mTransaction).setEarlyWakeupEnd(); + verify(mTransaction).apply(); + } + + @Test + public void testADPFHint() { + mHinter.setAdpfSession(mAdpfSession); + final SystemPerformanceHinter.HighPerfSession session = + mHinter.startSession(HINT_ADPF, DEFAULT_DISPLAY_ID, TEST_REASON); + + // Expect that this hint does not require a display root + assertEquals(0, mRootProvider.getCount); + + // Verify we call the perf manager + verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); + } + + @Test + public void testADPFHintCloseSession() { + mHinter.setAdpfSession(mAdpfSession); + final SystemPerformanceHinter.HighPerfSession session = + mHinter.startSession(HINT_ADPF, DEFAULT_DISPLAY_ID, TEST_REASON); + reset(mTransaction); + session.close(); + + // Verify we call the perf manager + verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); + } + + @Test + public void testAllHints() { + mHinter.setAdpfSession(mAdpfSession); + final SystemPerformanceHinter.HighPerfSession session = + mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_REASON); + + // Expect it to get a display root + assertEquals(DEFAULT_DISPLAY_ID, mRootProvider.lastRequestedDisplayId); + assertEquals(mDefaultDisplayRoot, mRootProvider.lastReturnedRoot); + + // Verify we call SF and perf manager + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); + verify(mTransaction).setEarlyWakeupStart(); + verify(mTransaction).apply(); + verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); + } + + @Test + public void testAllHintsCloseSession() { + mHinter.setAdpfSession(mAdpfSession); + final SystemPerformanceHinter.HighPerfSession session = + mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_REASON); + reset(mTransaction); + session.close(); + + // Verify we call SF and perf manager to clean up + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).setEarlyWakeupEnd(); + verify(mTransaction).apply(); + verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); + } + + @Test + public void testAutocloseable() { + mHinter.setAdpfSession(mAdpfSession); + try (final SystemPerformanceHinter.HighPerfSession session = + mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_REASON)) { + reset(mTransaction); + reset(mAdpfSession); + } finally { + // Verify we call SF and perf manager to clean up + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).setEarlyWakeupEnd(); + verify(mTransaction).apply(); + verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); + } + } + + @Test + public void testOverlappingHintsOnSameDisplay() { + mHinter.setAdpfSession(mAdpfSession); + final SystemPerformanceHinter.HighPerfSession session1 = + mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_REASON); + // Verify we call SF and perf manager + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); + verify(mTransaction).setEarlyWakeupStart(); + verify(mTransaction).apply(); + verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); + reset(mTransaction); + reset(mAdpfSession); + + final SystemPerformanceHinter.HighPerfSession session2 = + mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON); + // Verify we never call SF and perf manager since session1 is already running + verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); + verify(mTransaction, never()).setEarlyWakeupEnd(); + verify(mTransaction, never()).apply(); + verify(mAdpfSession, never()).sendHint(anyInt()); + + session2.close(); + // Verify we have not cleaned up because session1 is still running + verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); + verify(mTransaction, never()).setEarlyWakeupEnd(); + verify(mTransaction, never()).apply(); + verify(mAdpfSession, never()).sendHint(anyInt()); + + session1.close(); + // Verify we call SF and perf manager to clean up + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).setEarlyWakeupEnd(); + verify(mTransaction).apply(); + verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); + } + + @Test + public void testOverlappingHintsOnDifferentDisplays() { + mHinter.setAdpfSession(mAdpfSession); + final SystemPerformanceHinter.HighPerfSession session1 = + mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_REASON); + + // Verify we call SF and perf manager + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); + verify(mTransaction).setEarlyWakeupStart(); + verify(mTransaction).apply(); + verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); + reset(mTransaction); + reset(mAdpfSession); + + // Create a new session and ensure only per-display flags are updated and not global flags + final SystemPerformanceHinter.HighPerfSession session2 = + mHinter.startSession(HINT_ALL, SECONDARY_DISPLAY_ID, TEST_REASON); + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mSecondaryDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN)); + verify(mTransaction, never()).setEarlyWakeupStart(); + verify(mTransaction).apply(); + verify(mAdpfSession, never()).sendHint(anyInt()); + reset(mTransaction); + reset(mAdpfSession); + + // Close the primary display session and ensure it doesn't affect secondary display flags + // or any global flags still requested by the secondary display session + session1.close(); + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction, never()).setFrameRateSelectionStrategy( + eq(mSecondaryDisplayRoot), + anyInt()); + verify(mTransaction, never()).setEarlyWakeupEnd(); + verify(mTransaction).apply(); + verify(mAdpfSession, never()).sendHint(anyInt()); + reset(mTransaction); + reset(mAdpfSession); + + // Close all sessions, ensure it cleans up all the flags + session2.close(); + verify(mTransaction, never()).setFrameRateSelectionStrategy( + eq(mDefaultDisplayRoot), + anyInt()); + verify(mTransaction).setFrameRateSelectionStrategy( + eq(mSecondaryDisplayRoot), + eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + verify(mTransaction).setEarlyWakeupEnd(); + verify(mTransaction).apply(); + verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); + } + + private class RootProvider implements SystemPerformanceHinter.DisplayRootProvider { + private HashMap mRoots = new HashMap<>(); + public int getCount; + public int lastRequestedDisplayId = -1; + public SurfaceControl lastReturnedRoot; + + void put(int displayId, SurfaceControl root) { + mRoots.put(displayId, root); + } + + @NonNull + @Override + public SurfaceControl getRootForDisplay(int displayId) { + getCount++; + lastRequestedDisplayId = displayId; + lastReturnedRoot = mRoots.get(displayId); + return lastReturnedRoot; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index 410ae78dba1b..38550b405c0e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -32,6 +32,7 @@ import android.view.SurfaceControl; import android.window.DisplayAreaAppearedInfo; import android.window.DisplayAreaInfo; import android.window.DisplayAreaOrganizer; +import android.window.SystemPerformanceHinter; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -58,6 +59,14 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { /** {@link DisplayAreaContext} list, which is mapped by display IDs. */ private final SparseArray mDisplayAreaContexts = new SparseArray<>(); + private final SystemPerformanceHinter.DisplayRootProvider mPerfRootProvider = + new SystemPerformanceHinter.DisplayRootProvider() { + @Override + public SurfaceControl getRootForDisplay(int displayId) { + return mLeashes.get(displayId); + } + }; + private final Context mContext; public RootTaskDisplayAreaOrganizer(Executor executor, Context context) { @@ -229,6 +238,11 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { return mDisplayAreaContexts.get(displayId); } + @NonNull + public SystemPerformanceHinter.DisplayRootProvider getPerformanceRootProvider() { + return mPerfRootProvider; + } + public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 998cd5d08c72..e6d3abcc6e1d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.SystemProperties; import android.view.IWindowManager; import android.view.accessibility.AccessibilityManager; +import android.window.SystemPerformanceHinter; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; @@ -85,6 +86,7 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.keyguard.KeyguardTransitions; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.performance.PerfHintController; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; @@ -296,6 +298,17 @@ public abstract class WMShellBaseModule { return new LaunchAdjacentController(syncQueue); } + @WMSingleton + @Provides + static SystemPerformanceHinter provideSystemPerformanceHinter(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + RootTaskDisplayAreaOrganizer rootTdaOrganizer) { + final PerfHintController perfHintController = + new PerfHintController(context, shellInit, shellCommandHandler, rootTdaOrganizer); + return perfHintController.getHinter(); + } + // // Back animation // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt new file mode 100644 index 000000000000..f7977f88006e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 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.performance + +import android.content.Context +import android.os.PerformanceHintManager +import android.os.Process +import android.window.SystemPerformanceHinter +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.sysui.ShellCommandHandler +import com.android.wm.shell.sysui.ShellInit +import java.io.PrintWriter +import java.util.concurrent.TimeUnit + +/** + * Manages the performance hints to the system. + */ +class PerfHintController(private val mContext: Context, + shellInit: ShellInit, + private val mShellCommandHandler: ShellCommandHandler, + rootTdaOrganizer: RootTaskDisplayAreaOrganizer) { + + // The system perf hinter + val hinter: SystemPerformanceHinter + + init { + hinter = SystemPerformanceHinter(mContext, + rootTdaOrganizer.performanceRootProvider) + shellInit.addInitCallback(this::onInit, this) + } + + private fun onInit() { + mShellCommandHandler.addDumpCallback(this::dump, this) + val perfHintMgr = mContext.getSystemService(PerformanceHintManager::class.java) + val adpfSession = perfHintMgr!!.createHintSession(intArrayOf(Process.myTid()), + TimeUnit.SECONDS.toNanos(1)) + hinter.setAdpfSession(adpfSession) + } + + fun dump(pw: PrintWriter, prefix: String?) { + hinter.dump(pw, prefix) + } +} -- cgit v1.2.3-59-g8ed1b