/* * 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 onIlluminatedRunnable a runnable that should be run after GHBM is enabled. */ void enableGhbm(@NonNull Surface surface, @Nullable Runnable onIlluminatedRunnable); } @NonNull private final SurfaceHolder mHolder; @NonNull private final Paint mSensorPaint; @Nullable private GhbmIlluminationListener mGhbmIlluminationListener; @Nullable private Runnable mOnIlluminatedRunnable; 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(mOnIlluminatedRunnable); mOnIlluminatedRunnable = 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 onIlluminatedRunnable) { if (mGhbmIlluminationListener == null) { Log.e(TAG, "startIllumination | mGhbmIlluminationListener is null"); return; } if (mHasValidSurface) { doIlluminate(onIlluminatedRunnable); } else { mAwaitingSurfaceToStartIllumination = true; mOnIlluminatedRunnable = onIlluminatedRunnable; } } private void doIlluminate(@Nullable Runnable onIlluminatedRunnable) { if (mGhbmIlluminationListener == null) { Log.e(TAG, "doIlluminate | mGhbmIlluminationListener is null"); return; } mGhbmIlluminationListener.enableGhbm(mHolder.getSurface(), onIlluminatedRunnable); } /** * 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); } } } }