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

Commit 7df6e422 authored by Rohit Sekhar's avatar Rohit Sekhar
Browse files

Merge branch '1764devices-a16-mtk_ghbm' into 'a16'

SystemUI: Reverse MediaTek udfps dimlayer changes

See merge request e/os/android_frameworks_base!334
parents e9215965 6d96c9fb
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Boolean to enable/disable the UDFPS onPress node functionality -->
    <bool name="config_enableUdfpsSysfsNodes">false</bool>
</resources>
+5 −3
Original line number Diff line number Diff line
@@ -57,9 +57,11 @@
          <item>4095,0</item>
    </string-array>

    <!-- Array of UDFPS node paths -->
    <string-array name="config_udfpsSysfsNodePaths">
    </string-array>
    <!-- Whether to enable MediaTek-specific optical dimming for UDFPS -->
    <bool name="config_udfpsMtkGhbmDimming">false</bool>

    <!-- Name of the UDFPS HBM DimLayer for the HWComposer -->
    <string name="config_udfpsHbmDimLayer" translatable="false">OnScreenFingerprintDimLayer</string>

    <!-- Doze: does the double tap sensor need a proximity check? -->
    <bool name="doze_double_tap_proximity_check">false</bool>
+95 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2026 The LineageOS 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.systemui.biometrics;

import android.content.Context;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;

/**
 * Controller for managing the UDFPS HBM (High Brightness Mode) Scrim on MediaTek devices lacking LHBM support.
 *
 * Logic reversed from Smali: Lcom/android/systemui/biometrics/PriUdfpsScrimController; from LAVA
 */
public class MtkUdfpsScrimController {
    private static MtkUdfpsScrimController mInstance;

    // Accessed directly by UdfpsController
    public View mScrimView;
    public WindowManager mWindowManager;

    private final Context mContext;

    public MtkUdfpsScrimController(Context context) {
        mContext = context;
    }

    public static MtkUdfpsScrimController getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new MtkUdfpsScrimController(context);
        }
        return mInstance;
    }

    /**
     * Calculates the alpha (transparency) for the HBM overlay based on screen brightness.
     * The formula ensures the sensor gets a consistent amount of light.
     *
     * @param brightness Current system brightness (0-255)
     * @return Alpha value (0.0f to 1.0f)
     */
    public float calculateAlpha(int brightness) {
        float alpha = 1.0f - (brightness / 255.0f);

        if (brightness < 25) {
            // Make alpha 5% more transparent when brightness is under 25.
            alpha = alpha * 0.95f;
        }

        Log.d("MtkUdfpsScrimController", "Requested Brightness value: " + brightness);
        Log.d("MtkUdfpsScrimController", "Alpha Value: " + alpha);

        // Ensure we stay in bounds
        return Math.max(0.0f, Math.min(1.0f, alpha));
    }

    public int getSystemBrightness() {
        if (mContext == null) {
            return 127;
        }

        // Try Float first
        float brightFloat = Settings.System.getFloat(
            mContext.getContentResolver(),
            "screen_brightness_float",
            -1.0f
        );

        if (brightFloat >= 0.0f) {
            return (int) (brightFloat * 255.0f);
        }

        // Fallback
        return Settings.System.getInt(
            mContext.getContentResolver(),
            Settings.System.SCREEN_BRIGHTNESS,
            127
        );
    }
}
+44 −73
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ import com.android.keyguard.UserActivityNotifier;
import com.android.systemui.Dumpable;
import com.android.systemui.Flags;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.biometrics.MtkUdfpsScrimController;
import com.android.systemui.biometrics.dagger.BiometricsBackground;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
@@ -119,14 +120,9 @@ import kotlin.Unit;

import kotlinx.coroutines.CoroutineScope;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

