udfps: Restore illumination dot for global hbm

UdfpsSurfaceView.java is imported from android-12.1.0_r22

Change-Id: I4a4e85a7437a9a444a4f952fd62e4fe12f56ce5a
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index 257d238..0fcbfa1 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -28,4 +28,10 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
 
+    <com.android.systemui.biometrics.UdfpsSurfaceView
+        android:id="@+id/hbm_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible"/>
+
 </com.android.systemui.biometrics.UdfpsView>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
new file mode 100644
index 0000000..f5ada94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 The Android Open Source 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * Surface View for providing the Global High-Brightness Mode (GHBM) illumination for UDFPS.
+ */
+public class UdfpsSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+    private static final String TAG = "UdfpsSurfaceView";
+
+    /**
+     * Notifies {@link UdfpsView} when to enable GHBM illumination.
+     */
+    interface GhbmIlluminationListener {
+        /**
+         * @param surface the surface for which GHBM should be enabled.
+         * @param onDisplayConfigured a runnable that should be run after GHBM is enabled.
+         */
+        void enableGhbm(@NonNull Surface surface, @Nullable Runnable onDisplayConfigured);
+    }
+
+    @NonNull private final SurfaceHolder mHolder;
+    @NonNull private final Paint mSensorPaint;
+
+    @Nullable private GhbmIlluminationListener mGhbmIlluminationListener;
+    @Nullable private Runnable mOnDisplayConfigured;
+    boolean mAwaitingSurfaceToStartIllumination;
+    boolean mHasValidSurface;
+
+    public UdfpsSurfaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // Make this SurfaceView draw on top of everything else in this window. This allows us to
+        // 1) Always show the HBM circle on top of everything else, and
+        // 2) Properly composite this view with any other animations in the same window no matter
+        //    what contents are added in which order to this view hierarchy.
+        setZOrderOnTop(true);
+
+        mHolder = getHolder();
+        mHolder.addCallback(this);
+        mHolder.setFormat(PixelFormat.RGBA_8888);
+
+        mSensorPaint = new Paint(0 /* flags */);
+        mSensorPaint.setAntiAlias(true);
+        mSensorPaint.setARGB(255, 255, 255, 255);
+        mSensorPaint.setStyle(Paint.Style.FILL);
+    }
+
+    @Override public void surfaceCreated(SurfaceHolder holder) {
+        mHasValidSurface = true;
+        if (mAwaitingSurfaceToStartIllumination) {
+            doIlluminate(mOnDisplayConfigured);
+            mOnDisplayConfigured = null;
+            mAwaitingSurfaceToStartIllumination = false;
+        }
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        // Unused.
+    }
+
+    @Override public void surfaceDestroyed(SurfaceHolder holder) {
+        mHasValidSurface = false;
+    }
+
+    void setGhbmIlluminationListener(@Nullable GhbmIlluminationListener listener) {
+        mGhbmIlluminationListener = listener;
+    }
+
+    /**
+     * Note: there is no corresponding method to stop GHBM illumination. It is expected that
+     * {@link UdfpsView} will hide this view, which would destroy the surface and remove the
+     * illumination dot.
+     */
+    void startGhbmIllumination(@Nullable Runnable onDisplayConfigured) {
+        if (mGhbmIlluminationListener == null) {
+            Log.e(TAG, "startIllumination | mGhbmIlluminationListener is null");
+            return;
+        }
+
+        if (mHasValidSurface) {
+            doIlluminate(onDisplayConfigured);
+        } else {
+            mAwaitingSurfaceToStartIllumination = true;
+            mOnDisplayConfigured = onDisplayConfigured;
+        }
+    }
+
+    private void doIlluminate(@Nullable Runnable onDisplayConfigured) {
+        if (mGhbmIlluminationListener == null) {
+            Log.e(TAG, "doIlluminate | mGhbmIlluminationListener is null");
+            return;
+        }
+
+        mGhbmIlluminationListener.enableGhbm(mHolder.getSurface(), onDisplayConfigured);
+    }
+
+    /**
+     * Immediately draws the illumination dot on this SurfaceView's surface.
+     */
+    void drawIlluminationDot(@NonNull RectF sensorRect) {
+        if (!mHasValidSurface) {
+            Log.e(TAG, "drawIlluminationDot | the surface is destroyed or was never created.");
+            return;
+        }
+        Canvas canvas = null;
+        try {
+            canvas = mHolder.lockCanvas();
+            canvas.drawOval(sensorRect, mSensorPaint);
+        } finally {
+            // Make sure the surface is never left in a bad state.
+            if (canvas != null) {
+                mHolder.unlockCanvasAndPost(canvas);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 76bcd6e..a8e4e95 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -24,9 +24,11 @@
 import android.util.AttributeSet
 import android.util.Log
 import android.view.MotionEvent
+import android.view.Surface
 import android.widget.FrameLayout
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.doze.DozeReceiver
+import com.android.systemui.res.R
 
 private const val TAG = "UdfpsView"
 
@@ -47,6 +49,8 @@
         textSize = 32f
     }
 
+    private var ghbmView: UdfpsSurfaceView? = null
+
     /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
     var animationViewController: UdfpsAnimationViewController<*>? = null
 
@@ -73,6 +77,10 @@
         return (animationViewController == null || !animationViewController!!.shouldPauseAuth())
     }
 
+    override fun onFinishInflate() {
+        ghbmView = findViewById(R.id.hbm_view)
+    }
+
     override fun dozeTimeTick() {
         animationViewController?.dozeTimeTick()
     }
@@ -106,12 +114,34 @@
     fun configureDisplay(onDisplayConfigured: Runnable) {
         isDisplayConfigured = true
         animationViewController?.onDisplayConfiguring()
-        mUdfpsDisplayMode?.enable(onDisplayConfigured)
+        val gView = ghbmView
+        if (gView != null) {
+            gView.setGhbmIlluminationListener(this::doIlluminate)
+            gView.visibility = VISIBLE
+            gView.startGhbmIllumination(onDisplayConfigured)
+        } else {
+            doIlluminate(null /* surface */, onDisplayConfigured)
+        }
+    }
+
+    private fun doIlluminate(surface: Surface?, onDisplayConfigured: Runnable?) {
+        if (ghbmView != null && surface == null) {
+            Log.e(TAG, "doIlluminate | surface must be non-null for GHBM")
+        }
+
+        mUdfpsDisplayMode?.enable {
+            onDisplayConfigured?.run()
+            ghbmView?.drawIlluminationDot(RectF(sensorRect))
+        }
     }
 
     fun unconfigureDisplay() {
         isDisplayConfigured = false
         animationViewController?.onDisplayUnconfigured()
+        ghbmView?.let { view ->
+            view.setGhbmIlluminationListener(null)
+            view.visibility = INVISIBLE
+        }
         mUdfpsDisplayMode?.disable(null /* onDisabled */)
     }
 }