diff options
8 files changed, 312 insertions, 15 deletions
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 45106f54cb9f..6670b4321c2a 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -93,6 +93,10 @@ public class DisplayManagerFlags { com.android.graphics.surfaceflinger.flags.Flags.FLAG_ENABLE_SMALL_AREA_DETECTION, com.android.graphics.surfaceflinger.flags.Flags::enableSmallAreaDetection); + private final FlagState mDisplayConfigErrorHalFlagState = new FlagState( + com.android.graphics.surfaceflinger.flags.Flags.FLAG_DISPLAY_CONFIG_ERROR_HAL, + com.android.graphics.surfaceflinger.flags.Flags::displayConfigErrorHal); + private final FlagState mBrightnessIntRangeUserPerceptionFlagState = new FlagState( Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION, Flags::brightnessIntRangeUserPerception); @@ -357,6 +361,10 @@ public class DisplayManagerFlags { return mSmallAreaDetectionFlagState.isEnabled(); } + public boolean isDisplayConfigErrorHalEnabled() { + return mDisplayConfigErrorHalFlagState.isEnabled(); + } + public boolean isBrightnessIntRangeUserPerceptionEnabled() { return mBrightnessIntRangeUserPerceptionFlagState.isEnabled(); } @@ -583,6 +591,7 @@ public class DisplayManagerFlags { pw.println(" " + mPowerThrottlingClamperFlagState); pw.println(" " + mEvenDimmerFlagState); pw.println(" " + mSmallAreaDetectionFlagState); + pw.println(" " + mDisplayConfigErrorHalFlagState); pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState); pw.println(" " + mRestrictDisplayModes); pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState); diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 8423e1911764..02e2882442bf 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -136,6 +136,7 @@ public class DisplayModeDirector { private final ProximitySensorObserver mSensorObserver; private final HbmObserver mHbmObserver; private final SkinThermalStatusObserver mSkinThermalStatusObserver; + private final ModeChangeObserver mModeChangeObserver; @Nullable private final SystemRequestObserver mSystemRequestObserver; @@ -247,6 +248,7 @@ public class DisplayModeDirector { mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage, injector); mSensorObserver = new ProximitySensorObserver(mVotesStorage, injector); mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage); + mModeChangeObserver = new ModeChangeObserver(mVotesStorage, injector, handler.getLooper()); mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(), mDeviceConfigDisplaySettings); if (displayManagerFlags.isRestrictDisplayModesEnabled()) { @@ -275,6 +277,9 @@ public class DisplayModeDirector { mSensorObserver.observe(); mHbmObserver.observe(); mSkinThermalStatusObserver.observe(); + if (mDisplayManagerFlags.isDisplayConfigErrorHalEnabled()) { + mModeChangeObserver.observe(); + } synchronized (mLock) { // We may have a listener already registered before the call to start, so go ahead and // notify them to pick up our newly initialized state. diff --git a/services/core/java/com/android/server/display/mode/ModeChangeObserver.java b/services/core/java/com/android/server/display/mode/ModeChangeObserver.java new file mode 100644 index 000000000000..bbc13cc6ae7e --- /dev/null +++ b/services/core/java/com/android/server/display/mode/ModeChangeObserver.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 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.display.mode; + +import android.os.Looper; +import android.util.Slog; +import android.util.SparseArray; +import android.view.Display; +import android.view.DisplayAddress; +import android.view.DisplayEventReceiver; + +import com.android.internal.annotations.KeepForWeakReference; + +import java.util.HashSet; +import java.util.Set; + +final class ModeChangeObserver { + private static final String TAG = "ModeChangeObserver"; + + private final VotesStorage mVotesStorage; + private final DisplayModeDirector.Injector mInjector; + + @SuppressWarnings("unused") + @KeepForWeakReference + private DisplayEventReceiver mModeChangeListener; + private final SparseArray<Set<Integer>> mRejectedModesByDisplay = new SparseArray<>(); + private Looper mLooper; + + ModeChangeObserver(VotesStorage votesStorage, DisplayModeDirector.Injector injector, + Looper looper) { + mVotesStorage = votesStorage; + mInjector = injector; + mLooper = looper; + } + + void observe() { + mModeChangeListener = new DisplayEventReceiver(mLooper) { + @Override + public void onModeRejected(long physicalDisplayId, int modeId) { + Slog.d(TAG, "Mode Rejected event received"); + int displayId = getLogicalDisplayId(physicalDisplayId); + if (displayId < 0) { + Slog.e(TAG, "Logical Display Id not found"); + return; + } + populateRejectedModesListByDisplay(displayId, modeId); + } + + @Override + public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) { + Slog.d(TAG, "Hotplug event received"); + if (!connected) { + int displayId = getLogicalDisplayId(physicalDisplayId); + if (displayId < 0) { + Slog.e(TAG, "Logical Display Id not found"); + return; + } + clearRejectedModesListByDisplay(displayId); + } + } + }; + } + + private int getLogicalDisplayId(long rejectedModePhysicalDisplayId) { + Display[] displays = mInjector.getDisplays(); + + for (Display display : displays) { + DisplayAddress address = display.getAddress(); + if (address instanceof DisplayAddress.Physical physical) { + long physicalDisplayId = physical.getPhysicalDisplayId(); + if (physicalDisplayId == rejectedModePhysicalDisplayId) { + return display.getDisplayId(); + } + } + } + return -1; + } + + private void populateRejectedModesListByDisplay(int displayId, int rejectedModeId) { + Set<Integer> alreadyRejectedModes = mRejectedModesByDisplay.get(displayId); + if (alreadyRejectedModes == null) { + alreadyRejectedModes = new HashSet<>(); + mRejectedModesByDisplay.put(displayId, alreadyRejectedModes); + } + alreadyRejectedModes.add(rejectedModeId); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES, + Vote.forRejectedModes(alreadyRejectedModes)); + } + + private void clearRejectedModesListByDisplay(int displayId) { + mRejectedModesByDisplay.remove(displayId); + mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES, null); + } +} diff --git a/services/core/java/com/android/server/display/mode/RejectedModesVote.java b/services/core/java/com/android/server/display/mode/RejectedModesVote.java new file mode 100644 index 000000000000..db8c8527844b --- /dev/null +++ b/services/core/java/com/android/server/display/mode/RejectedModesVote.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 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.display.mode; + +import android.annotation.NonNull; + +import java.util.Collections; +import java.util.Set; + +public class RejectedModesVote implements Vote { + + final Set<Integer> mModeIds; + + RejectedModesVote(Set<Integer> modeIds) { + mModeIds = Collections.unmodifiableSet(modeIds); + } + @Override + public void updateSummary(@NonNull VoteSummary summary) { + summary.rejectedModeIds.addAll(mModeIds); + } + + @Override + public String toString() { + return "RejectedModesVote{ mModeIds=" + mModeIds + " }"; + } +} diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index f5abb0561ce7..428ccedf8760 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -25,6 +25,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Set; interface Vote { // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest @@ -82,68 +83,73 @@ interface Vote { int PRIORITY_APP_REQUEST_SIZE = 7; + // PRIORITY_REJECTED_MODES rejects the modes for which the mode config failed + // so that the modeset can be retried for next available mode after filtering + // out the rejected modes for the connected display + int PRIORITY_REJECTED_MODES = 8; + // PRIORITY_USER_SETTING_PEAK_REFRESH_RATE restricts physical refresh rate to // [0, max(PEAK, MIN)], depending on user settings peakRR/minRR values - int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 8; + int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 9; // PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE has a higher priority than // PRIORITY_USER_SETTING_PEAK_REFRESH_RATE and will limit render rate to [0, max(PEAK, MIN)] // in case physical refresh rate vote is discarded (due to other high priority votes), // render rate vote can still apply - int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 9; + int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 10; // Restrict all displays physical refresh rate to 60Hz when external display is connected. // It votes [59Hz, 61Hz]. - int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 10; + int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 11; // PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE has a higher priority than // PRIORITY_SYNCHRONIZED_REFRESH_RATE and will limit render rate to [59Hz, 61Hz]. // In case physical refresh rate vote discarded (due to physical refresh rate not supported), // render rate vote can still apply. - int PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE = 11; + int PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE = 12; // Restrict displays max available resolution and refresh rates. It votes [0, LIMIT] - int PRIORITY_LIMIT_MODE = 12; + int PRIORITY_LIMIT_MODE = 13; // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh // rate to max value (same as for PRIORITY_UDFPS) on lock screen - int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 13; + int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 14; // For concurrent displays we want to limit refresh rate on all displays - int PRIORITY_LAYOUT_LIMITED_REFRESH_RATE = 14; + int PRIORITY_LAYOUT_LIMITED_REFRESH_RATE = 15; // For concurrent displays we want to limit refresh rate on all displays - int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 15; + int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 16; // For internal application to limit display modes to specific ids - int PRIORITY_SYSTEM_REQUESTED_MODES = 16; + int PRIORITY_SYSTEM_REQUESTED_MODES = 17; // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if // Settings.Global.LOW_POWER_MODE is on. // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other // higher priority votes), render rate limit can still apply - int PRIORITY_LOW_POWER_MODE_MODES = 17; + int PRIORITY_LOW_POWER_MODE_MODES = 18; // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 18; + int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 19; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 19; + int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 20; // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - int PRIORITY_SKIN_TEMPERATURE = 20; + int PRIORITY_SKIN_TEMPERATURE = 21; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - int PRIORITY_PROXIMITY = 21; + int PRIORITY_PROXIMITY = 22; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - int PRIORITY_UDFPS = 22; + int PRIORITY_UDFPS = 23; @IntDef(prefix = { "PRIORITY_" }, value = { PRIORITY_DEFAULT_RENDER_FRAME_RATE, @@ -154,6 +160,7 @@ interface Vote { PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE, PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, PRIORITY_APP_REQUEST_SIZE, + PRIORITY_REJECTED_MODES, PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, PRIORITY_SYNCHRONIZED_REFRESH_RATE, @@ -245,6 +252,10 @@ interface Vote { return new SupportedModesVote(modeIds); } + static Vote forRejectedModes(Set<Integer> modeIds) { + return new RejectedModesVote(modeIds); + } + static String priorityToString(int priority) { switch (priority) { case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE: @@ -253,6 +264,8 @@ interface Vote { return "PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE"; case PRIORITY_APP_REQUEST_SIZE: return "PRIORITY_APP_REQUEST_SIZE"; + case PRIORITY_REJECTED_MODES: + return "PRIORITY_REJECTED_MODES"; case PRIORITY_DEFAULT_RENDER_FRAME_RATE: return "PRIORITY_DEFAULT_REFRESH_RATE"; case PRIORITY_FLICKER_REFRESH_RATE: diff --git a/services/core/java/com/android/server/display/mode/VoteSummary.java b/services/core/java/com/android/server/display/mode/VoteSummary.java index 00a922630d8e..41664930fc2e 100644 --- a/services/core/java/com/android/server/display/mode/VoteSummary.java +++ b/services/core/java/com/android/server/display/mode/VoteSummary.java @@ -55,6 +55,11 @@ final class VoteSummary { @Nullable public List<Integer> supportedModeIds; + /** + * set of rejected modes due to mode config failure for connected display + */ + public Set<Integer> rejectedModeIds = new HashSet<>(); + final boolean mIsDisplayResolutionRangeVotingEnabled; private final boolean mSupportedModesVoteEnabled; @@ -132,6 +137,9 @@ final class VoteSummary { if (!validateModeSupported(mode)) { continue; } + if (!validateModeRejected(mode)) { + continue; + } if (!validateModeSize(mode)) { continue; } @@ -285,6 +293,22 @@ final class VoteSummary { return false; } + private boolean validateModeRejected(Display.Mode mode) { + if (rejectedModeIds == null) { + return true; + } + if (!rejectedModeIds.contains(mode.getModeId())) { + return true; + } + if (mLoggingEnabled) { + Slog.w(TAG, "Discarding mode" + mode.getModeId() + + ", is a rejectedMode" + + ": mode.modeId=" + mode.getModeId() + + ", rejectedModeIds=" + rejectedModeIds); + } + return false; + } + private boolean validateRefreshRatesSupported(Display.Mode mode) { if (supportedRefreshRates == null || !mSupportedModesVoteEnabled) { return true; @@ -397,6 +421,7 @@ final class VoteSummary { requestedRefreshRates.clear(); supportedRefreshRates = null; supportedModeIds = null; + rejectedModeIds.clear(); if (mLoggingEnabled) { Slog.i(TAG, "Summary reset: " + this); } @@ -421,6 +446,7 @@ final class VoteSummary { + ", requestRefreshRates=" + requestedRefreshRates + ", supportedRefreshRates=" + supportedRefreshRates + ", supportedModeIds=" + supportedModeIds + + ", rejectedModeIds=" + rejectedModeIds + ", mIsDisplayResolutionRangeVotingEnabled=" + mIsDisplayResolutionRangeVotingEnabled + ", mSupportedModesVoteEnabled=" + mSupportedModesVoteEnabled diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/RejectedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/RejectedModesVoteTest.kt new file mode 100644 index 000000000000..dd3211d0ef83 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/RejectedModesVoteTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 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.display.mode + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class RejectedModesVoteTest { + private val rejectedModes = setOf(1, 2) + + private val otherMode = 2 + + private lateinit var rejectedModesVote: RejectedModesVote + + @Before + fun setUp() { + rejectedModesVote = RejectedModesVote(rejectedModes) + } + + @Test + fun addsRejectedModeIds_summaryIsEmpty() { + val summary = createVotesSummary() + + rejectedModesVote.updateSummary(summary) + + assertThat(summary.rejectedModeIds).containsExactlyElementsIn(rejectedModes) + } + + @Test + fun addsRejectedModeIds_summaryIsNotEmpty() { + val summary = createVotesSummary() + summary.rejectedModeIds.add(otherMode) + + rejectedModesVote.updateSummary(summary) + + assertThat(summary.rejectedModeIds).containsExactlyElementsIn(rejectedModes + otherMode) + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt index 239e59b69187..958cf21a38a2 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt @@ -186,6 +186,44 @@ class VoteSummaryTest { assertThat(result).hasSize(1) } + + enum class RejectedModesTestCase( + internal val summaryRejectedModes: Set<Int>?, + val modesToFilter: Array<Display.Mode>, + val expectedModeIds: Set<Int> + ) { + HAS_NO_MATCHING_VOTE( + setOf(4, 5), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + setOf(1, 2, 3) + ), + HAS_SINGLE_MATCHING_VOTE( + setOf(1), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + setOf(2, 3) + ), + HAS_MULTIPLE_MATCHING_VOTES( + setOf(1, 2), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + setOf(3) + ), + } + + @Test + fun testFilterModes_rejectedModes(@TestParameter testCase: RejectedModesTestCase) { + val summary = createSummary() + summary.rejectedModeIds = testCase.summaryRejectedModes + + val result = summary.filterModes(testCase.modesToFilter) + + assertThat(result.map {it.modeId}).containsExactlyElementsIn(testCase.expectedModeIds) + } } |