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

Commit 551929ac authored by Ilya Matyukhin's avatar Ilya Matyukhin Committed by Android (Google) Code Review
Browse files

Merge "Remove HBM logic from the UDFPS overlay" into sc-dev

parents ecb4816e f6eb03ea
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