diff options
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; + } + } +} |