summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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/DisplayModeDirector.java134
-rw-r--r--services/core/java/com/android/server/display/mode/ProximitySensorObserver.java138
-rw-r--r--services/core/java/com/android/server/display/mode/SystemRequestObserver.java113
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java127
11 files changed, 461 insertions, 121 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 8ea742d19047..3be9deb738e8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8149,6 +8149,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 ce7c22438d54..1a1f575f0bfb 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;
@@ -4530,6 +4531,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 15ee9372b46a..aff3c2a18b29 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,
@@ -233,8 +233,8 @@ public class DisplayManagerFlags {
return mBrightnessIntRangeUserPerceptionFlagState.isEnabled();
}
- public boolean isVsyncProximityVoteEnabled() {
- return mVsyncProximityVote.isEnabled();
+ public boolean isRestrictDisplayModesEnabled() {
+ return mRestrictDisplayModes.isEnabled();
}
public boolean isVsyncLowPowerVoteEnabled() {
@@ -294,7 +294,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 9bf36e4e605f..b472e02efb8b 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/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 64cbd5488d90..01f464d5592a 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.
@@ -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/SystemRequestObserver.java b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java
new file mode 100644
index 000000000000..068c679b3457
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java
@@ -0,0 +1,113 @@
+/*
+ * 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.Nullable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * SystemRequestObserver responsible for handling system requests to filter allowable display
+ * modes
+ */
+class SystemRequestObserver {
+ private static final String TAG = "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<int[]>> 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, int[] modeIds) {
+ try {
+ boolean needLinkToDeath = false;
+ synchronized (mLock) {
+ SparseArray<int[]> existingRestrictionForBinder = mDisplaysRestrictions.get(token);
+ if (existingRestrictionForBinder == null) {
+ needLinkToDeath = true;
+ existingRestrictionForBinder = new SparseArray<>();
+ mDisplaysRestrictions.put(token, existingRestrictionForBinder);
+ }
+ existingRestrictionForBinder.put(displayId, modeIds);
+
+ // aggregate modes for display and update vote storage
+ }
+ if (needLinkToDeath) {
+ token.linkToDeath(mDeathRecipient, 0);
+ }
+ } catch (RemoteException re) {
+ removeSystemRequestedVotes(token);
+ }
+ }
+
+ private void removeSystemRequestedVote(IBinder token, int displayId) {
+ boolean needToUnlink = false;
+ synchronized (mLock) {
+ SparseArray<int[]> existingRestrictionForBinder = mDisplaysRestrictions.get(token);
+ if (existingRestrictionForBinder != null) {
+ existingRestrictionForBinder.remove(displayId);
+ needToUnlink = existingRestrictionForBinder.size() == 0;
+
+ // aggregate modes for display and update vote storage
+ }
+ }
+ if (needToUnlink) {
+ token.unlinkToDeath(mDeathRecipient, 0);
+ }
+ }
+
+ private void removeSystemRequestedVotes(IBinder token) {
+ synchronized (mLock) {
+ mDisplaysRestrictions.remove(token);
+
+ // aggregate modes for display and update vote storage
+ }
+ }
+}
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;
+ }
+ }
+}