From e651abc2957bc20731887f723eb605aa4ebf5cbc Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Sat, 10 Apr 2021 01:43:01 +0100 Subject: [PATCH] Add DisplayModeDirector support for UDFPS UDFPS needs to lock to a given refresh rate in order to function properly, so we register a listener for when it's enabled in order to make sure it gets the right refresh rates. Bug: 181682258 Test: atest DisplayModeDirectorTest Change-Id: I54afb12f8e4f03a3b0c384112fc2d9b1806de719 --- .../fingerprint/IUdfpsHbmListener.aidl | 7 ++ .../android/systemui/biometrics/HbmTypes.java | 5 +- .../server/display/DisplayModeDirector.java | 111 ++++++++++++++++-- .../statusbar/StatusBarManagerInternal.java | 8 ++ .../statusbar/StatusBarManagerService.java | 30 ++++- .../display/DisplayModeDirectorTest.java | 69 +++++++++-- 6 files changed, 205 insertions(+), 25 deletions(-) diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl index b79d6e0f9dfe4..1551e0742186b 100644 --- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl +++ b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl @@ -24,6 +24,13 @@ package android.hardware.fingerprint; * @hide */ oneway interface IUdfpsHbmListener { + + /** HBM that applies to the whole screen. */ + const int GLOBAL_HBM = 0; + + /** HBM that only applies to a portion of the screen. */ + const int LOCAL_HBM = 1; + /** * UdfpsController will call this method when the HBM is enabled. * diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/HbmTypes.java b/packages/SystemUI/src/com/android/systemui/biometrics/HbmTypes.java index 96ee0f7be919d..f798005daabb1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/HbmTypes.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/HbmTypes.java @@ -17,6 +17,7 @@ package com.android.systemui.biometrics; import android.annotation.IntDef; +import android.hardware.fingerprint.IUdfpsHbmListener; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,10 +27,10 @@ import java.lang.annotation.RetentionPolicy; */ public final class HbmTypes { /** HBM that applies to the whole screen. */ - public static final int GLOBAL_HBM = 0; + public static final int GLOBAL_HBM = IUdfpsHbmListener.GLOBAL_HBM; /** HBM that only applies to a portion of the screen. */ - public static final int LOCAL_HBM = 1; + public static final int LOCAL_HBM = IUdfpsHbmListener.LOCAL_HBM; @Retention(RetentionPolicy.SOURCE) @IntDef({GLOBAL_HBM, LOCAL_HBM}) diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 4bbd33817d28a..67779a2e16854 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -27,6 +27,7 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.DisplayManager; +import android.hardware.fingerprint.IUdfpsHbmListener; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -40,14 +41,17 @@ import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.Display; import android.view.DisplayInfo; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; import com.android.server.display.utils.AmbientFilter; import com.android.server.display.utils.AmbientFilterFactory; +import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.utils.DeviceConfigInterface; import java.io.PrintWriter; @@ -93,6 +97,7 @@ public class DisplayModeDirector { private final AppRequestObserver mAppRequestObserver; private final SettingsObserver mSettingsObserver; private final DisplayObserver mDisplayObserver; + private final UdfpsObserver mUdfpsObserver; private final DeviceConfigInterface mDeviceConfig; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @@ -133,6 +138,7 @@ public class DisplayModeDirector { mSettingsObserver = new SettingsObserver(context, handler); mDisplayObserver = new DisplayObserver(context, handler); mBrightnessObserver = new BrightnessObserver(context, handler); + mUdfpsObserver = new UdfpsObserver(); mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); mDeviceConfig = injector.getDeviceConfig(); mAlwaysRespectAppRequest = false; @@ -149,6 +155,7 @@ public class DisplayModeDirector { mSettingsObserver.observe(); mDisplayObserver.observe(); mBrightnessObserver.observe(sensorManager); + mUdfpsObserver.observe(); synchronized (mLock) { // We may have a listener already registered before the call to start, so go ahead and // notify them to pick up our newly initialized state. @@ -545,6 +552,7 @@ public class DisplayModeDirector { mSettingsObserver.dumpLocked(pw); mAppRequestObserver.dumpLocked(pw); mBrightnessObserver.dumpLocked(pw); + mUdfpsObserver.dumpLocked(pw); } } @@ -566,7 +574,6 @@ public class DisplayModeDirector { } final SparseArray votes = getOrCreateVotesByDisplay(displayId); - Vote currentVote = votes.get(priority); if (vote != null) { votes.put(priority, vote); } else { @@ -649,6 +656,11 @@ public class DisplayModeDirector { return mSettingsObserver; } + @VisibleForTesting + UdfpsObserver getUdpfsObserver() { + return mUdfpsObserver; + } + @VisibleForTesting DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings( @@ -928,11 +940,15 @@ public class DisplayModeDirector { // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on. public static final int PRIORITY_LOW_POWER_MODE = 6; + // 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. + public static final int PRIORITY_UDFPS = 7; + // 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. public static final int MIN_PRIORITY = PRIORITY_DEFAULT_REFRESH_RATE; - public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE; + public static final int MAX_PRIORITY = PRIORITY_UDFPS; // The cutoff for the app request refresh rate range. Votes with priorities lower than this // value will not be considered when constructing the app request refresh rate range. @@ -989,6 +1005,9 @@ public class DisplayModeDirector { return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE"; case PRIORITY_LOW_POWER_MODE: return "PRIORITY_LOW_POWER_MODE"; + case PRIORITY_UDFPS: + return "PRIORITY_UDFPS"; + default: return Integer.toString(priority); } @@ -1162,7 +1181,7 @@ public class DisplayModeDirector { } final class AppRequestObserver { - private SparseArray mAppRequestedModeByDisplay; + private final SparseArray mAppRequestedModeByDisplay; AppRequestObserver() { mAppRequestedModeByDisplay = new SparseArray<>(); @@ -1196,7 +1215,6 @@ public class DisplayModeDirector { updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote); updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote); - return; } private Display.Mode findModeByIdLocked(int displayId, int modeId) { @@ -1328,7 +1346,8 @@ public class DisplayModeDirector { private SensorManager mSensorManager; private Sensor mLightSensor; - private LightSensorEventListener mLightSensorListener = new LightSensorEventListener(); + private final LightSensorEventListener mLightSensorListener = + new LightSensorEventListener(); // Take it as low brightness before valid sensor data comes private float mAmbientLux = -1.0f; private AmbientFilter mAmbientFilter; @@ -1559,8 +1578,7 @@ public class DisplayModeDirector { mLightSensorListener.dumpLocked(pw); if (mAmbientFilter != null) { - IndentingPrintWriter ipw = new IndentingPrintWriter(pw); - ipw.setIndent(" "); + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); mAmbientFilter.dump(ipw); } } @@ -1907,7 +1925,7 @@ public class DisplayModeDirector { return false; } - private Runnable mInjectSensorEventRunnable = new Runnable() { + private final Runnable mInjectSensorEventRunnable = new Runnable() { @Override public void run() { long now = SystemClock.uptimeMillis(); @@ -1926,6 +1944,83 @@ public class DisplayModeDirector { } } + private class UdfpsObserver extends IUdfpsHbmListener.Stub { + private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray(); + private final SparseBooleanArray mGlobalHbmEnabled = new SparseBooleanArray(); + + public void observe() { + StatusBarManagerInternal statusBar = + LocalServices.getService(StatusBarManagerInternal.class); + statusBar.setUdfpsHbmListener(this); + } + + @Override + public void onHbmEnabled(int hbmType, int displayId) { + synchronized (mLock) { + updateHbmStateLocked(hbmType, displayId, true /*enabled*/); + } + } + + @Override + public void onHbmDisabled(int hbmType, int displayId) { + synchronized (mLock) { + updateHbmStateLocked(hbmType, displayId, false /*enabled*/); + } + } + + private void updateHbmStateLocked(int hbmType, int displayId, boolean enabled) { + switch (hbmType) { + case UdfpsObserver.LOCAL_HBM: + mLocalHbmEnabled.put(displayId, enabled); + break; + case UdfpsObserver.GLOBAL_HBM: + mGlobalHbmEnabled.put(displayId, enabled); + break; + default: + Slog.w(TAG, "Unknown HBM type reported. Ignoring."); + return; + } + updateVoteLocked(displayId); + } + + private void updateVoteLocked(int displayId) { + final Vote vote; + if (mGlobalHbmEnabled.get(displayId)) { + vote = Vote.forRefreshRates(60f, 60f); + } else if (mLocalHbmEnabled.get(displayId)) { + Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); + float maxRefreshRate = 0f; + for (Display.Mode mode : modes) { + if (mode.getRefreshRate() > maxRefreshRate) { + maxRefreshRate = mode.getRefreshRate(); + } + } + vote = Vote.forRefreshRates(maxRefreshRate, maxRefreshRate); + } else { + vote = null; + } + + DisplayModeDirector.this.updateVoteLocked(displayId, Vote.PRIORITY_UDFPS, vote); + } + + void dumpLocked(PrintWriter pw) { + pw.println(" UdfpsObserver"); + pw.println(" mLocalHbmEnabled: "); + for (int i = 0; i < mLocalHbmEnabled.size(); i++) { + final int displayId = mLocalHbmEnabled.keyAt(i); + final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled"; + pw.println(" Display " + displayId + ": " + enabled); + } + pw.println(" mGlobalHbmEnabled: "); + for (int i = 0; i < mGlobalHbmEnabled.size(); i++) { + final int displayId = mGlobalHbmEnabled.keyAt(i); + final String enabled = mGlobalHbmEnabled.valueAt(i) ? "enabled" : "disabled"; + pw.println(" Display " + displayId + ": " + enabled); + } + + } + } + private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener { public DeviceConfigDisplaySettings() { } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 970420a284d68..a436e6b3787bd 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -18,6 +18,7 @@ package com.android.server.statusbar; import android.annotation.Nullable; import android.app.ITransientNotificationCallback; +import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -166,4 +167,11 @@ public interface StatusBarManagerInternal { * boolean) */ void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable); + + /** + * Sets the system-wide listener for UDFPS HBM status changes. + * + * @see com.android.internal.statusbar.IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener) + */ + void setUdfpsHbmListener(IUdfpsHbmListener listener); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index c4f5575c22abd..e37edebb09ba0 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -107,15 +107,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private final Context mContext; - private Handler mHandler = new Handler(); + private final Handler mHandler = new Handler(); private NotificationDelegate mNotificationDelegate; private volatile IStatusBar mBar; - private ArrayMap mIcons = new ArrayMap<>(); + private final ArrayMap mIcons = new ArrayMap<>(); // for disabling the status bar private final ArrayList mDisableRecords = new ArrayList(); private GlobalActionsProvider.GlobalActionsListener mGlobalActionListener; - private IBinder mSysUiVisToken = new Binder(); + private final IBinder mSysUiVisToken = new Binder(); private final Object mLock = new Object(); private final DeathRecipient mDeathRecipient = new DeathRecipient(); @@ -123,7 +123,9 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private int mCurrentUserId; private boolean mTracingEnabled; - private SparseArray mDisplayUiState = new SparseArray<>(); + private final SparseArray mDisplayUiState = new SparseArray<>(); + @GuardedBy("mLock") + private IUdfpsHbmListener mUdfpsHbmListener; private class DeathRecipient implements IBinder.DeathRecipient { public void binderDied() { @@ -598,6 +600,18 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } catch (RemoteException ex) { } } } + + @Override + public void setUdfpsHbmListener(IUdfpsHbmListener listener) { + synchronized (mLock) { + mUdfpsHbmListener = listener; + } + if (mBar != null) { + try { + mBar.setUdfpsHbmListener(listener); + } catch (RemoteException ex) { } + } + } }; private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() { @@ -1204,6 +1218,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D if (mGlobalActionListener == null) return; mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null); }); + // If StatusBarService dies, system_server doesn't get killed with it, so we need to make + // sure the UDFPS listener is refreshed as well. Deferring to the handler just so to avoid + // making registerStatusBar re-entrant. + mHandler.post(() -> { + synchronized (mLock) { + setUdfpsHbmListener(mUdfpsHbmListener); + } + }); } /** diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index c8099e2467a1d..cda659f05adf0 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -27,10 +27,13 @@ import static com.android.server.display.DisplayModeDirector.Vote.PRIORITY_FLICK import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,6 +48,7 @@ import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.DisplayManager; +import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -61,9 +65,11 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.Preconditions; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; +import com.android.server.LocalServices; import com.android.server.display.DisplayModeDirector.BrightnessObserver; import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs; import com.android.server.display.DisplayModeDirector.Vote; +import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.testutils.FakeDeviceConfigInterface; import org.junit.Before; @@ -71,6 +77,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -95,6 +102,8 @@ public class DisplayModeDirectorTest { private Handler mHandler; @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + @Mock + public StatusBarManagerInternal mStatusBarMock; @Before public void setUp() throws Exception { @@ -104,6 +113,9 @@ public class DisplayModeDirectorTest { when(mContext.getContentResolver()).thenReturn(resolver); mInjector = new FakesInjector(); mHandler = new Handler(Looper.getMainLooper()); + + LocalServices.removeServiceForTest(StatusBarManagerInternal.class); + LocalServices.addService(StatusBarManagerInternal.class, mStatusBarMock); } private DisplayModeDirector createDirectorFromRefreshRateArray( @@ -338,7 +350,7 @@ public class DisplayModeDirectorTest { void verifyBrightnessObserverCall(DisplayModeDirector director, float minFps, float peakFps, float defaultFps, float brightnessObserverMin, float brightnessObserverMax) { - BrightnessObserver brightnessObserver = Mockito.mock(BrightnessObserver.class); + BrightnessObserver brightnessObserver = mock(BrightnessObserver.class); director.injectBrightnessObserver(brightnessObserver); director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(minFps, peakFps, defaultFps); verify(brightnessObserver) @@ -581,7 +593,7 @@ public class DisplayModeDirectorTest { listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/)); Vote vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER); - assertVoteForRefreshRateLocked(vote, 90 /*fps*/); + assertVoteForRefreshRate(vote, 90 /*fps*/); setBrightness(125); // Sensor reads 1000 lux, @@ -611,7 +623,7 @@ public class DisplayModeDirectorTest { ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); - Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1))) + verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1))) .registerListener( listenerCaptor.capture(), eq(lightSensor), @@ -631,7 +643,7 @@ public class DisplayModeDirectorTest { listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000)); vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER); - assertVoteForRefreshRateLocked(vote, 60 /*fps*/); + assertVoteForRefreshRate(vote, 60 /*fps*/); } @Test @@ -654,21 +666,21 @@ public class DisplayModeDirectorTest { director.start(sensorManager); ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); - Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1))) + verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1))) .registerListener( listenerCaptor.capture(), eq(lightSensor), anyInt(), any(Handler.class)); - // Dispaly state changed from On to Doze + // Display state changed from On to Doze director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_DOZE); - Mockito.verify(sensorManager) + verify(sensorManager) .unregisterListener(listenerCaptor.capture()); - // Dispaly state changed from Doze to On + // Display state changed from Doze to On director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); - Mockito.verify(sensorManager, times(2)) + verify(sensorManager, times(2)) .registerListener( listenerCaptor.capture(), eq(lightSensor), @@ -677,7 +689,42 @@ public class DisplayModeDirectorTest { } - private void assertVoteForRefreshRateLocked(Vote vote, float refreshRate) { + @Test + public void testUdfpsListenerGetsRegistered() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0); + verify(mStatusBarMock, never()).setUdfpsHbmListener(any()); + + director.start(createMockSensorManager()); + verify(mStatusBarMock).setUdfpsHbmListener(eq(director.getUdpfsObserver())); + } + + @Test + public void testGbhmVotesFor60hz() throws Exception { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0); + director.start(createMockSensorManager()); + ArgumentCaptor captor = + ArgumentCaptor.forClass(IUdfpsHbmListener.class); + verify(mStatusBarMock).setUdfpsHbmListener(captor.capture()); + IUdfpsHbmListener hbmListener = captor.getValue(); + + // Should be no vote initially + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS); + assertNull(vote); + + // Enabling GHBM votes for 60hz + hbmListener.onHbmEnabled(IUdfpsHbmListener.GLOBAL_HBM, DISPLAY_ID); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS); + assertVoteForRefreshRate(vote, 60.f); + + // Disabling GHBM removes the vote + hbmListener.onHbmDisabled(IUdfpsHbmListener.GLOBAL_HBM, DISPLAY_ID); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS); + assertNull(vote); + } + + private void assertVoteForRefreshRate(Vote vote, float refreshRate) { assertThat(vote).isNotNull(); final DisplayModeDirector.RefreshRateRange expectedRange = new DisplayModeDirector.RefreshRateRange(refreshRate, refreshRate); @@ -787,7 +834,7 @@ public class DisplayModeDirectorTest { } private static SensorManager createMockSensorManager(Sensor... sensors) { - SensorManager sensorManager = Mockito.mock(SensorManager.class); + SensorManager sensorManager = mock(SensorManager.class); when(sensorManager.getSensorList(anyInt())).then((invocation) -> { List requestedSensors = new ArrayList<>(); int type = invocation.getArgument(0);