@@ -232,51 +228,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
    private boolean mAttemptedToDismissKeyguard;
    private final Set<Callback> mCallbacks = new HashSet<>();

    private final boolean mIsUdfpsNodeFeatureEnabled;
    private final List<String> mUdfpsSysfsNodePaths;
    private Handler mHandler;

    private void updateUdfpsNodes(String value) {
        if (!mIsUdfpsNodeFeatureEnabled) {
            Log.d(TAG, "UDFPS node functionality is disabled via overlay.");
            return;
        }

        if (mUdfpsSysfsNodePaths == null || mUdfpsSysfsNodePaths.isEmpty()) {
            Log.e(TAG, "UDFPS node paths are not properly initialized.");
            return;
        }

        for (String path : mUdfpsSysfsNodePaths) {
            writeToUdfpsNode(path, value);
        }
    }

    private void writeToUdfpsNode(String path, String value) {
         mBiometricExecutor.execute(() -> {
             if (isFileWritable(path)) {
                 try (FileWriter writer = new FileWriter(path)) {
                      writer.write(value);
                      Log.d(TAG, "Successfully set " + path + " to " + value);
                 } catch (IOException e) {
                      Log.e(TAG, "Failed to write to " + path, e);
                 }
             }
         });
    }

    private boolean isFileWritable(String path) {
         File file = new File(path);
         if (!file.exists()) {
              Log.e(TAG, "File does not exist: " + path);
              return false;
         }
         if (!file.canWrite()) {
              Log.e(TAG, "File is not writable: " + path);
              return false;
         }
         return true;
    }
    private boolean mUseMtkGhbmDimming;

    @VisibleForTesting
    public static final VibrationAttributes UDFPS_VIBRATION_ATTRIBUTES =
@@ -800,19 +752,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
        mDeviceEntryUdfpsTouchOverlayViewModel = deviceEntryUdfpsTouchOverlayViewModel;
        mDefaultUdfpsTouchOverlayViewModel = defaultUdfpsTouchOverlayViewModel;
        mPromptUdfpsTouchOverlayViewModel = promptUdfpsTouchOverlayViewModel;
        mHandler = new Handler(Looper.getMainLooper());

        mIsUdfpsNodeFeatureEnabled = mContext.getResources().getBoolean(
            com.android.systemui.res.R.bool.config_enableUdfpsSysfsNodes
        );
        Log.d(TAG, "UDFPS sysfs node feature enabled: " + mIsUdfpsNodeFeatureEnabled);

        mUdfpsSysfsNodePaths = Arrays.asList(
            mContext.getResources().getStringArray(
                com.android.systemui.res.R.array.config_udfpsSysfsNodePaths
            )
        );
        Log.d(TAG, "Initialized UDFPS node paths: " + mUdfpsSysfsNodePaths);

        mDumpManager.registerDumpable(TAG, this);

