summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Winson Chung <winsonc@google.com> 2023-10-05 03:28:15 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-10-05 03:28:15 +0000
commit67d47ab82b2d491607694df97a4c3a63a0f6ec00 (patch)
treeb2b75b6cf0a0f4751eb25ff1ae70574f156e104c
parent84a42c8fa4e09ccea16aefb9dfee1e587142a561 (diff)
parentc823bd02b369781fb2038d0a10109a3a3a1a0de8 (diff)
Merge "Add mechanism to help manage multiple system perf hints from SysUI" into main
-rw-r--r--core/java/android/window/SystemPerformanceHinter.java327
-rw-r--r--core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java389
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt56
5 files changed, 799 insertions, 0 deletions
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<HighPerfSession> 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<SurfaceControl.Transaction> 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<Integer, SurfaceControl> 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<DisplayAreaContext> 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)
+ }
+}