Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f6eb03ea authored by Ilya Matyukhin's avatar Ilya Matyukhin
Browse files

Remove HBM logic from the UDFPS overlay

This change removes the high-brightness mode logic, and provides hooks
for the subclasses to implement this logic instead.

This also changes the layout of the overlay from being a fullscreen
window, to a window that's the exact size of the sensor.

Bug: 177563105
Test: atest UdfpsControllerTest
Change-Id: Ie5dd4ea862e07c862feb9ae09ed9c602b32849e1
parent 5fe6130c
Loading
Loading
Loading
Loading
+48 −179
Original line number Diff line number Diff line
@@ -22,29 +22,22 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.Spline;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.WindowManager;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
@@ -53,10 +46,6 @@ import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.SystemSettings;

import java.io.FileWriter;
import java.io.IOException;

import javax.inject.Inject;

@@ -73,16 +62,13 @@ import javax.inject.Inject;
 */
@SuppressWarnings("deprecation")
@SysUISingleton
public class UdfpsController implements DozeReceiver {
public class UdfpsController implements UdfpsView.HbmCallback, DozeReceiver {
    private static final String TAG = "UdfpsController";
    // Gamma approximation for the sRGB color space.
    private static final float DISPLAY_GAMMA = 2.2f;
    private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;

    private final Context mContext;
    private final FingerprintManager mFingerprintManager;
    private final WindowManager mWindowManager;
    private final SystemSettings mSystemSettings;
    private final DelayableExecutor mFgExecutor;
    private final StatusBarStateController mStatusBarStateController;
    // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
@@ -90,23 +76,6 @@ public class UdfpsController implements DozeReceiver {
    @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
    private final WindowManager.LayoutParams mCoreLayoutParams;
    private final UdfpsView mView;
    // Debugfs path to control the high-brightness mode.
    private final String mHbmPath;
    private final String mHbmEnableCommand;
    private final String mHbmDisableCommand;
    private final boolean mHbmSupported;
    // Brightness in nits in the high-brightness mode.
    private final float mMaxNits;
    // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to a
    // brightness in nits.
    private final Spline mBacklightToNitsSpline;
    // A spline mapping from a value in nits to a backlight value of a hypothetical panel whose
    // maximum backlight value corresponds to our panel's high-brightness mode.
    // The output is normalized to the range [0, 1.0].
    private Spline mNitsToHbmBacklightSpline;
    // Default non-HBM backlight value normalized to the range [0, 1.0]. Used as a fallback when the
    // actual brightness value cannot be retrieved.
    private final float mDefaultBrightness;
    // Indicates whether the overlay is currently showing. Even if it has been requested, it might
    // not be showing.
    private boolean mIsOverlayShowing;
@@ -152,7 +121,7 @@ public class UdfpsController implements DozeReceiver {
    @SuppressLint("ClickableViewAccessibility")
    private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> {
        UdfpsView view = (UdfpsView) v;
        final boolean isFingerDown = view.isShowScrimAndDot();
        final boolean isFingerDown = view.isIlluminationRequested();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
@@ -183,9 +152,7 @@ public class UdfpsController implements DozeReceiver {
            @Main Resources resources,
            LayoutInflater inflater,
            @Nullable FingerprintManager fingerprintManager,
            DisplayManager displayManager,
            WindowManager windowManager,
            SystemSettings systemSettings,
            @NonNull StatusBarStateController statusBarStateController,
            @Main DelayableExecutor fgExecutor,
            @NonNull ScrimController scrimController) {
@@ -194,7 +161,6 @@ public class UdfpsController implements DozeReceiver {
        // fingerprint manager should never be null.
        mFingerprintManager = checkNotNull(fingerprintManager);
        mWindowManager = windowManager;
        mSystemSettings = systemSettings;
        mFgExecutor = fgExecutor;
        mStatusBarStateController = statusBarStateController;

@@ -211,72 +177,18 @@ public class UdfpsController implements DozeReceiver {
                PixelFormat.TRANSLUCENT);
        mCoreLayoutParams.setTitle(TAG);
        mCoreLayoutParams.setFitInsetsTypes(0);
        mCoreLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        mCoreLayoutParams.layoutInDisplayCutoutMode =
                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;

        mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
        mView.setSensorProperties(mSensorProps);
        mView.setHbmCallback(this);

        mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path);
        mHbmEnableCommand = resources.getString(R.string.udfps_hbm_enable_command);
        mHbmDisableCommand = resources.getString(R.string.udfps_hbm_disable_command);

        mHbmSupported = !TextUtils.isEmpty(mHbmPath);
        mView.setHbmSupported(mHbmSupported);
        scrimController.addScrimChangedListener(mView);
        statusBarStateController.addCallback(mView);

        // This range only consists of the minimum and maximum values, which only cover
        // non-high-brightness mode.
        float[] nitsRange = toFloatArray(resources.obtainTypedArray(
                com.android.internal.R.array.config_screenBrightnessNits));
        if (nitsRange.length < 2) {
            throw new IllegalArgumentException(
                    String.format("nitsRange.length: %d. Must be >= 2", nitsRange.length));
        }

        // The last value of this range corresponds to the high-brightness mode.
        float[] nitsAutoBrightnessValues = toFloatArray(resources.obtainTypedArray(
                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
        if (nitsAutoBrightnessValues.length < 2) {
            throw new IllegalArgumentException(
                    String.format("nitsAutoBrightnessValues.length: %d. Must be >= 2",
                            nitsAutoBrightnessValues.length));
        }

        mMaxNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1];
        float[] hbmNitsRange = nitsRange.clone();
        hbmNitsRange[hbmNitsRange.length - 1] = mMaxNits;

        // This range only consists of the minimum and maximum backlight values, which only apply
        // in non-high-brightness mode.
        float[] normalizedBacklightRange = normalizeBacklightRange(
                resources.getIntArray(
                        com.android.internal.R.array.config_screenBrightnessBacklight));
        if (normalizedBacklightRange.length < 2) {
            throw new IllegalArgumentException(
                    String.format("normalizedBacklightRange.length: %d. Must be >= 2",
                            normalizedBacklightRange.length));
        }
        if (normalizedBacklightRange.length != nitsRange.length) {
            throw new IllegalArgumentException(
                    "normalizedBacklightRange.length != nitsRange.length");
        }

        mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange);
        mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange);
        mDefaultBrightness = obtainDefaultBrightness(mContext);

        // TODO(b/160025856): move to the "dump" method.
        Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0],
                nitsRange[nitsRange.length - 1]));
        Log.v(TAG, String.format("ctor | mHbmNitsRange: [%f, %f]", hbmNitsRange[0],
                hbmNitsRange[hbmNitsRange.length - 1]));
        Log.v(TAG, String.format("ctor | mNormalizedBacklightRange: [%f, %f]",
                normalizedBacklightRange[0],
                normalizedBacklightRange[normalizedBacklightRange.length - 1]));

        mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
        mIsOverlayShowing = false;
    }
@@ -331,13 +243,33 @@ public class UdfpsController implements DozeReceiver {
    }

    private WindowManager.LayoutParams computeLayoutParams() {
        // Default dimensions assume portrait mode.
        mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius;
        mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
        mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius;
        mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius;

        Point p = new Point();
        // Gets the size based on the current rotation of the display.
        mContext.getDisplay().getRealSize(p);
        mCoreLayoutParams.width = p.x;
        mCoreLayoutParams.x = p.x;
        mCoreLayoutParams.height = p.y;
        mCoreLayoutParams.y = p.y;

        // Transform dimensions if the device is in landscape mode.
        switch (mContext.getDisplay().getRotation()) {
            case Surface.ROTATION_90:
                mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
                mCoreLayoutParams.y =
                        p.y - mSensorProps.sensorLocationX - mSensorProps.sensorRadius;
                break;

            case Surface.ROTATION_270:
                mCoreLayoutParams.x =
                        p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
                mCoreLayoutParams.y = mSensorProps.sensorLocationX - mSensorProps.sensorRadius;
                break;

            default:
                // Do nothing to stay in portrait mode.
        }
        return mCoreLayoutParams;
    }

@@ -402,36 +334,10 @@ public class UdfpsController implements DozeReceiver {
        });
    }

    // Returns a value in the range of [0, 255].
    private int computeScrimOpacity() {
        // Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f].
        float backlightSetting = mSystemSettings.getFloatForUser(
                Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness,
                UserHandle.USER_CURRENT);

        // Constrain the backlight setting to [0.0f, 1.0f].
        float backlightValue = MathUtils.constrain(backlightSetting,
                PowerManager.BRIGHTNESS_MIN,
                PowerManager.BRIGHTNESS_MAX);

        // Interpolate the backlight value to nits.
        float nits = mBacklightToNitsSpline.interpolate(backlightValue);

        // Interpolate nits to a backlight value for a panel with enabled HBM.
        float interpolatedHbmBacklightValue = mNitsToHbmBacklightSpline.interpolate(nits);

        float gammaCorrectedHbmBacklightValue = (float) Math.pow(interpolatedHbmBacklightValue,
                1.0f / DISPLAY_GAMMA);
        float scrimOpacity = PowerManager.BRIGHTNESS_MAX - gammaCorrectedHbmBacklightValue;

        // Interpolate the opacity value from [0.0f, 1.0f] to [0, 255].
        return BrightnessSynchronizer.brightnessFloatToInt(scrimOpacity);
    }

    /**
     * Request fingerprint scan.
     *
     * This is intented to be called in response to a sensor that triggers an AOD interrupt for the
     * This is intended to be called in response to a sensor that triggers an AOD interrupt for the
     * fingerprint sensor.
     */
    void onAodInterrupt(int screenX, int screenY, float major, float minor) {
@@ -451,7 +357,7 @@ public class UdfpsController implements DozeReceiver {
    /**
     * Cancel fingerprint scan.
     *
     * This is intented to be called after the fingerprint scan triggered by the AOD interrupt
     * This is intended to be called after the fingerprint scan triggered by the AOD interrupt
     * either succeeds or fails.
     */
    void onCancelAodInterrupt() {
@@ -466,65 +372,28 @@ public class UdfpsController implements DozeReceiver {
        onFingerUp();
    }

    protected void onFingerDown(int x, int y, float minor, float major) {
        if (mHbmSupported) {
            try {
                FileWriter fw = new FileWriter(mHbmPath);
                fw.write(mHbmEnableCommand);
                fw.close();
            } catch (IOException e) {
                mView.hideScrimAndDot();
                Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage());
            }
        }
        mView.setScrimAlpha(computeScrimOpacity());
        mView.setRunAfterShowingScrimAndDot(() -> {
            mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major);
        });
        mView.showScrimAndDot();
    // This method can be called from the UI thread.
    private void onFingerDown(int x, int y, float minor, float major) {
        mView.setOnIlluminatedRunnable(
                () -> mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major));
        mView.startIllumination();
    }

    protected void onFingerUp() {
    // This method can be called from the UI thread.
    private void onFingerUp() {
        mFingerprintManager.onPointerUp(mSensorProps.sensorId);
        // Hiding the scrim before disabling HBM results in less noticeable flicker.
        mView.hideScrimAndDot();
        if (mHbmSupported) {
            try {
                FileWriter fw = new FileWriter(mHbmPath);
                fw.write(mHbmDisableCommand);
                fw.close();
            } catch (IOException e) {
                mView.showScrimAndDot();
                Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage());
            }
        }
        mView.stopIllumination();
    }

    private static float obtainDefaultBrightness(Context context) {
        return MathUtils.constrain(context.getDisplay().getBrightnessDefault(),
                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
    }

    private static float[] toFloatArray(TypedArray array) {
        final int n = array.length();
        float[] vals = new float[n];
        for (int i = 0; i < n; i++) {
            vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
        }
        array.recycle();
        return vals;
    }

    private static float[] normalizeBacklightRange(int[] backlight) {
        final int n = backlight.length;
        float[] normalizedBacklight = new float[n];
        for (int i = 0; i < n; i++) {
            normalizedBacklight[i] = BrightnessSynchronizer.brightnessIntToFloat(backlight[i]);
        }
        return normalizedBacklight;
    @Override
    public void enableHbm(Surface surface) {
        // Do nothing. This method can be implemented for devices that require the high-brightness
        // mode for fingerprint illumination.
    }

    protected UdfpsView getView() {
        return mView;
    @Override
    public void disableHbm(Surface surface) {
        // Do nothing. This method can be implemented for devices that require the high-brightness
        // mode for fingerprint illumination.
    }
}
+120 −143

File changed.

Preview size limit exceeded, changes collapsed.

+16 −26
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -47,7 +46,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.Before;
@@ -86,15 +84,12 @@ public class UdfpsControllerTest extends SysuiTestCase {
    @Mock
    private FingerprintManager mFingerprintManager;
    @Mock
    private DisplayManager mDisplayManager;
    @Mock
    private WindowManager mWindowManager;
    @Mock
    private StatusBarStateController mStatusBarStateController;
    @Mock
    private ScrimController mScrimController;

    private FakeSettings mSystemSettings;
    private FakeExecutor mFgExecutor;

    // Stuff for configuring mocks
@@ -109,7 +104,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
    @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
    private IUdfpsOverlayController mOverlayController;
    @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
    @Captor private ArgumentCaptor<Runnable> mRunAfterShowingScrimAndDotCaptor;
    @Captor private ArgumentCaptor<Runnable> mOnIlluminatedRunnableCaptor;

    @Before
    public void setUp() {
@@ -122,16 +117,13 @@ public class UdfpsControllerTest extends SysuiTestCase {
                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
                true /* resetLockoutRequiresHardwareAuthToken */));
        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
        mSystemSettings = new FakeSettings();
        mFgExecutor = new FakeExecutor(new FakeSystemClock());
        mUdfpsController = new UdfpsController(
                mContext,
                mResources,
                mLayoutInflater,
                mFingerprintManager,
                mDisplayManager,
                mWindowManager,
                mSystemSettings,
                mStatusBarStateController,
                mFgExecutor,
                mScrimController);
@@ -183,7 +175,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
    @Test
    public void fingerDown() throws RemoteException {
        // Configure UdfpsView to accept the ACTION_DOWN event
        when(mUdfpsView.isShowScrimAndDot()).thenReturn(false);
        when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
        when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true);

        // GIVEN that the overlay is showing
@@ -195,12 +187,11 @@ public class UdfpsControllerTest extends SysuiTestCase {
        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
        event.recycle();
        // THEN the scrim and dot is shown
        verify(mUdfpsView).showScrimAndDot();
        // AND a runnable that passes the event to FingerprintManager is set on the view
        verify(mUdfpsView).setRunAfterShowingScrimAndDot(
                mRunAfterShowingScrimAndDotCaptor.capture());
        mRunAfterShowingScrimAndDotCaptor.getValue().run();
        // THEN illumination begins
        verify(mUdfpsView).startIllumination();
        // AND onIlluminatedRunnable that notifies FingerprintManager is set
        verify(mUdfpsView).setOnIlluminatedRunnable(mOnIlluminatedRunnableCaptor.capture());
        mOnIlluminatedRunnableCaptor.getValue().run();
        verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
                eq(0), eq(0f), eq(0f));
    }
@@ -213,12 +204,11 @@ public class UdfpsControllerTest extends SysuiTestCase {
        mFgExecutor.runAllReady();
        // WHEN fingerprint is requested because of AOD interrupt
        mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
        // THEN the scrim and dot is shown
        verify(mUdfpsView).showScrimAndDot();
        // AND a runnable that passes the event to FingerprintManager is set on the view
        verify(mUdfpsView).setRunAfterShowingScrimAndDot(
                mRunAfterShowingScrimAndDotCaptor.capture());
        mRunAfterShowingScrimAndDotCaptor.getValue().run();
        // THEN illumination begins
        verify(mUdfpsView).startIllumination();
        // AND onIlluminatedRunnable that notifies FingerprintManager is set
        verify(mUdfpsView).setOnIlluminatedRunnable(mOnIlluminatedRunnableCaptor.capture());
        mOnIlluminatedRunnableCaptor.getValue().run();
        verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
                eq(0), eq(3f) /* minor */, eq(2f) /* major */);
    }
@@ -232,8 +222,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
        mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
        // WHEN it is cancelled
        mUdfpsController.onCancelAodInterrupt();
        // THEN the scrim and dot is hidden
        verify(mUdfpsView).hideScrimAndDot();
        // THEN the illumination is hidden
        verify(mUdfpsView).stopIllumination();
    }

    @Test
@@ -246,8 +236,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
        // WHEN it times out
        mFgExecutor.advanceClockToNext();
        mFgExecutor.runAllReady();
        // THEN the scrim and dot is hidden
        verify(mUdfpsView).hideScrimAndDot();
        // THEN the illumination is hidden
        verify(mUdfpsView).stopIllumination();
    }

    @Test