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

Commit 2c21d1ce authored by Danny Lin's avatar Danny Lin Committed by LuK1337
Browse files

SystemUI: Add display mode 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.

Co-authored-by: default avatarMichael Bestas <mkbestas@lineageos.org>
Co-authored-by: default avatarTommy Webb <tommy@calyxinstitute.org>
Original-Change-Id: I8d301c7e1ce54ee8ffd987708b6f0e2bf115f7ef
Change-Id: Iefc334720a3db7d9f21045e5bd6b8a8a2ea7fd3e
parent aee4be2e
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;
@@ -357,6 +358,7 @@ public class Dependency {
    @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
    @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
    @Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
    @Inject Lazy<AuthController> mAuthController;

    @Inject
    public Dependency() {
@@ -565,6 +567,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);
    }

+140 −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.IBinder
import android.os.ServiceManager

import com.android.systemui.biometrics.AuthController
import com.android.systemui.Dependency
import com.google.hardware.pixel.display.IDisplay

class PixelUdfpsDisplayModeProvider constructor(
    private val context: Context
) : UdfpsDisplayModeProvider, 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
    @Volatile private var alreadyDisabled = true

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

    override fun enable(onEnabled: Runnable?) {
        // Run the callback and skip enabling if already enabled
        // (otherwise it may fail, similar to disabling)
        if (displayHal.getLhbmState()) {
            onEnabled?.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(onEnabled)
            } else {
                // Otherwise, queue it and wait for the refresh rate update callback
                pendingEnable = true
                pendingEnableCallback = onEnabled
            }
        }
    }

    private fun doPendingEnable(callback: Runnable? = null) {
        alreadyDisabled = false
        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 disable(onDisabled: 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() && alreadyDisabled) {
            return
        }

        alreadyDisabled = true

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

            onDisabled?.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"
    }
}