@@ -839,6 +778,9 @@ public class UdfpsController implements DozeReceiver, Dumpable {

        udfpsHapticsSimulator.setUdfpsController(this);
        udfpsShell.setUdfpsOverlayController(mUdfpsOverlayController);

        mUseMtkGhbmDimming = mContext.getResources().getBoolean(
            com.android.systemui.res.R.bool.config_udfpsMtkGhbmDimming);
    }

    /**
@@ -911,6 +853,15 @@ public class UdfpsController implements DozeReceiver, Dumpable {
            if (oldView != null) {
                onFingerUp(mOverlay.getRequestId(), oldView);
            }

            // Ensure HBM views are removed from WindowManager
            if (mUseMtkGhbmDimming) {
                View hbmView = mOverlay.getHbmView();
                if (hbmView != null) {
                    hbmView.setVisibility(View.GONE);
                }
            }

            final boolean removed = mOverlay.hide();
            mKeyguardViewManager.hideAlternateBouncer(true);
            Log.v(TAG, "hideUdfpsOverlay | removing window: " + removed);
@@ -930,12 +881,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
        if (udfpsView.isDisplayConfigured()) {
            udfpsView.unconfigureDisplay();
        }

        // Check if the fingerprint is still pressed before proceeding
        if (mIsUdfpsNodeFeatureEnabled && mOnFingerDown) {
            Log.v(TAG, "Fingerprint is still pressed; skipping unconfigureDisplay.");
            return;
        }
    }

    /**
@@ -1071,9 +1016,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
        mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId,
                mSensorProps.sensorId);
        mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
        mHandler.postDelayed(() -> {
            updateUdfpsNodes("1");
        }, 50);
    }

    private void onFingerDown(
@@ -1137,6 +1079,30 @@ public class UdfpsController implements DozeReceiver, Dumpable {
        mOnFingerDown = true;
        mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
                minor, major, orientation, time, gestureStart, isAod);

        if (mUseMtkGhbmDimming && mOverlay != null) {
            View hbmView = mOverlay.getHbmView(); // Ensure UdfpsControllerOverlay exposes this
            WindowManager.LayoutParams hbmParams = mOverlay.getHbmLayoutParamsFull();

            if (hbmView != null && hbmParams != null) {
                MtkUdfpsScrimController mtkController = MtkUdfpsScrimController.getInstance(mContext);

                int brightness = mtkController.getSystemBrightness();
                float alpha = mtkController.calculateAlpha(brightness);

                Log.d(TAG, "UDFPS Scrim: Brightness=" + brightness + " CalculatedAlpha=" + alpha);

                if (hbmView.getVisibility() != View.VISIBLE) {
                    hbmView.setVisibility(View.VISIBLE);
                }

                if (Math.abs(hbmParams.alpha - alpha) > 0.001f) {
                    hbmParams.alpha = alpha;
                    mWindowManager.updateViewLayout(hbmView, hbmParams);
                }
            }
        }

        Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);

        final View view = mOverlay.getTouchOverlay();
@@ -1196,7 +1162,12 @@ public class UdfpsController implements DozeReceiver, Dumpable {
        }
        mOnFingerDown = false;

	updateUdfpsNodes("0");
        if (mUseMtkGhbmDimming && mOverlay != null) {
            View hbmView = mOverlay.getHbmView();
            if (hbmView != null) {
                hbmView.setVisibility(View.GONE);
            }
        }

        unconfigureDisplay(view);
        cancelAodSendFingerUpAction();
+181 −3
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.biometrics
import android.annotation.SuppressLint
import android.annotation.UiThread
import android.content.Context
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.Rect
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
@@ -124,7 +125,53 @@ constructor(
        null
    }

    private val coreLayoutParams =
    private val useMtkGhbmDimming = context.resources.getBoolean(
        com.android.systemui.res.R.bool.config_udfpsMtkGhbmDimming
    )

    private val hbmDimLayerName = context.resources.getString(
        com.android.systemui.res.R.string.config_udfpsHbmDimLayer
    )

    val hbmView: View? = if (useMtkGhbmDimming) {
        View(context).apply {
            setBackgroundColor(Color.BLACK)
            visibility = View.INVISIBLE
        }
    } else null

    private val dimView: View? = if (useMtkGhbmDimming) {
        View(context).apply {
            setBackgroundColor(Color.TRANSPARENT)
        }
    } else null

    private var isAddDimView: Boolean = false

    private val coreLayoutParams: WindowManager.LayoutParams = if (useMtkGhbmDimming) {
            WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                0 /* flags set in computeLayoutParams() */,
                PixelFormat.TRANSLUCENT
            ).apply {
                title = TAG
                fitInsetsTypes = 0
                gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
                layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS

                flags = (WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED or
                    WindowManager.LayoutParams.FLAG_SPLIT_TOUCH or
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

                privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
                // Avoid announcing window title.
                accessibilityTitle = " "

                inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
            }
    } else {
            WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                0 /* flags set in computeLayoutParams() */,
@@ -144,6 +191,92 @@ constructor(
                accessibilityTitle = " "
                inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
            }
    }

    // HBM Params (The high brightness layer)
    private val hbmLayoutParams: WindowManager.LayoutParams? = if (useMtkGhbmDimming) {
            WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                0 /* flags set in computeLayoutParams() */,
                PixelFormat.TRANSLUCENT
            ).apply {
                title = hbmDimLayerName
                fitInsetsTypes = 0
                alpha = 0.1f
                gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
                layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS

                flags = (WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED or
                    WindowManager.LayoutParams.FLAG_SPLIT_TOUCH or
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

                privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
                // Avoid announcing window title.
                accessibilityTitle = " "

                inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
            }
    } else {
        null
    }

    // HBM Full Params
    val hbmLayoutParamsFull: WindowManager.LayoutParams? = if (useMtkGhbmDimming) {
            WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                0 /* flags set in computeLayoutParams() */,
                PixelFormat.TRANSLUCENT
            ).apply {
                title = hbmDimLayerName
                fitInsetsTypes = 0
                alpha = 0.1f
                gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
                layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS

                flags = (WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED or
                    WindowManager.LayoutParams.FLAG_SPLIT_TOUCH or
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

                privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
                // Avoid announcing window title.
                accessibilityTitle = " "

                inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
            }
    } else {
        null
    }

    // Dim Params
    private val dimLayoutParams: WindowManager.LayoutParams? = if (useMtkGhbmDimming) {
            WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                0 /* flags set in computeLayoutParams() */,
                PixelFormat.TRANSLUCENT
            ).apply {
                title = "UdfpsDim"
                fitInsetsTypes = 0
                gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
                layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS

                flags = (WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED or
                    WindowManager.LayoutParams.FLAG_SPLIT_TOUCH or
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

                privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
                accessibilityTitle = " "

                inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
            }
    } else {
        null
    }

    /** If the overlay is currently showing. */
    val isShowing: Boolean
