summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java9
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java5
-rw-r--r--services/core/java/com/android/server/display/mode/ModeChangeObserver.java108
-rw-r--r--services/core/java/com/android/server/display/mode/RejectedModesVote.java40
-rw-r--r--services/core/java/com/android/server/display/mode/Vote.java43
-rw-r--r--services/core/java/com/android/server/display/mode/VoteSummary.java26
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/RejectedModesVoteTest.kt58
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt38
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)
+ }
}