diff options
| author | 2024-03-14 16:52:58 +0000 | |
|---|---|---|
| committer | 2024-03-14 16:52:58 +0000 | |
| commit | 87793a4114e289437b6cf83a124ea76144996256 (patch) | |
| tree | 0ff8d5cb1c904b08b269210a4eef3ba6491ad73f | |
| parent | 1dbe381c9a272dc89a235a22880bd4ff7b7a627a (diff) | |
| parent | 0e4a0aa5e10cc3a49901d562e225361f8030abb8 (diff) | |
Merge changes from topic "b/309466682" into main
* changes:
Adding new SYSTEM_REQUESTED priority to DisplayModeDirector voting
Adding new API to restrict Display modes to specific ids
30 files changed, 1148 insertions, 244 deletions
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index eb26a768f2a6..4894fb1ec452 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1653,6 +1653,22 @@ public final class DisplayManager { } /** + * Allows internal application to restrict display modes to specified modeIds + * + * @param displayId display that restrictions will be applied to + * @param modeIds allowed mode ids + * + * @hide + */ + @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES") + public void requestDisplayModes(int displayId, @Nullable int[] modeIds) { + if (modeIds != null && modeIds.length == 0) { + throw new IllegalArgumentException("requestDisplayModes: modesIds can't be empty"); + } + mGlobal.requestDisplayModes(displayId, modeIds); + } + + /** * Listens for changes in available display devices. */ public interface DisplayListener { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 75f0ceb7e651..3d7b714a2f5b 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -38,6 +38,7 @@ import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.graphics.common.DisplayDecorationSupport; import android.media.projection.IMediaProjection; import android.media.projection.MediaProjection; +import android.os.Binder; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; @@ -138,6 +139,8 @@ public final class DisplayManagerGlobal { private int mWifiDisplayScanNestCount; + private final Binder mToken = new Binder(); + @VisibleForTesting public DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; @@ -1182,6 +1185,20 @@ public final class DisplayManagerGlobal { } } + /** + * Sets allowed display mode ids + * + * @hide + */ + @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES") + public void requestDisplayModes(int displayId, @Nullable int[] modeIds) { + try { + mDm.requestDisplayModes(mToken, displayId, modeIds); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, @DisplayEvent int event) { diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 83de4e45cf2f..70efc6f2e33f 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -235,4 +235,8 @@ interface IDisplayManager { // Disable a connected display that is enabled. @EnforcePermission("MANAGE_DISPLAYS") void disableConnectedDisplay(int displayId); + + // Restricts display modes to specified modeIds. + @EnforcePermission("RESTRICT_DISPLAY_MODES") + void requestDisplayModes(in IBinder token, int displayId, in @nullable int[] modeIds); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0869af5b1fd8..7e46818da745 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8150,6 +8150,14 @@ <permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES" android:protectionLevel="signature|privileged"/> + <!-- Allows internal applications to restrict display modes + <p>Not for use by third-party applications. + <p>Protection level: signature + @hide + --> + <permission android:name="android.permission.RESTRICT_DISPLAY_MODES" + android:protectionLevel="signature" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 29e0586b9ac4..84eebe838954 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -22,6 +22,7 @@ import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT; import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.MANAGE_DISPLAYS; +import static android.Manifest.permission.RESTRICT_DISPLAY_MODES; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.hardware.display.DisplayManager.EventsMask; @@ -4531,6 +4532,14 @@ public final class DisplayManagerService extends SystemService { disableConnectedDisplay_enforcePermission(); DisplayManagerService.this.enableConnectedDisplay(displayId, false); } + + @EnforcePermission(RESTRICT_DISPLAY_MODES) + @Override // Binder call + public void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) { + requestDisplayModes_enforcePermission(); + DisplayManagerService.this.mDisplayModeDirector.requestDisplayModes( + token, displayId, modeIds); + } } private static boolean isValidBrightness(float brightness) { 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 31524dc7056d..e1a166ec95f5 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -92,9 +92,9 @@ public class DisplayManagerFlags { Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION, Flags::brightnessIntRangeUserPerception); - private final FlagState mVsyncProximityVote = new FlagState( - Flags.FLAG_ENABLE_EXTERNAL_VSYNC_PROXIMITY_VOTE, - Flags::enableExternalVsyncProximityVote); + private final FlagState mRestrictDisplayModes = new FlagState( + Flags.FLAG_ENABLE_RESTRICT_DISPLAY_MODES, + Flags::enableRestrictDisplayModes); private final FlagState mVsyncLowPowerVote = new FlagState( Flags.FLAG_ENABLE_VSYNC_LOW_POWER_VOTE, @@ -242,8 +242,8 @@ public class DisplayManagerFlags { return mBrightnessIntRangeUserPerceptionFlagState.isEnabled(); } - public boolean isVsyncProximityVoteEnabled() { - return mVsyncProximityVote.isEnabled(); + public boolean isRestrictDisplayModesEnabled() { + return mRestrictDisplayModes.isEnabled(); } public boolean isVsyncLowPowerVoteEnabled() { @@ -311,7 +311,7 @@ public class DisplayManagerFlags { pw.println(" " + mPowerThrottlingClamperFlagState); pw.println(" " + mSmallAreaDetectionFlagState); pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState); - pw.println(" " + mVsyncProximityVote); + pw.println(" " + mRestrictDisplayModes); pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState); pw.println(" " + mAutoBrightnessModesFlagState); pw.println(" " + mFastHdrTransitions); diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index ff0f597ab607..a5f241f4d68e 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -130,9 +130,9 @@ flag { } flag { - name: "enable_external_vsync_proximity_vote" + name: "enable_restrict_display_modes" namespace: "display_manager" - description: "Feature flag for external vsync proximity vote" + description: "Feature flag for restriction display modes api" bug: "284866750" is_fixed_read_only: true } diff --git a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java index c53823139ffe..6d750c0aa3cb 100644 --- a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java +++ b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; class BaseModeRefreshRateVote implements Vote { @@ -31,7 +33,7 @@ class BaseModeRefreshRateVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { if (summary.appRequestBaseModeRefreshRate == 0f && mAppRequestBaseModeRefreshRate > 0f) { summary.appRequestBaseModeRefreshRate = mAppRequestBaseModeRefreshRate; diff --git a/services/core/java/com/android/server/display/mode/CombinedVote.java b/services/core/java/com/android/server/display/mode/CombinedVote.java index 4b68791268e9..3cd16bf5c640 100644 --- a/services/core/java/com/android/server/display/mode/CombinedVote.java +++ b/services/core/java/com/android/server/display/mode/CombinedVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Collections; import java.util.List; import java.util.Objects; @@ -28,7 +30,7 @@ class CombinedVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { mVotes.forEach(vote -> vote.updateSummary(summary)); } diff --git a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java index 7f5740690c7f..7abb518ec494 100644 --- a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java +++ b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; class DisableRefreshRateSwitchingVote implements Vote { @@ -31,7 +33,7 @@ class DisableRefreshRateSwitchingVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { summary.disableRefreshRateSwitching = summary.disableRefreshRateSwitching || mDisableRefreshRateSwitching; } 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 64cbd5488d90..495ae87fe0b9 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -41,6 +41,7 @@ import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.net.Uri; import android.os.Handler; +import android.os.IBinder; import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.Looper; @@ -80,7 +81,6 @@ import com.android.server.display.utils.AmbientFilterFactory; import com.android.server.display.utils.DeviceConfigParsingUtils; import com.android.server.display.utils.SensorUtils; import com.android.server.sensors.SensorManagerInternal; -import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import com.android.server.statusbar.StatusBarManagerInternal; import java.io.PrintWriter; @@ -128,9 +128,12 @@ public class DisplayModeDirector { private final SettingsObserver mSettingsObserver; private final DisplayObserver mDisplayObserver; private final UdfpsObserver mUdfpsObserver; - private final SensorObserver mSensorObserver; + private final ProximitySensorObserver mSensorObserver; private final HbmObserver mHbmObserver; private final SkinThermalStatusObserver mSkinThermalStatusObserver; + + @Nullable + private final SystemRequestObserver mSystemRequestObserver; private final DeviceConfigParameterProvider mConfigParameterProvider; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @@ -203,6 +206,7 @@ public class DisplayModeDirector { .isDisplaysRefreshRatesSynchronizationEnabled(); mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags .isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled(); + mContext = context; mHandler = new DisplayModeDirectorHandler(handler.getLooper()); mInjector = injector; @@ -222,10 +226,15 @@ public class DisplayModeDirector { mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked, mVotesStatsReporter); mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage); - mSensorObserver = new SensorObserver(context, mVotesStorage, injector); + mSensorObserver = new ProximitySensorObserver(mVotesStorage, injector); mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage); mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(), mDeviceConfigDisplaySettings); + if (mDvrrSupported && displayManagerFlags.isRestrictDisplayModesEnabled()) { + mSystemRequestObserver = new SystemRequestObserver(mVotesStorage); + } else { + mSystemRequestObserver = null; + } mAlwaysRespectAppRequest = false; mSupportsFrameRateOverride = injector.supportsFrameRateOverride(); } @@ -520,6 +529,15 @@ public class DisplayModeDirector { } /** + * Delegates requestDisplayModes call to SystemRequestObserver + */ + public void requestDisplayModes(IBinder token, int displayId, int[] modeIds) { + if (mSystemRequestObserver != null) { + mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds); + } + } + + /** * Print the object's state and debug information into the given stream. * * @param pw The stream to dump information to. @@ -970,10 +988,10 @@ public class DisplayModeDirector { Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0; final Vote vote; if (inLowPowerMode && mVsynLowPowerVoteEnabled) { - vote = Vote.forSupportedModes(List.of( - new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f, + vote = Vote.forSupportedRefreshRates(List.of( + new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, /* vsyncRate= */ 240f), - new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f, + new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f) )); } else if (inLowPowerMode) { @@ -2158,11 +2176,11 @@ public class DisplayModeDirector { } if (mVsyncLowLightBlockingVoteEnabled) { - refreshRateSwitchingVote = Vote.forSupportedModesAndDisableRefreshRateSwitching( + refreshRateSwitchingVote = Vote.forSupportedRefreshRatesAndDisableSwitching( List.of( - new SupportedModesVote.SupportedMode( + new SupportedRefreshRatesVote.RefreshRates( /* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f), - new SupportedModesVote.SupportedMode( + new SupportedRefreshRatesVote.RefreshRates( /* peakRefreshRate= */120f, /* vsyncRate= */ 120f))); } else { refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); @@ -2498,116 +2516,6 @@ public class DisplayModeDirector { } } - protected static final class SensorObserver implements ProximityActiveListener, - DisplayManager.DisplayListener { - private final String mProximitySensorName = null; - private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY; - - private final VotesStorage mVotesStorage; - private final Context mContext; - private final Injector mInjector; - @GuardedBy("mSensorObserverLock") - private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray(); - private final Object mSensorObserverLock = new Object(); - - private DisplayManager mDisplayManager; - private DisplayManagerInternal mDisplayManagerInternal; - @GuardedBy("mSensorObserverLock") - private boolean mIsProxActive = false; - - SensorObserver(Context context, VotesStorage votesStorage, Injector injector) { - mContext = context; - mVotesStorage = votesStorage; - mInjector = injector; - } - - @Override - public void onProximityActive(boolean isActive) { - synchronized (mSensorObserverLock) { - if (mIsProxActive != isActive) { - mIsProxActive = isActive; - recalculateVotesLocked(); - } - } - } - - public void observe() { - mDisplayManager = mContext.getSystemService(DisplayManager.class); - mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); - - final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal(); - sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); - - synchronized (mSensorObserverLock) { - for (Display d : mInjector.getDisplays()) { - mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); - } - } - mInjector.registerDisplayListener(this, BackgroundThread.getHandler(), - DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); - } - - private void recalculateVotesLocked() { - final Display[] displays = mInjector.getDisplays(); - for (Display d : displays) { - int displayId = d.getDisplayId(); - Vote vote = null; - if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) { - final RefreshRateRange rate = - mDisplayManagerInternal.getRefreshRateForDisplayAndSensor( - displayId, mProximitySensorName, mProximitySensorType); - if (rate != null) { - vote = Vote.forPhysicalRefreshRates(rate.min, rate.max); - } - } - mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote); - } - } - - void dump(PrintWriter pw) { - pw.println(" SensorObserver"); - synchronized (mSensorObserverLock) { - pw.println(" mIsProxActive=" + mIsProxActive); - pw.println(" mDozeStateByDisplay:"); - for (int i = 0; i < mDozeStateByDisplay.size(); i++) { - final int id = mDozeStateByDisplay.keyAt(i); - final boolean dozed = mDozeStateByDisplay.valueAt(i); - pw.println(" " + id + " -> " + dozed); - } - } - } - - @Override - public void onDisplayAdded(int displayId) { - boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId)); - synchronized (mSensorObserverLock) { - mDozeStateByDisplay.put(displayId, isDozeState); - recalculateVotesLocked(); - } - } - - @Override - public void onDisplayChanged(int displayId) { - boolean wasDozeState = mDozeStateByDisplay.get(displayId); - synchronized (mSensorObserverLock) { - mDozeStateByDisplay.put(displayId, - mInjector.isDozeState(mInjector.getDisplay(displayId))); - if (wasDozeState != mDozeStateByDisplay.get(displayId)) { - recalculateVotesLocked(); - } - } - } - - @Override - public void onDisplayRemoved(int displayId) { - synchronized (mSensorObserverLock) { - mDozeStateByDisplay.delete(displayId); - recalculateVotesLocked(); - } - } - } /** * Listens to DisplayManager for HBM status and applies any refresh-rate restrictions for diff --git a/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java new file mode 100644 index 000000000000..11418c147caa --- /dev/null +++ b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java @@ -0,0 +1,138 @@ +/* + * 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.hardware.Sensor; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; +import android.util.SparseBooleanArray; +import android.view.Display; +import android.view.SurfaceControl; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; +import com.android.server.sensors.SensorManagerInternal; + +import java.io.PrintWriter; + +class ProximitySensorObserver implements + SensorManagerInternal.ProximityActiveListener, + DisplayManager.DisplayListener { + private final String mProximitySensorName = null; + private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY; + + private final VotesStorage mVotesStorage; + private final DisplayModeDirector.Injector mInjector; + @GuardedBy("mSensorObserverLock") + private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray(); + private final Object mSensorObserverLock = new Object(); + private DisplayManagerInternal mDisplayManagerInternal; + @GuardedBy("mSensorObserverLock") + private boolean mIsProxActive = false; + + ProximitySensorObserver(VotesStorage votesStorage, DisplayModeDirector.Injector injector) { + mVotesStorage = votesStorage; + mInjector = injector; + } + + @Override + public void onProximityActive(boolean isActive) { + synchronized (mSensorObserverLock) { + if (mIsProxActive != isActive) { + mIsProxActive = isActive; + recalculateVotesLocked(); + } + } + } + + void observe() { + mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); + + final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal(); + sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); + + synchronized (mSensorObserverLock) { + for (Display d : mInjector.getDisplays()) { + mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); + } + } + mInjector.registerDisplayListener(this, BackgroundThread.getHandler(), + DisplayManager.EVENT_FLAG_DISPLAY_ADDED + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + } + + @GuardedBy("mSensorObserverLock") + private void recalculateVotesLocked() { + final Display[] displays = mInjector.getDisplays(); + for (Display d : displays) { + int displayId = d.getDisplayId(); + Vote vote = null; + if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) { + final SurfaceControl.RefreshRateRange rate = + mDisplayManagerInternal.getRefreshRateForDisplayAndSensor( + displayId, mProximitySensorName, mProximitySensorType); + if (rate != null) { + vote = Vote.forPhysicalRefreshRates(rate.min, rate.max); + } + } + mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote); + } + } + + void dump(PrintWriter pw) { + pw.println(" SensorObserver"); + synchronized (mSensorObserverLock) { + pw.println(" mIsProxActive=" + mIsProxActive); + pw.println(" mDozeStateByDisplay:"); + for (int i = 0; i < mDozeStateByDisplay.size(); i++) { + final int id = mDozeStateByDisplay.keyAt(i); + final boolean dozed = mDozeStateByDisplay.valueAt(i); + pw.println(" " + id + " -> " + dozed); + } + } + } + + @Override + public void onDisplayAdded(int displayId) { + boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId)); + synchronized (mSensorObserverLock) { + mDozeStateByDisplay.put(displayId, isDozeState); + recalculateVotesLocked(); + } + } + + @Override + public void onDisplayChanged(int displayId) { + synchronized (mSensorObserverLock) { + boolean wasDozeState = mDozeStateByDisplay.get(displayId); + mDozeStateByDisplay.put(displayId, + mInjector.isDozeState(mInjector.getDisplay(displayId))); + if (wasDozeState != mDozeStateByDisplay.get(displayId)) { + recalculateVotesLocked(); + } + } + } + + @Override + public void onDisplayRemoved(int displayId) { + synchronized (mSensorObserverLock) { + mDozeStateByDisplay.delete(displayId); + recalculateVotesLocked(); + } + } +} diff --git a/services/core/java/com/android/server/display/mode/RefreshRateVote.java b/services/core/java/com/android/server/display/mode/RefreshRateVote.java index 670b8a13da4d..b96ab3b6be3d 100644 --- a/services/core/java/com/android/server/display/mode/RefreshRateVote.java +++ b/services/core/java/com/android/server/display/mode/RefreshRateVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; @@ -64,7 +66,7 @@ abstract class RefreshRateVote implements Vote { * Vote: min(ignored) min(applied) min(applied+physical) max(applied) max(ignored) */ @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate, mMinRefreshRate); summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, mMaxRefreshRate); // Physical refresh rate cannot be lower than the minimal render frame rate. @@ -97,7 +99,7 @@ abstract class RefreshRateVote implements Vote { * Vote: min(ignored) min(applied) max(applied+render) max(applied) max(ignored) */ @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate, mMinRefreshRate); summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate, diff --git a/services/core/java/com/android/server/display/mode/SizeVote.java b/services/core/java/com/android/server/display/mode/SizeVote.java index f2f8dc451098..f5a5abea9d9e 100644 --- a/services/core/java/com/android/server/display/mode/SizeVote.java +++ b/services/core/java/com/android/server/display/mode/SizeVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; class SizeVote implements Vote { @@ -48,7 +50,7 @@ class SizeVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { if (mHeight > 0 && mWidth > 0) { // For display size, disable refresh rate switching and base mode refresh rate use // only the first vote we come across (i.e. the highest priority vote that includes diff --git a/services/core/java/com/android/server/display/mode/SupportedModesVote.java b/services/core/java/com/android/server/display/mode/SupportedModesVote.java index 7eebcc050b16..0cf8311128d0 100644 --- a/services/core/java/com/android/server/display/mode/SupportedModesVote.java +++ b/services/core/java/com/android/server/display/mode/SupportedModesVote.java @@ -16,77 +16,42 @@ package com.android.server.display.mode; -import java.util.ArrayList; +import android.annotation.NonNull; + import java.util.Collections; import java.util.List; import java.util.Objects; -class SupportedModesVote implements Vote { +public class SupportedModesVote implements Vote { - final List<SupportedMode> mSupportedModes; + final List<Integer> mModeIds; - SupportedModesVote(List<SupportedMode> supportedModes) { - mSupportedModes = Collections.unmodifiableList(supportedModes); + SupportedModesVote(List<Integer> modeIds) { + mModeIds = Collections.unmodifiableList(modeIds); } - - /** - * Summary should have subset of supported modes. - * If Vote1.supportedModes=(A,B), Vote2.supportedModes=(B,C) then summary.supportedModes=(B) - * If summary.supportedModes==null then there is no restriction on supportedModes - */ @Override - public void updateSummary(VoteSummary summary) { - if (summary.supportedModes == null) { - summary.supportedModes = new ArrayList<>(mSupportedModes); + public void updateSummary(@NonNull VoteSummary summary) { + if (summary.supportedModeIds == null) { + summary.supportedModeIds = mModeIds; } else { - summary.supportedModes.retainAll(mSupportedModes); + summary.supportedModeIds.retainAll(mModeIds); } } @Override + public String toString() { + return "SupportedModesVote{ mModeIds=" + mModeIds + " }"; + } + + @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SupportedModesVote that)) return false; - return mSupportedModes.equals(that.mSupportedModes); + return mModeIds.equals(that.mModeIds); } @Override public int hashCode() { - return Objects.hash(mSupportedModes); - } - - @Override - public String toString() { - return "SupportedModesVote{ mSupportedModes=" + mSupportedModes + " }"; - } - - static class SupportedMode { - final float mPeakRefreshRate; - final float mVsyncRate; - - - SupportedMode(float peakRefreshRate, float vsyncRate) { - mPeakRefreshRate = peakRefreshRate; - mVsyncRate = vsyncRate; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SupportedMode that)) return false; - return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0 - && Float.compare(that.mVsyncRate, mVsyncRate) == 0; - } - - @Override - public int hashCode() { - return Objects.hash(mPeakRefreshRate, mVsyncRate); - } - - @Override - public String toString() { - return "SupportedMode{ mPeakRefreshRate=" + mPeakRefreshRate - + ", mVsyncRate=" + mVsyncRate + " }"; - } + return Objects.hash(mModeIds); } } diff --git a/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java new file mode 100644 index 000000000000..5305487b2ddd --- /dev/null +++ b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java @@ -0,0 +1,94 @@ +/* + * 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.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +class SupportedRefreshRatesVote implements Vote { + + final List<RefreshRates> mRefreshRates; + + SupportedRefreshRatesVote(List<RefreshRates> refreshRates) { + mRefreshRates = Collections.unmodifiableList(refreshRates); + } + + /** + * Summary should have subset of supported modes. + * If Vote1.refreshRates=(A,B), Vote2.refreshRates=(B,C) + * then summary.supportedRefreshRates=(B) + * If summary.supportedRefreshRates==null then there is no restriction on supportedRefreshRates + */ + @Override + public void updateSummary(@NonNull VoteSummary summary) { + if (summary.supportedRefreshRates == null) { + summary.supportedRefreshRates = new ArrayList<>(mRefreshRates); + } else { + summary.supportedRefreshRates.retainAll(mRefreshRates); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SupportedRefreshRatesVote that)) return false; + return mRefreshRates.equals(that.mRefreshRates); + } + + @Override + public int hashCode() { + return Objects.hash(mRefreshRates); + } + + @Override + public String toString() { + return "SupportedRefreshRatesVote{ mSupportedModes=" + mRefreshRates + " }"; + } + + static class RefreshRates { + final float mPeakRefreshRate; + final float mVsyncRate; + + RefreshRates(float peakRefreshRate, float vsyncRate) { + mPeakRefreshRate = peakRefreshRate; + mVsyncRate = vsyncRate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RefreshRates that)) return false; + return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0 + && Float.compare(that.mVsyncRate, mVsyncRate) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(mPeakRefreshRate, mVsyncRate); + } + + @Override + public String toString() { + return "RefreshRates{ mPeakRefreshRate=" + mPeakRefreshRate + + ", mVsyncRate=" + mVsyncRate + " }"; + } + } +} diff --git a/services/core/java/com/android/server/display/mode/SystemRequestObserver.java b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java new file mode 100644 index 000000000000..15f19cca99db --- /dev/null +++ b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java @@ -0,0 +1,139 @@ +/* + * 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 android.annotation.Nullable; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * SystemRequestObserver responsible for handling system requests to filter allowable display + * modes + */ +class SystemRequestObserver { + private final VotesStorage mVotesStorage; + + private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + // noop, binderDied(@NonNull IBinder who) is overridden + } + @Override + public void binderDied(@NonNull IBinder who) { + removeSystemRequestedVotes(who); + who.unlinkToDeath(mDeathRecipient, 0); + } + }; + + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final Map<IBinder, SparseArray<List<Integer>>> mDisplaysRestrictions = new HashMap<>(); + + SystemRequestObserver(VotesStorage storage) { + mVotesStorage = storage; + } + + void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) { + if (modeIds == null) { + removeSystemRequestedVote(token, displayId); + } else { + addSystemRequestedVote(token, displayId, modeIds); + } + } + + private void addSystemRequestedVote(IBinder token, int displayId, @NonNull int[] modeIds) { + try { + boolean needLinkToDeath = false; + List<Integer> modeIdsList = new ArrayList<>(); + for (int mode: modeIds) { + modeIdsList.add(mode); + } + synchronized (mLock) { + SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token); + if (modesByDisplay == null) { + needLinkToDeath = true; + modesByDisplay = new SparseArray<>(); + mDisplaysRestrictions.put(token, modesByDisplay); + } + + modesByDisplay.put(displayId, modeIdsList); + updateStorageLocked(displayId); + } + if (needLinkToDeath) { + token.linkToDeath(mDeathRecipient, 0); + } + } catch (RemoteException re) { + removeSystemRequestedVotes(token); + } + } + + private void removeSystemRequestedVote(IBinder token, int displayId) { + boolean needToUnlink = false; + synchronized (mLock) { + SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token); + if (modesByDisplay != null) { + modesByDisplay.remove(displayId); + needToUnlink = modesByDisplay.size() == 0; + updateStorageLocked(displayId); + } + } + if (needToUnlink) { + token.unlinkToDeath(mDeathRecipient, 0); + } + } + + private void removeSystemRequestedVotes(IBinder token) { + synchronized (mLock) { + SparseArray<List<Integer>> removed = mDisplaysRestrictions.remove(token); + if (removed != null) { + for (int i = 0; i < removed.size(); i++) { + updateStorageLocked(removed.keyAt(i)); + } + } + } + } + + @GuardedBy("mLock") + private void updateStorageLocked(int displayId) { + List<Integer> modeIds = new ArrayList<>(); + boolean[] modesFound = new boolean[1]; + + mDisplaysRestrictions.forEach((key, value) -> { + List<Integer> modesForDisplay = value.get(displayId); + if (modesForDisplay != null) { + if (!modesFound[0]) { + modeIds.addAll(modesForDisplay); + modesFound[0] = true; + } else { + modeIds.retainAll(modesForDisplay); + } + } + }); + + mVotesStorage.updateVote(displayId, Vote.PRIORITY_SYSTEM_REQUESTED_MODES, + modesFound[0] ? Vote.forSupportedModes(modeIds) : null); + } +} 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 e8d5a194f8f4..5b987f491a45 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.List; interface Vote { @@ -91,26 +93,29 @@ interface Vote { // For concurrent displays we want to limit refresh rate on all displays int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12; + // For internal application to limit display modes to specific ids + int PRIORITY_SYSTEM_REQUESTED_MODES = 13; + // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - int PRIORITY_LOW_POWER_MODE = 13; + int PRIORITY_LOW_POWER_MODE = 14; // 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 = 14; + int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 15; // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - int PRIORITY_SKIN_TEMPERATURE = 15; + int PRIORITY_SKIN_TEMPERATURE = 16; // 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 = 16; + int PRIORITY_PROXIMITY = 17; // 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 = 17; + int PRIORITY_UDFPS = 18; // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. @@ -128,7 +133,7 @@ interface Vote { */ int INVALID_SIZE = -1; - void updateSummary(VoteSummary summary); + void updateSummary(@NonNull VoteSummary summary); static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) { return new CombinedVote( @@ -166,15 +171,22 @@ interface Vote { return new BaseModeRefreshRateVote(baseModeRefreshRate); } - static Vote forSupportedModes(List<SupportedModesVote.SupportedMode> supportedModes) { - return new SupportedModesVote(supportedModes); + static Vote forSupportedRefreshRates( + List<SupportedRefreshRatesVote.RefreshRates> refreshRates) { + return new SupportedRefreshRatesVote(refreshRates); } + static Vote forSupportedModes(List<Integer> modeIds) { + return new SupportedModesVote(modeIds); + } + + - static Vote forSupportedModesAndDisableRefreshRateSwitching( - List<SupportedModesVote.SupportedMode> supportedModes) { + static Vote forSupportedRefreshRatesAndDisableSwitching( + List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates) { return new CombinedVote( - List.of(forDisableRefreshRateSwitching(), forSupportedModes(supportedModes))); + List.of(forDisableRefreshRateSwitching(), + forSupportedRefreshRates(supportedRefreshRates))); } static String priorityToString(int priority) { 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 5fc36b589d38..d4ce892eeba9 100644 --- a/services/core/java/com/android/server/display/mode/VoteSummary.java +++ b/services/core/java/com/android/server/display/mode/VoteSummary.java @@ -16,6 +16,7 @@ package com.android.server.display.mode; +import android.annotation.Nullable; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -39,7 +40,11 @@ final class VoteSummary { public boolean disableRefreshRateSwitching; public float appRequestBaseModeRefreshRate; - public List<SupportedModesVote.SupportedMode> supportedModes; + @Nullable + public List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates; + + @Nullable + public List<Integer> supportedModeIds; final boolean mIsDisplayResolutionRangeVotingEnabled; @@ -112,6 +117,9 @@ final class VoteSummary { boolean missingBaseModeRefreshRate = appRequestBaseModeRefreshRate > 0f; for (Display.Mode mode : modes) { + if (!validateRefreshRatesSupported(mode)) { + continue; + } if (!validateModeSupported(mode)) { continue; } @@ -253,21 +261,37 @@ final class VoteSummary { } private boolean validateModeSupported(Display.Mode mode) { - if (supportedModes == null || !mSupportedModesVoteEnabled) { + if (supportedModeIds == null || !mSupportedModesVoteEnabled) { + return true; + } + if (supportedModeIds.contains(mode.getModeId())) { return true; } - for (SupportedModesVote.SupportedMode supportedMode : supportedModes) { - if (equalsWithinFloatTolerance(mode.getRefreshRate(), supportedMode.mPeakRefreshRate) - && equalsWithinFloatTolerance(mode.getVsyncRate(), supportedMode.mVsyncRate)) { + if (mLoggingEnabled) { + Slog.w(TAG, "Discarding mode " + mode.getModeId() + + ", supportedMode not found" + + ": mode.modeId=" + mode.getModeId() + + ", supportedModeIds=" + supportedModeIds); + } + return false; + } + + private boolean validateRefreshRatesSupported(Display.Mode mode) { + if (supportedRefreshRates == null || !mSupportedModesVoteEnabled) { + return true; + } + for (SupportedRefreshRatesVote.RefreshRates refreshRates : this.supportedRefreshRates) { + if (equalsWithinFloatTolerance(mode.getRefreshRate(), refreshRates.mPeakRefreshRate) + && equalsWithinFloatTolerance(mode.getVsyncRate(), refreshRates.mVsyncRate)) { return true; } } if (mLoggingEnabled) { Slog.w(TAG, "Discarding mode " + mode.getModeId() - + ", supportedMode not found" + + ", supportedRefreshRates not found" + ": mode.refreshRate=" + mode.getRefreshRate() + ", mode.vsyncRate=" + mode.getVsyncRate() - + ", supportedModes=" + supportedModes); + + ", supportedRefreshRates=" + supportedRefreshRates); } return false; } @@ -298,7 +322,8 @@ final class VoteSummary { return false; } - if (supportedModes != null && mSupportedModesVoteEnabled && supportedModes.isEmpty()) { + if (supportedRefreshRates != null && mSupportedModesVoteEnabled + && supportedRefreshRates.isEmpty()) { if (mLoggingEnabled) { Slog.w(TAG, "Vote summary resulted in empty set (empty supportedModes)"); } @@ -345,7 +370,8 @@ final class VoteSummary { minHeight = 0; disableRefreshRateSwitching = false; appRequestBaseModeRefreshRate = 0f; - supportedModes = null; + supportedRefreshRates = null; + supportedModeIds = null; if (mLoggingEnabled) { Slog.i(TAG, "Summary reset: " + this); } @@ -367,7 +393,8 @@ final class VoteSummary { + ", minHeight=" + minHeight + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate - + ", supportedModes=" + supportedModes + + ", supportedRefreshRates=" + supportedRefreshRates + + ", supportedModeIds=" + supportedModeIds + ", mIsDisplayResolutionRangeVotingEnabled=" + mIsDisplayResolutionRangeVotingEnabled + ", mSupportedModesVoteEnabled=" + mSupportedModesVoteEnabled diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java index e80b9451dd14..7562a525b5f6 100644 --- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java +++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java @@ -117,11 +117,11 @@ class VotesStatsReporter { maxRefreshRate = (int) physicalVote.mMaxRefreshRate; } else if (!ignoreRenderRate && (vote instanceof RefreshRateVote.RenderVote renderVote)) { maxRefreshRate = (int) renderVote.mMaxRefreshRate; - } else if (vote instanceof SupportedModesVote supportedModesVote) { - // SupportedModesVote limits mode by specific refreshRates, so highest rr is allowed + } else if (vote instanceof SupportedRefreshRatesVote refreshRatesVote) { + // SupportedRefreshRatesVote limits mode by refreshRates, so highest rr is allowed maxRefreshRate = 0; - for (SupportedModesVote.SupportedMode mode : supportedModesVote.mSupportedModes) { - maxRefreshRate = Math.max(maxRefreshRate, (int) mode.mPeakRefreshRate); + for (SupportedRefreshRatesVote.RefreshRates rr : refreshRatesVote.mRefreshRates) { + maxRefreshRate = Math.max(maxRefreshRate, (int) rr.mPeakRefreshRate); } } else if (vote instanceof CombinedVote combinedVote) { for (Vote subVote: combinedVote.mVotes) { diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java index 56c7c18c0a11..6becf1c46d05 100644 --- a/services/core/java/com/android/server/display/mode/VotesStorage.java +++ b/services/core/java/com/android/server/display/mode/VotesStorage.java @@ -18,6 +18,7 @@ package com.android.server.display.mode; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; @@ -124,6 +125,44 @@ class VotesStorage { } } + /** removes all votes with certain priority from vote storage */ + void removeAllVotesForPriority(int priority) { + if (mLoggingEnabled) { + Slog.i(TAG, "removeAllVotesForPriority(priority=" + + Vote.priorityToString(priority) + ")"); + } + if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) { + Slog.w(TAG, "Received an invalid priority, ignoring:" + + " priority=" + Vote.priorityToString(priority)); + return; + } + IntArray removedVotesDisplayIds = new IntArray(); + synchronized (mStorageLock) { + int size = mVotesByDisplay.size(); + for (int i = 0; i < size; i++) { + SparseArray<Vote> votes = mVotesByDisplay.valueAt(i); + if (votes.get(priority) != null) { + votes.remove(priority); + removedVotesDisplayIds.add(mVotesByDisplay.keyAt(i)); + } + } + } + if (mLoggingEnabled) { + Slog.i(TAG, "Removed votes with priority=" + priority + + " for displays=" + removedVotesDisplayIds); + } + int removedVotesSize = removedVotesDisplayIds.size(); + if (removedVotesSize > 0) { + if (mVotesStatsReporter != null) { + for (int i = 0; i < removedVotesSize; i++) { + mVotesStatsReporter.reportVoteChanged( + removedVotesDisplayIds.get(i), priority, null); + } + } + mListener.onChanged(); + } + } + /** dump class values, for debugging */ void dump(@NonNull PrintWriter pw) { SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt index 638924eeb2a3..b182ccef091e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt @@ -95,9 +95,9 @@ class BrightnessObserverTest { ) { ALL_ENABLED(true, true, CombinedVote( listOf(DisableRefreshRateSwitchingVote(true), - SupportedModesVote( - listOf(SupportedModesVote.SupportedMode(60f, 60f), - SupportedModesVote.SupportedMode(120f, 120f)))))), + SupportedRefreshRatesVote( + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f), + SupportedRefreshRatesVote.RefreshRates(120f, 120f)))))), VRR_NOT_SUPPORTED(false, true, DisableRefreshRateSwitchingVote(true)), VSYNC_VOTE_DISABLED(true, false, DisableRefreshRateSwitchingVote(true)) } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java new file mode 100644 index 000000000000..e93e5bc63dd8 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java @@ -0,0 +1,127 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.hardware.display.DisplayManagerInternal; +import android.util.SparseArray; +import android.view.Display; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.server.sensors.SensorManagerInternal; + +import junitparams.JUnitParamsRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(JUnitParamsRunner.class) +public class ProximitySensorObserverTest { + + private static final float FLOAT_TOLERANCE = 0.01f; + private static final int DISPLAY_ID = 1; + private static final SurfaceControl.RefreshRateRange REFRESH_RATE_RANGE = + new SurfaceControl.RefreshRateRange(60, 90); + + private final VotesStorage mStorage = new VotesStorage(() -> { }, null); + private final FakesInjector mInjector = new FakesInjector(); + private ProximitySensorObserver mSensorObserver; + + @Mock + DisplayManagerInternal mMockDisplayManagerInternal; + @Mock + SensorManagerInternal mMockSensorManagerInternal; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockDisplayManagerInternal.getRefreshRateForDisplayAndSensor(eq(DISPLAY_ID), + any(), any())).thenReturn(REFRESH_RATE_RANGE); + mSensorObserver = new ProximitySensorObserver(mStorage, mInjector); + mSensorObserver.observe(); + } + + @Test + public void testAddsProximityVoteIfSensorManagerProximityActive() { + mSensorObserver.onProximityActive(true); + + SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); + assertThat(displayVotes.size()).isEqualTo(1); + Vote vote = displayVotes.get(Vote.PRIORITY_PROXIMITY); + assertThat(vote).isNotNull(); + assertThat(vote).isInstanceOf(CombinedVote.class); + CombinedVote combinedVote = (CombinedVote) vote; + RefreshRateVote.PhysicalVote physicalVote = + (RefreshRateVote.PhysicalVote) combinedVote.mVotes.get(0); + assertThat(physicalVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(physicalVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90); + } + + @Test + public void testDoesNotAddProximityVoteIfSensorManagerProximityNotActive() { + mSensorObserver.onProximityActive(false); + + SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); + assertThat(displayVotes.size()).isEqualTo(0); + } + + @Test + public void testDoesNotAddProximityVoteIfDoze() { + mInjector.mDozeState = true; + mSensorObserver.onDisplayChanged(DISPLAY_ID); + mSensorObserver.onProximityActive(true); + + SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); + assertThat(displayVotes.size()).isEqualTo(0); + } + + private class FakesInjector extends DisplayModeDirectorTest.FakesInjector { + + private boolean mDozeState = false; + + @Override + public Display[] getDisplays() { + return new Display[] { createDisplay(DISPLAY_ID) }; + } + + @Override + public DisplayManagerInternal getDisplayManagerInternal() { + return mMockDisplayManagerInternal; + } + + @Override + public SensorManagerInternal getSensorManagerInternal() { + return mMockSensorManagerInternal; + } + + @Override + public boolean isDozeState(Display d) { + return mDozeState; + } + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt index ebb4f1889cd6..230317ba738b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt @@ -85,9 +85,9 @@ class SettingsObserverTest { internal val expectedVote: Vote? ) { ALL_ENABLED(true, true, true, - SupportedModesVote(listOf( - SupportedModesVote.SupportedMode(60f, 240f), - SupportedModesVote.SupportedMode(60f, 60f) + SupportedRefreshRatesVote(listOf( + SupportedRefreshRatesVote.RefreshRates(60f, 240f), + SupportedRefreshRatesVote.RefreshRates(60f, 60f) ))), LOW_POWER_OFF(true, true, false, null), DVRR_NOT_SUPPORTED_LOW_POWER_ON(false, true, true, diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt index 04e626536eba..6ce49b8cb31e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -27,12 +27,9 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SupportedModesVoteTest { - private val supportedModes = listOf( - SupportedModesVote.SupportedMode(60f, 90f ), - SupportedModesVote.SupportedMode(120f, 240f ) - ) + private val supportedModes = listOf(1, 2, 4) - private val otherMode = SupportedModesVote.SupportedMode(120f, 120f ) + private val otherMode = 5 private lateinit var supportedModesVote: SupportedModesVote @@ -42,31 +39,31 @@ class SupportedModesVoteTest { } @Test - fun `adds supported modes if supportedModes in summary is null`() { + fun `adds supported mode ids if supportedModeIds in summary is null`() { val summary = createVotesSummary() supportedModesVote.updateSummary(summary) - assertThat(summary.supportedModes).containsExactlyElementsIn(supportedModes) + assertThat(summary.supportedModeIds).containsExactlyElementsIn(supportedModes) } @Test - fun `does not add supported modes if summary has empty list of modes`() { + fun `does not add supported mode ids if summary has empty list of modeIds`() { val summary = createVotesSummary() - summary.supportedModes = ArrayList() + summary.supportedModeIds = ArrayList() supportedModesVote.updateSummary(summary) - assertThat(summary.supportedModes).isEmpty() + assertThat(summary.supportedModeIds).isEmpty() } @Test fun `filters out modes that does not match vote`() { val summary = createVotesSummary() - summary.supportedModes = ArrayList(listOf(otherMode, supportedModes[0])) + summary.supportedModeIds = ArrayList(listOf(otherMode, supportedModes[0])) supportedModesVote.updateSummary(summary) - assertThat(summary.supportedModes).containsExactly(supportedModes[0]) + assertThat(summary.supportedModeIds).containsExactly(supportedModes[0]) } }
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt new file mode 100644 index 000000000000..d0c112be24a2 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt @@ -0,0 +1,72 @@ +/* + * 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.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 SupportedRefreshRatesVoteTest { + private val refreshRates = listOf( + SupportedRefreshRatesVote.RefreshRates(60f, 90f), + SupportedRefreshRatesVote.RefreshRates(120f, 240f) + ) + + private val otherMode = SupportedRefreshRatesVote.RefreshRates(120f, 120f) + + private lateinit var supportedRefreshRatesVote: SupportedRefreshRatesVote + + @Before + fun setUp() { + supportedRefreshRatesVote = SupportedRefreshRatesVote(refreshRates) + } + + @Test + fun `adds supported refresh rates if supportedModes in summary is null`() { + val summary = createVotesSummary() + + supportedRefreshRatesVote.updateSummary(summary) + + assertThat(summary.supportedRefreshRates).containsExactlyElementsIn(refreshRates) + } + + @Test + fun `does not add supported refresh rates if summary has empty list of refresh rates`() { + val summary = createVotesSummary() + summary.supportedRefreshRates = ArrayList() + + supportedRefreshRatesVote.updateSummary(summary) + + assertThat(summary.supportedRefreshRates).isEmpty() + } + + @Test + fun `filters out supported refresh rates that does not match vote`() { + val summary = createVotesSummary() + summary.supportedRefreshRates = ArrayList(listOf(otherMode, refreshRates[0])) + + supportedRefreshRatesVote.updateSummary(summary) + + assertThat(summary.supportedRefreshRates).containsExactly(refreshRates[0]) + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt new file mode 100644 index 000000000000..c49205bcfe3d --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt @@ -0,0 +1,212 @@ +/* + * 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.IBinder +import android.os.RemoteException +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +private const val DISPLAY_ID = 1 +private const val DISPLAY_ID_OTHER = 2 + +@SmallTest +@RunWith(TestParameterInjector::class) +class SystemRequestObserverTest { + + + @get:Rule + val mockitoRule = MockitoJUnit.rule() + + private val mockToken = mock<IBinder>() + private val mockOtherToken = mock<IBinder>() + + private val storage = VotesStorage({}, null) + + @Test + fun `requestDisplayModes adds vote to storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size) + for (mode in requestedModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } + + @Test + fun `requestDisplayModes overrides votes in storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, intArrayOf(1, 2, 3)) + + val overrideModes = intArrayOf(10, 20, 30) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, overrideModes) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(overrideModes.size) + for (mode in overrideModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } + + @Test + fun `requestDisplayModes removes vote to storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(0) + } + + @Test + fun `requestDisplayModes calls linkToDeath to token`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + verify(mockToken).linkToDeath(any(), eq(0)) + } + + @Test + fun `does not add votes to storage if binder died when requestDisplayModes called`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + doThrow(RemoteException()).whenever(mockOtherToken).linkToDeath(any(), eq(0)) + systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedModes) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(0) + } + + @Test + fun `removes all votes from storage when binder dies`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>() + verify(mockToken).linkToDeath(deathRecipientCaptor.capture(), eq(0)) + + deathRecipientCaptor.lastValue.binderDied(mockToken) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(0) + } + + @Test + fun `calls unlinkToDeath on token when no votes remaining`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null) + + verify(mockToken).unlinkToDeath(any(), eq(0)) + } + + @Test + fun `does not call unlinkToDeath on token when votes for other display in storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID_OTHER, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null) + + verify(mockToken, never()).unlinkToDeath(any(), eq(0)) + } + + @Test + fun `requestDisplayModes subset modes from different tokens`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + val requestedOtherModes = intArrayOf(2, 3, 4) + systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes) + + verify(mockToken).linkToDeath(any(), eq(0)) + verify(mockOtherToken).linkToDeath(any(), eq(0)) + verify(mockToken, never()).unlinkToDeath(any(), eq(0)) + verify(mockOtherToken, never()).unlinkToDeath(any(), eq(0)) + + val expectedModes = intArrayOf(2, 3) + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(expectedModes.size) + for (mode in expectedModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } + + @Test + fun `recalculates vote if one binder dies`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + val requestedOtherModes = intArrayOf(2, 3, 4) + systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes) + + val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>() + verify(mockOtherToken).linkToDeath(deathRecipientCaptor.capture(), eq(0)) + deathRecipientCaptor.lastValue.binderDied(mockOtherToken) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size) + for (mode in requestedModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt index 910e03c5db85..6b90bde188c5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt @@ -18,10 +18,10 @@ package com.android.server.display.mode internal fun createVotesSummary( isDisplayResolutionRangeVotingEnabled: Boolean = true, - vsyncProximityVoteEnabled: Boolean = true, + supportedModesVoteEnabled: Boolean = true, loggingEnabled: Boolean = true, supportsFrameRateOverride: Boolean = true ): VoteSummary { - return VoteSummary(isDisplayResolutionRangeVotingEnabled, vsyncProximityVoteEnabled, + return VoteSummary(isDisplayResolutionRangeVotingEnabled, supportedModesVoteEnabled, loggingEnabled, supportsFrameRateOverride) }
\ 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 d6c84690e65f..04b35f10545f 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 @@ -28,29 +28,29 @@ import org.junit.runner.RunWith @RunWith(TestParameterInjector::class) class VoteSummaryTest { - enum class SupportedModesVoteTestCase( - val vsyncProximityVoteEnabled: Boolean, - internal val summarySupportedModes: List<SupportedModesVote.SupportedMode>?, + enum class SupportedRefreshRatesTestCase( + val supportedModesVoteEnabled: Boolean, + internal val summaryRefreshRates: List<SupportedRefreshRatesVote.RefreshRates>?, val modesToFilter: Array<Display.Mode>, val expectedModeIds: List<Int> ) { HAS_NO_MATCHING_VOTE(true, - listOf(SupportedModesVote.SupportedMode(60f, 60f)), + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f)), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), createMode(3, 60f, 90f)), listOf() ), HAS_SINGLE_MATCHING_VOTE(true, - listOf(SupportedModesVote.SupportedMode(60f, 90f)), + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f)), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), createMode(3, 60f, 90f)), listOf(3) ), HAS_MULTIPLE_MATCHING_VOTES(true, - listOf(SupportedModesVote.SupportedMode(60f, 90f), - SupportedModesVote.SupportedMode(90f, 90f)), + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f), + SupportedRefreshRatesVote.RefreshRates(90f, 90f)), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), createMode(3, 60f, 90f)), @@ -70,7 +70,69 @@ class VoteSummaryTest { createMode(3, 60f, 90f)), listOf(1, 2, 3) ), - HAS_VSYNC_PROXIMITY_DISABLED(false, + HAS_SUPPORTED_MODES_VOTE_DISABLED(false, + listOf(), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(1, 2, 3) + ), + } + + @Test + fun `filters modes for summary supportedRefreshRates`( + @TestParameter testCase: SupportedRefreshRatesTestCase + ) { + val summary = createSummary(testCase.supportedModesVoteEnabled) + summary.supportedRefreshRates = testCase.summaryRefreshRates + + val result = summary.filterModes(testCase.modesToFilter) + + assertThat(result.map { it.modeId }).containsExactlyElementsIn(testCase.expectedModeIds) + } + + enum class SupportedModesTestCase( + val supportedModesVoteEnabled: Boolean, + internal val summarySupportedModes: List<Int>?, + val modesToFilter: Array<Display.Mode>, + val expectedModeIds: List<Int> + ) { + HAS_NO_MATCHING_VOTE(true, + listOf(4, 5), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf() + ), + HAS_SINGLE_MATCHING_VOTE(true, + listOf(3), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(3) + ), + HAS_MULTIPLE_MATCHING_VOTES(true, + listOf(1, 3), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(1, 3) + ), + HAS_NO_SUPPORTED_MODES(true, + listOf(), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf() + ), + HAS_NULL_SUPPORTED_MODES(true, + null, + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(1, 2, 3) + ), + HAS_SUPPORTED_MODES_VOTE_DISABLED(false, listOf(), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), @@ -81,10 +143,10 @@ class VoteSummaryTest { @Test fun `filters modes for summary supportedModes`( - @TestParameter testCase: SupportedModesVoteTestCase + @TestParameter testCase: SupportedModesTestCase ) { - val summary = createSummary(testCase.vsyncProximityVoteEnabled) - summary.supportedModes = testCase.summarySupportedModes + val summary = createSummary(testCase.supportedModesVoteEnabled) + summary.supportedModeIds = testCase.summarySupportedModes val result = summary.filterModes(testCase.modesToFilter) @@ -96,8 +158,8 @@ private fun createMode(modeId: Int, refreshRate: Float, vsyncRate: Float): Displ FloatArray(0), IntArray(0)) } -private fun createSummary(vsyncVoteEnabled: Boolean): VoteSummary { - val summary = createVotesSummary(vsyncProximityVoteEnabled = vsyncVoteEnabled) +private fun createSummary(supportedModesVoteEnabled: Boolean): VoteSummary { + val summary = createVotesSummary(supportedModesVoteEnabled = supportedModesVoteEnabled) summary.width = 600 summary.height = 800 summary.maxPhysicalRefreshRate = Float.POSITIVE_INFINITY diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java index 1f6f1a41bea7..a248d6de118f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java @@ -18,6 +18,7 @@ package com.android.server.display.mode; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -153,4 +154,51 @@ public class VotesStorageTest { assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0); verify(mVotesListener, never()).onChanged(); } + + + @Test + public void removesAllVotesForPriority() { + // GIVEN vote storage with votes + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER); + // WHEN removeAllVotesForPriority is called + mVotesStorage.removeAllVotesForPriority(PRIORITY); + // THEN votes with priority are removed from the storage + SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isNull(); + votes = mVotesStorage.getVotes(DISPLAY_ID_OTHER); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isNull(); + } + + @Test + public void removesAllVotesForPriority_notifiesListenerOnce() { + // GIVEN vote storage with votes + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER); + clearInvocations(mVotesListener); + // WHEN removeAllVotesForPriority is called + mVotesStorage.removeAllVotesForPriority(PRIORITY); + // THEN listener notified once + verify(mVotesListener).onChanged(); + } + + @Test + public void removesAllVotesForPriority_noChangesIfNothingRemoved() { + // GIVEN vote storage with votes + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + clearInvocations(mVotesListener); + // WHEN removeAllVotesForPriority is called for missing priority + mVotesStorage.removeAllVotesForPriority(PRIORITY_OTHER); + // THEN no changes to votes storage + SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isEqualTo(VOTE); + verify(mVotesListener, never()).onChanged(); + } } |