summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author petsjonkin <petsjonkin@google.com> 2023-11-08 22:03:09 +0000
committer Oleg Petšjonkin <petsjonkin@google.com> 2024-03-14 09:52:18 +0000
commit4e4a917477c391ac0a8b43fcd34010e41efa3fd9 (patch)
tree2c9cb59d54f33a98629517e4a9b275119a439919
parentd0fa42223df9be45f9d37c15cb650455a20eee71 (diff)
Adding new API to restrict Display modes to specific ids
Part of VRR support for DisplayManager Allows internal application to request specific display modes As a part of this CL, extracted ProximitySensorObserver to separate class and added tests Test: atest SensorObserverTest Bug: b/309466682 API-Coverage-Bug: b/313916551 Change-Id: I90b6ac36849fa9d0c10857300b9fedaafd04fdad
-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;
+ }
+ }
+}