@@ -230,8 +363,36 @@ constructor(
        addViewRunnable =
            kotlinx.coroutines.Runnable {
                Trace.setCounter("UdfpsAddView", 1)

                if (useMtkGhbmDimming) {
                    hbmLayoutParams?.updateDimensions(animation)
                    hbmLayoutParamsFull?.updateDimensions(animation)
                    dimLayoutParams?.updateDimensions(animation)

                    try {
                        windowManager.addView(hbmView, hbmLayoutParams)
                        windowManager.addView(dimView, dimLayoutParams)
                        isAddDimView = true
                    } catch (e: Exception) {
                        Log.e(TAG, "Failed to add vendor HBM/Dim views", e)
                    }

                    windowManager.addView(view, coreLayoutParams.updateDimensions(animation))

                    if (requestReason == REASON_ENROLL_FIND_SENSOR || requestReason == REASON_ENROLL_ENROLLING) {
                        val child = view.findViewById<View>(R.id.udfps_enroll_accessibility_view)
                        child?.let {
                            val lp = it.layoutParams
                            lp.width = sensorBounds.width()
                            lp.height = sensorBounds.height()
                            it.layoutParams = lp
                            it.requestLayout()
                        }
                    }
                } else {
                    windowManager.addView(view, coreLayoutParams.updateDimensions(animation))
                }
            }
        if (powerInteractor.detailedWakefulness.value.isAwake()) {
            // Device is awake, so we add the view immediately.
            addViewIfPending()
@@ -261,6 +422,12 @@ constructor(
                // no need to update any layouts. Instead the correct params will be used when the
                // view is eventually added.
                windowManager.updateViewLayout(it, coreLayoutParams.updateDimensions(null))

                if (useMtkGhbmDimming && isAddDimView) {
                    hbmLayoutParamsFull?.updateDimensions(null)
                    windowManager.updateViewLayout(hbmView, hbmLayoutParams)
                    windowManager.updateViewLayout(dimView, dimLayoutParams)
                }
            }
        }
    }
@@ -280,6 +447,17 @@ constructor(
            if (this.parent != null) {
                windowManager.removeView(this)
            }

            if (useMtkGhbmDimming && isAddDimView) {
                try {
                    windowManager.removeView(hbmView)
                    windowManager.removeView(dimView)
                } catch (e: Exception) {
                    Log.w(TAG, "Failed to remove HBM/Dim views", e)
                }
                isAddDimView = false
            }

            Trace.setCounter("UdfpsAddView", 0)
            setOnTouchListener(null)
            setOnHoverListener(null)