summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Oleg Petšjonkin <petsjonkin@google.com> 2024-03-14 16:52:58 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-03-14 16:52:58 +0000
commit87793a4114e289437b6cf83a124ea76144996256 (patch)
tree0ff8d5cb1c904b08b269210a4eef3ba6491ad73f
parent1dbe381c9a272dc89a235a22880bd4ff7b7a627a (diff)
parent0e4a0aa5e10cc3a49901d562e225361f8030abb8 (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
-rw-r--r--core/java/android/hardware/display/DisplayManager.java16
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java17
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl4
-rw-r--r--core/res/AndroidManifest.xml8
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java9
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java12
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig4
-rw-r--r--services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java4
-rw-r--r--services/core/java/com/android/server/display/mode/CombinedVote.java4
-rw-r--r--services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java4
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java146
-rw-r--r--services/core/java/com/android/server/display/mode/ProximitySensorObserver.java138
-rw-r--r--services/core/java/com/android/server/display/mode/RefreshRateVote.java6
-rw-r--r--services/core/java/com/android/server/display/mode/SizeVote.java4
-rw-r--r--services/core/java/com/android/server/display/mode/SupportedModesVote.java69
-rw-r--r--services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java94
-rw-r--r--services/core/java/com/android/server/display/mode/SystemRequestObserver.java139
-rw-r--r--services/core/java/com/android/server/display/mode/Vote.java34
-rw-r--r--services/core/java/com/android/server/display/mode/VoteSummary.java47
-rw-r--r--services/core/java/com/android/server/display/mode/VotesStatsReporter.java8
-rw-r--r--services/core/java/com/android/server/display/mode/VotesStorage.java39
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java127
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt23
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt72
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt212
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt88
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java48
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();
+ }
}