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

Unverified Commit 5b138137 authored by Danny Lin's avatar Danny Lin Committed by Michael Bestas
Browse files

SystemUI: Add HBM provider for UDFPS on Pixel devices

This implements high brightness mode (HBM) control for the under-display
fingerprint sensor on Pixel devices. It enables and disables local HBM
through the Pixel display HAL, similar to Google's implementation in the
stock OS.

Blocking IPC calls to the display HAL are done on a background thread to
avoid causing frame drops by blocking the main thread, but callbacks are
always run on the main thread as specified by the UdfpsHbmProvider docs.
Care is taken to avoid enabling LHBM when it's already enabled and vice
versa, as doing so causes the display HAL's request to time out and
throw an exception.

For simplicity, global HBM is not supported as it has never been used in
production on Pixel devices.

Change-Id: I8d301c7e1ce54ee8ffd987708b6f0e2bf115f7ef
parent 2b022acf
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenu
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
@@ -374,6 +375,7 @@ public class Dependency {
    @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
    @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
    @Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
    @Inject Lazy<AuthController> mAuthController;

    @Inject
    public Dependency() {
@@ -595,6 +597,8 @@ public class Dependency {
        mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
        mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get);

        mProviders.put(AuthController.class, mAuthController::get);

        Dependency.setInstance(this);
    }

+141 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The ProtonAOSP Project
 * Copyright (C) 2022 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.hardware.display.DisplayManager
import android.os.Handler
import android.os.IBinder
import android.os.ServiceManager
import android.view.Surface
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.Dependency
import com.google.hardware.pixel.display.IDisplay
import java.util.concurrent.Executor

class PixelUdfpsHbmProvider constructor(
    private val context: Context
) : UdfpsHbmProvider, IBinder.DeathRecipient, DisplayManager.DisplayListener {

    private val authController = Dependency.get(AuthController::class.java)
    private val bgExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR)
    private val handler = Dependency.get(Dependency.MAIN_HANDLER)
    private val displayId = context.getDisplayId()
    private val displayManager = context.getSystemService(DisplayManager::class.java)

    private var displayHal = ServiceManager.waitForDeclaredService(PIXEL_DISPLAY_HAL)
            .let { binder ->
                binder.linkToDeath(this, 0)
                IDisplay.Stub.asInterface(binder)
            }

    private val peakRefreshRate = displayManager.getDisplay(displayId).supportedModes
            .maxOf { it.refreshRate }
    private val currentRefreshRate: Float
        get() = displayManager.getDisplay(displayId).refreshRate

    // Used by both main and UI background threads
    @Volatile private var pendingEnable = false
    @Volatile private var pendingEnableCallback: Runnable? = null

    init {
        // Listen for refresh rate changes
        displayManager.registerDisplayListener(this, handler)
    }

    override fun enableHbm(halControlsIllumination: Boolean, onHbmEnabled: Runnable?) {
        // Run the callback and skip enabling if already enabled
        // (otherwise it may fail, similar to disabling)
        if (displayHal.getLhbmState()) {
            onHbmEnabled?.run()
            return
        }

        // Takes 20-30 ms, so switch to background
        bgExecutor.execute {
            // Request HbmSVManager to lock the refresh rate. On the Pixel 6 Pro (raven), LHBM only
            // works at peak refresh rate.
            authController.udfpsHbmListener?.onHbmEnabled(displayId)

            if (currentRefreshRate == peakRefreshRate) {
                // Enable immediately if refresh rate is correct
                doPendingEnable(onHbmEnabled)
            } else {
                // Otherwise, queue it and wait for the refresh rate update callback
                pendingEnable = true
                pendingEnableCallback = onHbmEnabled
            }
        }
    }

    private fun doPendingEnable(callback: Runnable? = null) {
        displayHal?.setLhbmState(true)
        // Make sure callback runs on main thread
        (callback ?: pendingEnableCallback)?.let { handler.post(it) }

        pendingEnable = false
        pendingEnableCallback = null // to avoid leaking memory
    }

    override fun disableHbm(onHbmDisabled: Runnable?) {
        // If there's a pending enable, clear it and skip the disable request entirely.
        // Otherwise, HBM will be disabled before the enable - while it's already disabled, which
        // causes the display HAL call to throw an exception.
        if (pendingEnable) {
            pendingEnable = false
            pendingEnableCallback = null
            return
        }

        // Also bail out if HBM is already disabled *and* no enable is pending.
        // This can happen sometimes if the user spams taps on the UDFPS icon.
        if (!displayHal.getLhbmState()) {
            return
        }

        // Takes 10-20 ms, so switch to background
        bgExecutor.execute {
            displayHal?.setLhbmState(false)
            // Unlock refresh rate
            handler.post { authController.udfpsHbmListener?.onHbmDisabled(displayId) }

            onHbmDisabled?.let { handler.post(it) }
        }
    }

    override fun onDisplayAdded(displayId: Int) = Unit
    override fun onDisplayRemoved(displayId: Int) = Unit
    override fun onDisplayChanged(displayId: Int) {
        // Dispatch pending enable if we were waiting for the refresh rate to change
        if (pendingEnable && displayId == this.displayId && currentRefreshRate == peakRefreshRate) {
            doPendingEnable()
        }
    }

    override fun binderDied() {
        displayHal = null
    }

    companion object {
        // Descriptor for Pixel display HAL's AIDL service
        private const val PIXEL_DISPLAY_HAL = "com.google.hardware.pixel.display.IDisplay/default"
    }
}