Add Face Scanning anim

Render the face scanning animation and the camera
protection in FaceScanningOverlay, a SW layer.

Implementation notes:
  * The face scanning animation cannot be rendered
    in the HW layer because it requires color.
  * We cannot render the face scanning and camera
    protection overlays in different layers (HW and SW),
    because then they are difficult to coordinate.
 => Therefore, we render both the camera + face
    scanning animation in a SW layer together.

Test: atest ScreenDecorationsTest
Test: debug mode (adb shell setprop debug.screenshot_rounded_corners 1)
Test: enable color inversion, see camera protection is still black
Test: enable/disable face scanning feature and see that camera
  protection and face scanning anim works. Also test privacy dot
  indicator view still shows appropriately with and w/o the face
  scanning animation
Test: Check systemui dumpsys for view visibilities. Face
  scanning overlay should NOT be visible unless playing
  its animation.
Bug: 185128224
Change-Id: I2f67257bfcbfe177c76ebe76f20b1d1553e87175
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index a25ab51..b1d3375 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -28,6 +28,11 @@
     <!-- Will display the bouncer on one side of the display, and the current user icon and
          user switcher on the other side -->
     <bool name="config_enableBouncerUserSwitcher">false</bool>
+    <!-- Whether to show the face scanning animation on devices with face auth supported.
+         The face scanning animation renders in a SW layer in ScreenDecorations.
+         Enabling this will also render the camera protection in the SW layer
+         (instead of HW, if relevant)."=-->
+    <bool name="config_enableFaceScanningAnimation">true</bool>
     <!-- Time to be considered a consecutive fingerprint failure in ms -->
     <integer name="fp_consecutive_failure_time_ms">3500</integer>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 5eacc3e..dca5ea8 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -176,5 +176,8 @@
     <item type="id" name="rounded_corner_top_right"/>
     <item type="id" name="rounded_corner_bottom_left"/>
     <item type="id" name="rounded_corner_bottom_right"/>
+
+    <!-- face scanning view id -->
+    <item type="id" name="face_scanning_anim"/>
 </resources>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1ffadb4..727d108 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1040,7 +1040,7 @@
 
     private void handleFaceError(int msgId, String errString) {
         Assert.isMainThread();
-        if (DEBUG_FACE) Log.d(TAG, "Face error received: " + errString);
+        if (DEBUG_FACE) Log.d(TAG, "Face error received: " + errString + " msgId=" + msgId);
         if (mHandler.hasCallbacks(mFaceCancelNotReceived)) {
             mHandler.removeCallbacks(mFaceCancelNotReceived);
         }
@@ -2183,6 +2183,10 @@
                         && mBiometricEnabledForUser.get(userId));
     }
 
+    public boolean isFaceSupported() {
+        return mFaceManager != null && mFaceManager.isHardwareDetected();
+    }
+
     /**
      * @return true if there's at least one udfps enrolled for the current user.
      */
@@ -2309,6 +2313,10 @@
         stopListeningForFace();
     }
 
+    public boolean isFaceScanning() {
+        return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
+    }
+
     private void updateFaceListeningState(int action) {
         // If this message exists, we should not authenticate again until this message is
         // consumed by the handler
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index 34164f3..d2c229b 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -279,7 +279,7 @@
     }
 
     companion object {
-        private const val HIDDEN_CAMERA_PROTECTION_SCALE = 0.5f
+        const val HIDDEN_CAMERA_PROTECTION_SCALE = 0.5f
 
         @JvmStatic protected fun transformPhysicalToLogicalCoordinates(
             @Surface.Rotation rotation: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
new file mode 100644
index 0000000..b0530f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2022 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
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.RectF
+import android.view.View
+import androidx.core.graphics.ColorUtils
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.settingslib.Utils
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+
+/**
+ * When the face is enrolled, we use this view to show the face scanning animation and the camera
+ * protection on the keyguard.
+ */
+class FaceScanningOverlay(
+    context: Context,
+    pos: Int,
+    val statusBarStateController: StatusBarStateController,
+    val keyguardUpdateMonitor: KeyguardUpdateMonitor
+) : ScreenDecorations.DisplayCutoutView(context, pos) {
+    private var showScanningAnim = false
+    private val rimPaint = Paint()
+    private var rimProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE
+    private var rimAnimator: AnimatorSet? = null
+    private val rimRect = RectF()
+    private var cameraProtectionColor = Color.BLACK
+    var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context,
+            com.android.systemui.R.attr.wallpaperTextColorAccent)
+
+    override fun setColor(color: Int) {
+        cameraProtectionColor = color
+        invalidate()
+    }
+
+    private var cameraProtectionAnimator: ValueAnimator? = null
+    var hideOverlayRunnable: Runnable? = null
+
+    override fun drawCutoutProtection(canvas: Canvas) {
+        if (rimProgress > HIDDEN_RIM_SCALE && !protectionRect.isEmpty) {
+            val rimPath = Path(protectionPath)
+            val scaleMatrix = Matrix().apply {
+                val rimBounds = RectF()
+                rimPath.computeBounds(rimBounds, true)
+                setScale(rimProgress, rimProgress, rimBounds.centerX(), rimBounds.centerY())
+            }
+            rimPath.transform(scaleMatrix)
+            rimPaint.style = Paint.Style.FILL
+            val rimPaintAlpha = rimPaint.alpha
+            rimPaint.color = ColorUtils.blendARGB(
+                    faceScanningAnimColor,
+                    Color.WHITE,
+                    statusBarStateController.dozeAmount)
+            rimPaint.alpha = rimPaintAlpha
+            canvas.drawPath(rimPath, rimPaint)
+        }
+
+        if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE &&
+                !protectionRect.isEmpty) {
+            val scaledProtectionPath = Path(protectionPath)
+            val scaleMatrix = Matrix().apply {
+                val protectionPathRect = RectF()
+                scaledProtectionPath.computeBounds(protectionPathRect, true)
+                setScale(cameraProtectionProgress, cameraProtectionProgress,
+                        protectionPathRect.centerX(), protectionPathRect.centerY())
+            }
+            scaledProtectionPath.transform(scaleMatrix)
+            paint.style = Paint.Style.FILL
+            paint.color = cameraProtectionColor
+            canvas.drawPath(scaledProtectionPath, paint)
+        }
+    }
+
+    override fun enableShowProtection(show: Boolean) {
+        val showScanningAnimNow = keyguardUpdateMonitor.isFaceScanning && show
+        if (showScanningAnimNow == showScanningAnim) {
+            return
+        }
+
+        showScanningAnim = showScanningAnimNow
+        updateProtectionBoundingPath()
+        // Delay the relayout until the end of the animation when hiding,
+        // otherwise we'd clip it.
+        if (showScanningAnim) {
+            visibility = View.VISIBLE
+            requestLayout()
+        }
+
+        cameraProtectionAnimator?.cancel()
+        cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress,
+                if (showScanningAnimNow) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).apply {
+            startDelay = if (showScanningAnim) 0 else PULSE_DISAPPEAR_DURATION
+            duration = if (showScanningAnim) PULSE_APPEAR_DURATION else
+                CAMERA_PROTECTION_DISAPPEAR_DURATION
+            interpolator = if (showScanningAnim) Interpolators.STANDARD else
+                Interpolators.EMPHASIZED
+
+            addUpdateListener(ValueAnimator.AnimatorUpdateListener {
+                animation: ValueAnimator ->
+                cameraProtectionProgress = animation.animatedValue as Float
+                invalidate()
+            })
+            addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    cameraProtectionAnimator = null
+                    if (!showScanningAnim) {
+                        visibility = View.INVISIBLE
+                        hideOverlayRunnable?.run()
+                        hideOverlayRunnable = null
+                        requestLayout()
+                    }
+                }
+            })
+            start()
+        }
+
+        rimAnimator?.cancel()
+        rimAnimator = AnimatorSet().apply {
+            val rimAppearOrDisappearAnimator = ValueAnimator.ofFloat(rimProgress,
+                    if (showScanningAnim) PULSE_RADIUS_OUT else (PULSE_RADIUS_IN * 1.15f)).apply {
+                duration = if (showScanningAnim) PULSE_APPEAR_DURATION else PULSE_DISAPPEAR_DURATION
+                interpolator = Interpolators.STANDARD
+                addUpdateListener(ValueAnimator.AnimatorUpdateListener {
+                    animation: ValueAnimator ->
+                    rimProgress = animation.animatedValue as Float
+                    invalidate()
+                })
+            }
+            if (showScanningAnim) {
+                // appear and then pulse in/out
+                playSequentially(rimAppearOrDisappearAnimator,
+                        createPulseAnimator(), createPulseAnimator(),
+                        createPulseAnimator(), createPulseAnimator(),
+                        createPulseAnimator(), createPulseAnimator())
+            } else {
+                val opacityAnimator = ValueAnimator.ofInt(255, 0).apply {
+                    duration = PULSE_DISAPPEAR_DURATION
+                    interpolator = Interpolators.LINEAR
+                    addUpdateListener(ValueAnimator.AnimatorUpdateListener {
+                        animation: ValueAnimator ->
+                        rimPaint.alpha = animation.animatedValue as Int
+                        invalidate()
+                    })
+                }
+                addListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        rimProgress = HIDDEN_RIM_SCALE
+                        rimPaint.alpha = 255
+                        invalidate()
+                    }
+                })
+
+                // disappear
+                playTogether(rimAppearOrDisappearAnimator, opacityAnimator)
+            }
+
+            addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    rimAnimator = null
+                    if (!showScanningAnim) {
+                        requestLayout()
+                    }
+                }
+            })
+            start()
+        }
+    }
+
+    fun createPulseAnimator(): AnimatorSet {
+        return AnimatorSet().apply {
+            val pulseInwards = ValueAnimator.ofFloat(
+                    PULSE_RADIUS_OUT, PULSE_RADIUS_IN).apply {
+                duration = PULSE_DURATION_INWARDS
+                interpolator = Interpolators.STANDARD
+                addUpdateListener(ValueAnimator.AnimatorUpdateListener {
+                    animation: ValueAnimator ->
+                    rimProgress = animation.animatedValue as Float
+                    invalidate()
+                })
+            }
+            val pulseOutwards = ValueAnimator.ofFloat(
+                    PULSE_RADIUS_IN, PULSE_RADIUS_OUT).apply {
+                duration = PULSE_DURATION_OUTWARDS
+                interpolator = Interpolators.STANDARD
+                addUpdateListener(ValueAnimator.AnimatorUpdateListener {
+                    animation: ValueAnimator ->
+                    rimProgress = animation.animatedValue as Float
+                    invalidate()
+                })
+            }
+            playSequentially(pulseInwards, pulseOutwards)
+        }
+    }
+
+    override fun updateProtectionBoundingPath() {
+        super.updateProtectionBoundingPath()
+        rimRect.set(protectionRect)
+        rimRect.scale(rimProgress)
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        if (mBounds.isEmpty()) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+            return
+        }
+        if (showScanningAnim) {
+            // Make sure that our measured height encompasses the extra space for the animation
+            mTotalBounds.union(mBoundingRect)
+            mTotalBounds.union(
+                    rimRect.left.toInt(),
+                    rimRect.top.toInt(),
+                    rimRect.right.toInt(),
+                    rimRect.bottom.toInt())
+            setMeasuredDimension(
+                    resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
+                    resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
+        } else {
+            setMeasuredDimension(
+                    resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
+                    resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0))
+        }
+    }
+
+    companion object {
+        private const val HIDDEN_RIM_SCALE = HIDDEN_CAMERA_PROTECTION_SCALE
+
+        private const val PULSE_APPEAR_DURATION = 350L
+        private const val PULSE_DURATION_INWARDS = 500L
+        private const val PULSE_DURATION_OUTWARDS = 500L
+        private const val PULSE_DISAPPEAR_DURATION = 850L
+        private const val CAMERA_PROTECTION_DISAPPEAR_DURATION = 700L // excluding start delay
+        private const val PULSE_RADIUS_IN = 1.15f
+        private const val PULSE_RADIUS_OUT = 1.25f
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
index 0df2730..a74f2f8 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
@@ -44,7 +44,7 @@
 import kotlin.math.floor
 
 /**
- * When the HWC of the device supports Composition.DISPLAY_DECORATON, we use this layer to draw
+ * When the HWC of the device supports Composition.DISPLAY_DECORATION, we use this layer to draw
  * screen decorations.
  */
 class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport)
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index dc1a1e0..e09c16c 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -69,12 +69,14 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.util.Preconditions;
+import com.android.settingslib.Utils;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.decor.DecorProvider;
 import com.android.systemui.decor.DecorProviderFactory;
 import com.android.systemui.decor.DecorProviderKt;
+import com.android.systemui.decor.FaceScanningProviderFactory;
 import com.android.systemui.decor.OverlayWindow;
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
@@ -90,8 +92,10 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -131,6 +135,8 @@
     private final PrivacyDotViewController mDotViewController;
     private final ThreadFactory mThreadFactory;
     private final DecorProviderFactory mDotFactory;
+    private final FaceScanningProviderFactory mFaceScanningFactory;
+    public final int mFaceScanningViewId;
 
     @VisibleForTesting
     protected RoundedCornerResDelegate mRoundedCornerResDelegate;
@@ -161,46 +167,84 @@
     @VisibleForTesting
     protected DisplayInfo mDisplayInfo = new DisplayInfo();
 
+    @VisibleForTesting
+    protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
+        if (mFaceScanningFactory.shouldShowFaceScanningAnim()) {
+            DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(
+                    mFaceScanningViewId);
+            if (overlay != null) {
+                overlay.setProtection(protectionPath, bounds);
+                overlay.enableShowProtection(true);
+                updateOverlayWindowVisibilityIfViewExists(
+                        overlay.findViewById(mFaceScanningViewId));
+                // immediately return, bc FaceScanningOverlay also renders the camera
+                // protection, so we don't need to show the camera protection in
+                // mScreenDecorHwcLayer or mCutoutViews
+                return;
+            }
+        }
+
+        if (mScreenDecorHwcLayer != null) {
+            mScreenDecorHwcLayer.setProtection(protectionPath, bounds);
+            mScreenDecorHwcLayer.enableShowProtection(true);
+            return;
+        }
+
+        if (mCutoutViews == null) {
+            Log.w(TAG, "DisplayCutoutView not initialized onApplyCameraProtection");
+            return;
+        }
+
+        // Show the extra protection around the front facing camera if necessary
+        for (DisplayCutoutView dcv : mCutoutViews) {
+            // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
+            if (dcv != null) {
+                dcv.setProtection(protectionPath, bounds);
+                dcv.enableShowProtection(true);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    protected void hideCameraProtection() {
+        FaceScanningOverlay faceScanningOverlay =
+                (FaceScanningOverlay) getOverlayView(mFaceScanningViewId);
+        if (faceScanningOverlay != null) {
+            faceScanningOverlay.setHideOverlayRunnable(() -> {
+                updateOverlayWindowVisibilityIfViewExists(
+                        faceScanningOverlay.findViewById(mFaceScanningViewId));
+            });
+            faceScanningOverlay.enableShowProtection(false);
+        }
+
+        if (mScreenDecorHwcLayer != null) {
+            mScreenDecorHwcLayer.enableShowProtection(false);
+            return;
+        }
+
+        if (mCutoutViews == null) {
+            Log.w(TAG, "DisplayCutoutView not initialized onHideCameraProtection");
+            return;
+        }
+        // Go back to the regular anti-aliasing
+        for (DisplayCutoutView dcv : mCutoutViews) {
+            // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
+            if (dcv != null) {
+                dcv.enableShowProtection(false);
+            }
+        }
+    }
+
     private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
             new CameraAvailabilityListener.CameraTransitionCallback() {
         @Override
         public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
-            if (mScreenDecorHwcLayer != null) {
-                mScreenDecorHwcLayer.setProtection(protectionPath, bounds);
-                mScreenDecorHwcLayer.enableShowProtection(true);
-                return;
-            }
-            if (mCutoutViews == null) {
-                Log.w(TAG, "DisplayCutoutView do not initialized");
-                return;
-            }
-            // Show the extra protection around the front facing camera if necessary
-            for (DisplayCutoutView dcv : mCutoutViews) {
-                // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
-                if (dcv != null) {
-                    dcv.setProtection(protectionPath, bounds);
-                    dcv.enableShowProtection(true);
-                }
-            }
+            showCameraProtection(protectionPath, bounds);
         }
 
         @Override
         public void onHideCameraProtection() {
-            if (mScreenDecorHwcLayer != null) {
-                mScreenDecorHwcLayer.enableShowProtection(false);
-                return;
-            }
-            if (mCutoutViews == null) {
-                Log.w(TAG, "DisplayCutoutView do not initialized");
-                return;
-            }
-            // Go back to the regular anti-aliasing
-            for (DisplayCutoutView dcv : mCutoutViews) {
-                // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
-                if (dcv != null) {
-                    dcv.enableShowProtection(false);
-                }
-            }
+            hideCameraProtection();
         }
     };
 
@@ -209,25 +253,24 @@
             new PrivacyDotViewController.ShowingListener() {
         @Override
         public void onPrivacyDotShown(@Nullable View v) {
-            setOverlayWindowVisibilityIfViewExist(v, View.VISIBLE);
+            updateOverlayWindowVisibilityIfViewExists(v);
         }
 
         @Override
         public void onPrivacyDotHidden(@Nullable View v) {
-            setOverlayWindowVisibilityIfViewExist(v, View.INVISIBLE);
+            updateOverlayWindowVisibilityIfViewExists(v);
         }
     };
 
     @VisibleForTesting
-    protected void setOverlayWindowVisibilityIfViewExist(@Nullable View view,
-            @View.Visibility int visibility) {
+    protected void updateOverlayWindowVisibilityIfViewExists(@Nullable View view) {
         if (view == null) {
             return;
         }
         mExecutor.execute(() -> {
             // We don't need to control the window visibility if rounded corners or cutout is drawn
             // on sw layer since the overlay windows are always visible in this case.
-            if (mOverlays == null || !isOnlyPrivacyDotInSwLayer()) {
+            if (mOverlays == null || !shouldOptimizeVisibility()) {
                 return;
             }
 
@@ -236,7 +279,7 @@
                     continue;
                 }
                 if (overlay.getView(view.getId()) != null) {
-                    overlay.getRootView().setVisibility(visibility);
+                    overlay.getRootView().setVisibility(getWindowVisibility(overlay, true));
                     return;
                 }
             }
@@ -258,7 +301,8 @@
             UserTracker userTracker,
             PrivacyDotViewController dotViewController,
             ThreadFactory threadFactory,
-            PrivacyDotDecorProviderFactory dotFactory) {
+            PrivacyDotDecorProviderFactory dotFactory,
+            FaceScanningProviderFactory faceScanningFactory) {
         super(context);
         mMainExecutor = mainExecutor;
         mSecureSettings = secureSettings;
@@ -268,6 +312,8 @@
         mDotViewController = dotViewController;
         mThreadFactory = threadFactory;
         mDotFactory = dotFactory;
+        mFaceScanningFactory = faceScanningFactory;
+        mFaceScanningViewId = com.android.systemui.R.id.face_scanning_anim;
     }
 
     @Override
@@ -289,6 +335,7 @@
     @NonNull
     private List<DecorProvider> getProviders(boolean hasHwLayer) {
         List<DecorProvider> decorProviders = new ArrayList<>(mDotFactory.getProviders());
+        decorProviders.addAll(mFaceScanningFactory.getProviders());
         if (!hasHwLayer) {
             decorProviders.addAll(mRoundedCornerFactory.getProviders());
         }
@@ -466,7 +513,6 @@
             if (overlay == null) {
                 continue;
             }
-
             final View view = overlay.getView(id);
             if (view != null) {
                 return view;
@@ -503,7 +549,9 @@
     }
 
     private void setupDecorations() {
-        if (hasRoundedCorners() || shouldDrawCutout() || isPrivacyDotEnabled()) {
+        if (hasRoundedCorners() || shouldDrawCutout() || isPrivacyDotEnabled()
+                || mFaceScanningFactory.getHasProviders()) {
+
             List<DecorProvider> decorProviders = getProviders(mHwcScreenDecorationSupport != null);
             removeRedundantOverlayViews(decorProviders);
 
@@ -512,21 +560,24 @@
             } else {
                 removeHwcOverlay();
             }
+
             final DisplayCutout cutout = getCutout();
-            final boolean isOnlyPrivacyDotInSwLayer = isOnlyPrivacyDotInSwLayer();
+            final boolean shouldOptimizeVisibility = shouldOptimizeVisibility();
             for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
-                if (shouldShowSwLayerCutout(i, cutout) || shouldShowSwLayerRoundedCorner(i, cutout)
+                if (shouldShowSwLayerCutout(i, cutout)
+                        || shouldShowSwLayerFaceScan(i, cutout)
+                        || shouldShowSwLayerRoundedCorner(i, cutout)
                         || shouldShowSwLayerPrivacyDot(i, cutout)) {
                     Pair<List<DecorProvider>, List<DecorProvider>> pair =
                             DecorProviderKt.partitionAlignedBound(decorProviders, i);
                     decorProviders = pair.getSecond();
-                    createOverlay(i, pair.getFirst(), isOnlyPrivacyDotInSwLayer);
+                    createOverlay(i, pair.getFirst(), shouldOptimizeVisibility);
                 } else {
                     removeOverlay(i);
                 }
             }
 
-            if (isOnlyPrivacyDotInSwLayer) {
+            if (shouldOptimizeVisibility) {
                 mDotViewController.setShowingListener(mPrivacyDotShowingListener);
             } else {
                 mDotViewController.setShowingListener(null);
@@ -565,6 +616,7 @@
             }
             mColorInversionSetting.setListening(true);
             mColorInversionSetting.onChange(false);
+            updateColorInversion(mColorInversionSetting.getValue());
 
             IntentFilter filter = new IntentFilter();
             filter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -626,59 +678,61 @@
 
     @View.Visibility
     private int getWindowVisibility(@NonNull OverlayWindow overlay,
-            boolean isOnlyPrivacyDotInSwLayer) {
-        if (!isOnlyPrivacyDotInSwLayer) {
-            // Multiple views inside overlay, no need to optimize
+            boolean shouldOptimizeVisibility) {
+        if (!shouldOptimizeVisibility) {
+            // All overlays have visible views so there's no need to optimize visibility.
+            // For example, the rounded corners could exist in each overlay and since the rounded
+            // corners are always visible, there's no need to optimize visibility.
             return View.VISIBLE;
         }
 
+        // Optimize if it's just the privacy dot & face scanning animation, since the privacy
+        // dot and face scanning overlay aren't always visible.
         int[] ids = {
                 R.id.privacy_dot_top_left_container,
                 R.id.privacy_dot_top_right_container,
                 R.id.privacy_dot_bottom_left_container,
-                R.id.privacy_dot_bottom_right_container
+                R.id.privacy_dot_bottom_right_container,
+                mFaceScanningViewId
         };
         for (int id: ids) {
-            final View view = overlay.getView(id);
-            if (view != null && view.getVisibility() == View.VISIBLE) {
-                // Only privacy dot in sw layers, overlay shall be VISIBLE if one of privacy dot
-                // views inside this overlay is VISIBLE
+            final View notAlwaysVisibleViews = overlay.getView(id);
+            if (notAlwaysVisibleViews != null
+                    && notAlwaysVisibleViews.getVisibility() == View.VISIBLE) {
+                // Overlay is VISIBLE if one the views inside this overlay is VISIBLE
                 return View.VISIBLE;
             }
         }
-        // Only privacy dot in sw layers, overlay shall be INVISIBLE like default if no privacy dot
-        // view inside this overlay is VISIBLE.
+
+        // Only non-visible views in this overlay, so set overlay to INVISIBLE
         return View.INVISIBLE;
     }
 
     private void createOverlay(
             @BoundsPosition int pos,
             @NonNull List<DecorProvider> decorProviders,
-            boolean isOnlyPrivacyDotInSwLayer) {
+            boolean shouldOptimizeVisibility) {
         if (mOverlays == null) {
             mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH];
         }
 
         if (mOverlays[pos] != null) {
-            initOverlay(mOverlays[pos], decorProviders, isOnlyPrivacyDotInSwLayer);
+            initOverlay(mOverlays[pos], decorProviders, shouldOptimizeVisibility);
             return;
         }
-
         mOverlays[pos] = new OverlayWindow(mContext);
-        initOverlay(mOverlays[pos], decorProviders, isOnlyPrivacyDotInSwLayer);
+        initOverlay(mOverlays[pos], decorProviders, shouldOptimizeVisibility);
         final ViewGroup overlayView = mOverlays[pos].getRootView();
         overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
         overlayView.setAlpha(0);
         overlayView.setForceDarkAllowed(false);
 
-        // Only show cutout and rounded corners in mOverlays when hwc don't support screen
-        // decoration.
+        // Only show cutout in mOverlays when hwc doesn't support screen decoration
         if (mHwcScreenDecorationSupport == null) {
             if (mCutoutViews == null) {
                 mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH];
             }
             mCutoutViews[pos] = new DisplayCutoutView(mContext, pos);
-            mCutoutViews[pos].setColor(mTintColor);
             overlayView.addView(mCutoutViews[pos]);
             mCutoutViews[pos].updateRotation(mRotation);
         }
@@ -736,7 +790,7 @@
     private void initOverlay(
             @NonNull OverlayWindow overlay,
             @NonNull List<DecorProvider> decorProviders,
-            boolean isOnlyPrivacyDotInSwLayer) {
+            boolean shouldOptimizeVisibility) {
         if (!overlay.hasSameProviders(decorProviders)) {
             decorProviders.forEach(provider -> {
                 if (overlay.getView(provider.getViewId()) != null) {
@@ -746,9 +800,10 @@
                 overlay.addDecorProvider(provider, mRotation);
             });
         }
-        // Use visibility of privacy dot views if only privacy dot in sw layer
-        overlay.getRootView().setVisibility(
-                getWindowVisibility(overlay, isOnlyPrivacyDotInSwLayer));
+        // Use visibility of privacy dot views & face scanning view to determine the overlay's
+        // visibility if the screen decoration SW layer overlay isn't persistenly showing
+        // (ie: rounded corners always showing in SW layer)
+        overlay.getRootView().setVisibility(getWindowVisibility(overlay, shouldOptimizeVisibility));
     }
 
     @VisibleForTesting
@@ -888,23 +943,28 @@
             mTintColor = Color.RED;
         }
 
-        // When the hwc supports screen decorations, the layer will use the A8 color mode which
-        // won't be affected by the color inversion. If the composition goes the client composition
-        // route, the color inversion will be handled by the RenderEngine.
-        if (mOverlays == null || mHwcScreenDecorationSupport != null) {
+        if (mOverlays == null) {
             return;
         }
 
-        ColorStateList tintList = ColorStateList.valueOf(mTintColor);
-        mRoundedCornerResDelegate.setColorTintList(tintList);
-
-        Integer[] roundedCornerIds = {
-                R.id.rounded_corner_top_left,
-                R.id.rounded_corner_top_right,
-                R.id.rounded_corner_bottom_left,
-                R.id.rounded_corner_bottom_right
-        };
-
+        // When the hwc supports screen decorations, the layer will use the A8 color mode which
+        // won't be affected by the color inversion. If the composition goes the client composition
+        // route, the color inversion will be handled by the RenderEngine.
+        final Set<Integer> viewsMayNeedColorUpdate = new HashSet<>();
+        if (mHwcScreenDecorationSupport == null) {
+            ColorStateList tintList = ColorStateList.valueOf(mTintColor);
+            mRoundedCornerResDelegate.setColorTintList(tintList);
+            viewsMayNeedColorUpdate.add(R.id.rounded_corner_top_left);
+            viewsMayNeedColorUpdate.add(R.id.rounded_corner_top_right);
+            viewsMayNeedColorUpdate.add(R.id.rounded_corner_bottom_left);
+            viewsMayNeedColorUpdate.add(R.id.rounded_corner_bottom_right);
+            viewsMayNeedColorUpdate.add(R.id.display_cutout);
+        }
+        if (mFaceScanningFactory.getHasProviders()) {
+            viewsMayNeedColorUpdate.add(mFaceScanningViewId);
+        }
+        final Integer[] views = new Integer[viewsMayNeedColorUpdate.size()];
+        viewsMayNeedColorUpdate.toArray(views);
         for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
             if (mOverlays[i] == null) {
                 continue;
@@ -914,12 +974,13 @@
             View child;
             for (int j = 0; j < size; j++) {
                 child = overlayView.getChildAt(j);
-                if (child instanceof DisplayCutoutView) {
+                if (viewsMayNeedColorUpdate.contains(child.getId())
+                        && child instanceof DisplayCutoutView) {
                     ((DisplayCutoutView) child).setColor(mTintColor);
                 }
             }
-            mOverlays[i].onReloadResAndMeasure(roundedCornerIds, mProviderRefreshToken, mRotation,
-                    mDisplayUniqueId);
+            mOverlays[i].onReloadResAndMeasure(views, mProviderRefreshToken,
+                    mRotation, mDisplayUniqueId);
         }
     }
 
@@ -976,7 +1037,15 @@
         }
 
         pw.println("  mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
-        pw.println("  isOnlyPrivacyDotInSwLayer:" + isOnlyPrivacyDotInSwLayer());
+        pw.println("  shouldOptimizeOverlayVisibility:" + shouldOptimizeVisibility());
+        final boolean supportsShowingFaceScanningAnim = mFaceScanningFactory.getHasProviders();
+        pw.println("    supportsShowingFaceScanningAnim:" + supportsShowingFaceScanningAnim);
+        if (supportsShowingFaceScanningAnim) {
+            pw.println("      canShowFaceScanningAnim:"
+                    + mFaceScanningFactory.canShowFaceScanningAnim());
+            pw.println("      shouldShowFaceScanningAnim (at time dump was taken):"
+                    + mFaceScanningFactory.shouldShowFaceScanningAnim());
+        }
         pw.println("  mPendingConfigChange:" + mPendingConfigChange);
         if (mHwcScreenDecorationSupport != null) {
             pw.println("  mHwcScreenDecorationSupport:");
@@ -1045,6 +1114,16 @@
             // update all provider views inside overlay
             updateOverlayProviderViews();
         }
+
+        if (mFaceScanningFactory.getHasProviders()) {
+            FaceScanningOverlay faceScanningOverlay =
+                    (FaceScanningOverlay) getOverlayView(mFaceScanningViewId);
+            if (faceScanningOverlay != null) {
+                faceScanningOverlay.setFaceScanningAnimColor(
+                        Utils.getColorAttrDefaultColor(faceScanningOverlay.getContext(),
+                                com.android.systemui.R.attr.wallpaperTextColorAccent));
+            }
+        }
     }
 
     private boolean hasRoundedCorners() {
@@ -1078,6 +1157,11 @@
         return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout);
     }
 
+    private boolean shouldShowSwLayerFaceScan(@BoundsPosition int pos,
+            @Nullable DisplayCutout cutout) {
+        return mFaceScanningFactory.getHasProviders() && isDefaultShownOverlayPos(pos, cutout);
+    }
+
     private boolean shouldShowSwLayerCutout(@BoundsPosition int pos,
             @Nullable DisplayCutout cutout) {
         final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
@@ -1086,8 +1170,8 @@
                 && mHwcScreenDecorationSupport == null);
     }
 
-    private boolean isOnlyPrivacyDotInSwLayer() {
-        return isPrivacyDotEnabled()
+    private boolean shouldOptimizeVisibility() {
+        return (isPrivacyDotEnabled() || mFaceScanningFactory.getHasProviders())
                 && (mHwcScreenDecorationSupport != null
                     || (!hasRoundedCorners() && !shouldDrawCutout())
                 );
@@ -1201,9 +1285,9 @@
     }
 
     public static class DisplayCutoutView extends DisplayCutoutBaseView {
-        private final List<Rect> mBounds = new ArrayList();
-        private final Rect mBoundingRect = new Rect();
-        private Rect mTotalBounds = new Rect();
+        final List<Rect> mBounds = new ArrayList();
+        final Rect mBoundingRect = new Rect();
+        Rect mTotalBounds = new Rect();
 
         private int mColor = Color.BLACK;
         private int mRotation;
@@ -1213,6 +1297,7 @@
         public DisplayCutoutView(Context context, @BoundsPosition int pos) {
             super(context);
             mInitialPosition = pos;
+
             paint.setColor(mColor);
             paint.setStyle(Paint.Style.FILL);
             setId(R.id.display_cutout);
@@ -1309,7 +1394,7 @@
             }
 
             if (showProtection) {
-                // Make sure that our measured height encompases the protection
+                // Make sure that our measured height encompasses the protection
                 mTotalBounds.union(mBoundingRect);
                 mTotalBounds.union((int) protectionRect.left, (int) protectionRect.top,
                         (int) protectionRect.right, (int) protectionRect.bottom);
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
index 03ee8b1..169b50e 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
@@ -79,4 +79,16 @@
     override val alignedBounds: List<Int> by lazy {
         listOf(alignedBound1, alignedBound2)
     }
-}
\ No newline at end of file
+}
+
+/**
+ * A provider for view shown on bound.
+ */
+abstract class BoundDecorProvider : DecorProvider() {
+    /** The bound which a view is aligned based on rotation 0 */
+    @DisplayCutout.BoundsPosition protected abstract val alignedBound: Int
+
+    override val alignedBounds: List<Int> by lazy {
+        listOf(alignedBound)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
new file mode 100644
index 0000000..22a7cd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 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.decor
+
+import android.content.Context
+import android.util.Log
+import android.view.DisplayCutout
+import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM
+import android.view.DisplayCutout.BOUNDS_POSITION_LEFT
+import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT
+import android.view.DisplayCutout.BOUNDS_POSITION_TOP
+import android.view.DisplayInfo
+import android.view.Gravity
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.FaceScanningOverlay
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import javax.inject.Inject
+
+@SysUISingleton
+class FaceScanningProviderFactory @Inject constructor(
+    private val authController: AuthController,
+    private val context: Context,
+    private val statusBarStateController: StatusBarStateController,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val featureFlags: FeatureFlags
+) : DecorProviderFactory() {
+    private val display = context.display
+    private val displayInfo = DisplayInfo()
+
+    override val hasProviders: Boolean
+        get() {
+            // update display info:
+            display?.getDisplayInfo(displayInfo) ?: run {
+                Log.w(TAG, "display is null, can't update displayInfo")
+            }
+            val hasDisplayCutout = DisplayCutout.getFillBuiltInDisplayCutout(
+                    context.resources, displayInfo.uniqueId)
+            return hasDisplayCutout &&
+                    authController.faceAuthSensorLocation != null &&
+                    featureFlags.isEnabled(Flags.FACE_SCANNING_ANIM)
+        }
+
+    override val providers: List<DecorProvider>
+        get() {
+            if (!hasProviders) {
+                return emptyList()
+            }
+
+            return ArrayList<DecorProvider>().also { list ->
+                // displayInfo must be updated before using it; however it will already have
+                // been updated when accessing the hasProviders field above
+                displayInfo.displayCutout?.getBoundBaseOnCurrentRotation()?.let { bounds ->
+                    // Add a face scanning view for each screen orientation.
+                    // Cutout drawing is updated in ScreenDecorations#updateCutout
+                    for (bound in bounds) {
+                        list.add(
+                                FaceScanningOverlayProviderImpl(
+                                        bound.baseOnRotation0(displayInfo.rotation),
+                                        authController,
+                                        statusBarStateController,
+                                        keyguardUpdateMonitor)
+                        )
+                    }
+                }
+            }
+        }
+
+    fun canShowFaceScanningAnim(): Boolean {
+        return hasProviders && keyguardUpdateMonitor.isFaceEnrolled
+    }
+
+    fun shouldShowFaceScanningAnim(): Boolean {
+        return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceScanning
+    }
+}
+
+class FaceScanningOverlayProviderImpl(
+    override val alignedBound: Int,
+    private val authController: AuthController,
+    private val statusBarStateController: StatusBarStateController,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor
+) : BoundDecorProvider() {
+    override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
+
+    override fun onReloadResAndMeasure(
+        view: View,
+        reloadToken: Int,
+        rotation: Int,
+        displayUniqueId: String?
+    ) {
+        // no need to handle rotation changes
+    }
+
+    override fun inflateView(
+        context: Context,
+        parent: ViewGroup,
+        @Surface.Rotation rotation: Int
+    ): View {
+        val view = FaceScanningOverlay(
+                context,
+                alignedBound,
+                statusBarStateController,
+                keyguardUpdateMonitor)
+        view.id = viewId
+        view.visibility = View.INVISIBLE // only show this view when face scanning is happening
+        var heightLayoutParam = ViewGroup.LayoutParams.MATCH_PARENT
+        authController.faceAuthSensorLocation?.y?.let {
+            heightLayoutParam = (it * 3).toInt()
+        }
+        parent.addView(view, FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                heightLayoutParam,
+                Gravity.TOP or Gravity.START))
+        return view
+    }
+}
+
+fun DisplayCutout.getBoundBaseOnCurrentRotation(): List<Int> {
+    return ArrayList<Int>().also {
+        if (!boundingRectLeft.isEmpty) {
+            it.add(BOUNDS_POSITION_LEFT)
+        }
+        if (!boundingRectTop.isEmpty) {
+            it.add(BOUNDS_POSITION_TOP)
+        }
+        if (!boundingRectRight.isEmpty) {
+            it.add(BOUNDS_POSITION_RIGHT)
+        }
+        if (!boundingRectBottom.isEmpty) {
+            it.add(BOUNDS_POSITION_BOTTOM)
+        }
+    }
+}
+
+fun Int.baseOnRotation0(@DisplayCutout.BoundsPosition currentRotation: Int): Int {
+    return when (currentRotation) {
+        Surface.ROTATION_0 -> this
+        Surface.ROTATION_90 -> when (this) {
+            BOUNDS_POSITION_LEFT -> BOUNDS_POSITION_TOP
+            BOUNDS_POSITION_TOP -> BOUNDS_POSITION_RIGHT
+            BOUNDS_POSITION_RIGHT -> BOUNDS_POSITION_BOTTOM
+            else /* BOUNDS_POSITION_BOTTOM */ -> BOUNDS_POSITION_LEFT
+        }
+        Surface.ROTATION_270 -> when (this) {
+            BOUNDS_POSITION_LEFT -> BOUNDS_POSITION_BOTTOM
+            BOUNDS_POSITION_TOP -> BOUNDS_POSITION_LEFT
+            BOUNDS_POSITION_RIGHT -> BOUNDS_POSITION_TOP
+            else /* BOUNDS_POSITION_BOTTOM */ -> BOUNDS_POSITION_RIGHT
+        }
+        else /* Surface.ROTATION_180 */ -> when (this) {
+            BOUNDS_POSITION_LEFT -> BOUNDS_POSITION_RIGHT
+            BOUNDS_POSITION_TOP -> BOUNDS_POSITION_BOTTOM
+            BOUNDS_POSITION_RIGHT -> BOUNDS_POSITION_LEFT
+            else /* BOUNDS_POSITION_BOTTOM */ -> BOUNDS_POSITION_TOP
+        }
+    }
+}
+
+private const val TAG = "FaceScanningProvider"
diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
index 136f135..9f624b3 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -28,6 +28,10 @@
 import com.android.systemui.dagger.qualifiers.Main
 import javax.inject.Inject
 
+/**
+ * Provides privacy dot views for each orientation. The PrivacyDot orientation and visibility
+ * of the privacy dot views are controlled by the PrivacyDotViewController.
+ */
 @SysUISingleton
 class PrivacyDotDecorProviderFactory @Inject constructor(
     @Main private val res: Resources
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index afa7d5e..3c6253a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -80,6 +80,9 @@
     public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER =
             new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher);
 
+    public static final ResourceBooleanFlag FACE_SCANNING_ANIM =
+            new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation);
+
     /***************************************/
     // 300 - power menu
     public static final BooleanFlag POWER_MENU_LITE =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 4f27fb4..d88f07c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -195,7 +195,6 @@
 
     @UiThread
     private fun showDotView(dot: View, animate: Boolean) {
-        showingListener?.onPrivacyDotShown(dot)
         dot.clearAnimation()
         if (animate) {
             dot.visibility = View.VISIBLE
@@ -209,6 +208,7 @@
             dot.visibility = View.VISIBLE
             dot.alpha = 1f
         }
+        showingListener?.onPrivacyDotShown(dot)
     }
 
     // Update the gravity and margins of the privacy views
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 92e4947..a35efa9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -50,6 +50,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Insets;
+import android.graphics.Path;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -75,14 +76,19 @@
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.decor.CornerDecorProvider;
 import com.android.systemui.decor.DecorProvider;
 import com.android.systemui.decor.DecorProviderFactory;
+import com.android.systemui.decor.FaceScanningOverlayProviderImpl;
+import com.android.systemui.decor.FaceScanningProviderFactory;
 import com.android.systemui.decor.OverlayWindow;
 import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
@@ -113,6 +119,13 @@
     private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThreadFactory mThreadFactory;
     private ArrayList<DecorProvider> mPrivacyDecorProviders;
+    private ArrayList<DecorProvider> mFaceScanningProviders;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private AuthController mAuthController;
     @Mock
     private Display mDisplay;
     @Mock
@@ -128,6 +141,10 @@
     @Mock
     private PrivacyDotDecorProviderFactory mPrivacyDotDecorProviderFactory;
     @Mock
+    private FaceScanningProviderFactory mFaceScanningProviderFactory;
+    @Mock
+    private FaceScanningOverlayProviderImpl mFaceScanningDecorProvider;
+    @Mock
     private CornerDecorProvider mPrivacyDotTopLeftDecorProvider;
     @Mock
     private CornerDecorProvider mPrivacyDotTopRightDecorProvider;
@@ -189,9 +206,15 @@
                 DisplayCutout.BOUNDS_POSITION_RIGHT,
                 R.layout.privacy_dot_bottom_right));
 
+        mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
+                BOUNDS_POSITION_TOP,
+                mAuthController,
+                mStatusBarStateController,
+                mKeyguardUpdateMonitor));
+
         mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
                 mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
-                mThreadFactory, mPrivacyDotDecorProviderFactory) {
+                mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) {
             @Override
             public void start() {
                 super.start();
@@ -211,9 +234,8 @@
             }
 
             @Override
-            protected void setOverlayWindowVisibilityIfViewExist(@Nullable View view,
-                    @View.Visibility int visibility) {
-                super.setOverlayWindowVisibilityIfViewExist(view, visibility);
+            protected void updateOverlayWindowVisibilityIfViewExists(@Nullable View view) {
+                super.updateOverlayWindowVisibilityIfViewExists(view);
                 mExecutor.runAllReady();
             }
         });
@@ -269,6 +291,16 @@
         }
     }
 
+    private void verifyFaceScanningViewExists(final boolean exists) {
+        final View overlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView();
+        final View view = overlay.findViewById(mFaceScanningDecorProvider.getViewId());
+        if (exists) {
+            assertNotNull(view);
+        } else {
+            assertNull(view);
+        }
+    }
+
     @Nullable
     private View findViewFromOverlays(@IdRes int id) {
         for (OverlayWindow overlay: mScreenDecorations.mOverlays) {
@@ -393,10 +425,11 @@
     }
 
     @Test
-    public void testNoRounding_NoCutout_NoPrivacyDot() {
+    public void testNoRounding_NoCutout_NoPrivacyDot_NoFaceScanning() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // no cutout
         doReturn(null).when(mScreenDecorations).getCutout();
@@ -411,10 +444,11 @@
     }
 
     @Test
-    public void testNoRounding_NoCutout_PrivacyDot() {
+    public void testNoRounding_NoCutout_PrivacyDot_NoFaceScanning() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // no cutout
         doReturn(null).when(mScreenDecorations).getCutout();
@@ -435,6 +469,9 @@
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
 
+        // Face scanning doesn't exist
+        verifyFaceScanningViewExists(false);
+
         // One tunable.
         verify(mTunerService, times(1)).addTunable(any(), any());
         // Dot controller init
@@ -443,10 +480,11 @@
     }
 
     @Test
-    public void testRounding_NoCutout_NoPrivacyDot() {
+    public void testRounding_NoCutout_NoPrivacyDot_NoFaceScanning() {
         setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                20 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */);
+                20 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // no cutout
         doReturn(null).when(mScreenDecorations).getCutout();
@@ -464,6 +502,9 @@
         // Privacy dots shall not exist
         verifyDotViewsNullable(true);
 
+        // Face scanning doesn't exist
+        verifyFaceScanningViewExists(false);
+
         // One tunable.
         verify(mTunerService, times(1)).addTunable(any(), any());
         // No dot controller init
@@ -471,10 +512,11 @@
     }
 
     @Test
-    public void testRounding_NoCutout_PrivacyDot() {
+    public void testRounding_NoCutout_PrivacyDot_NoFaceScanning() {
         setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                20 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                20 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // no cutout
         doReturn(null).when(mScreenDecorations).getCutout();
@@ -494,6 +536,9 @@
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
 
+        // Face scanning doesn't exist
+        verifyFaceScanningViewExists(false);
+
         // One tunable.
         verify(mTunerService, times(1)).addTunable(any(), any());
         // Dot controller init
@@ -509,7 +554,8 @@
                 /* roundedTopDrawable */,
                 getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px)
                 /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
         // no cutout
         doReturn(null).when(mScreenDecorations).getCutout();
 
@@ -527,7 +573,8 @@
                 /* roundedTopDrawable */,
                 getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px)
                 /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // no cutout
         doReturn(null).when(mScreenDecorations).getCutout();
@@ -563,7 +610,8 @@
                 /* roundedTopDrawable */,
                 getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
                 /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // left cutout
         final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
@@ -598,7 +646,8 @@
     public void testNoRounding_CutoutShortEdge_NoPrivacyDot() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // top cutout
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -621,7 +670,8 @@
     public void testNoRounding_CutoutShortEdge_PrivacyDot() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // top cutout
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -655,7 +705,8 @@
     public void testNoRounding_CutoutLongEdge_NoPrivacyDot() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // left cutout
         final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
@@ -682,7 +733,8 @@
     public void testNoRounding_CutoutLongEdge_PrivacyDot() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // left cutout
         final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
@@ -709,7 +761,8 @@
     public void testRounding_CutoutShortEdge_NoPrivacyDot() {
         setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // top cutout
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -737,7 +790,8 @@
     public void testRounding_CutoutShortEdge_PrivacyDot() {
         setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */);
+                20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // top cutout
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -768,7 +822,8 @@
     public void testRounding_CutoutLongEdge_NoPrivacyDot() {
         setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // left cutout
         final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
@@ -786,7 +841,8 @@
     public void testRounding_CutoutLongEdge_PrivacyDot() {
         setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */);
+                20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // left cutout
         final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
@@ -806,7 +862,8 @@
     public void testRounding_CutoutShortAndLongEdge_NoPrivacyDot() {
         setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // top and left cutout
         final Rect[] bounds = {new Rect(0, 50, 1, 60), new Rect(9, 0, 10, 1), null, null};
@@ -825,7 +882,8 @@
     public void testRounding_CutoutShortAndLongEdge_PrivacyDot() {
         setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */);
+                20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // top and left cutout
         final Rect[] bounds = {new Rect(0, 50, 1, 60), new Rect(9, 0, 10, 1), null, null};
@@ -846,7 +904,8 @@
     public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_NoPrivacyDot() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // Set to short edge cutout(top).
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -869,7 +928,8 @@
     public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_PrivacyDot() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // Set to short edge cutout(top).
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -912,7 +972,8 @@
     public void testDelayedCutout_NoPrivacyDot() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // top cutout
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -935,7 +996,8 @@
     public void testDelayedCutout_PrivacyDot() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         // top cutout
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -980,7 +1042,8 @@
                 /* roundedTopDrawable */,
                 getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
                 /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning*/);
         mDisplayInfo.rotation = Surface.ROTATION_0;
 
         mScreenDecorations.start();
@@ -994,7 +1057,8 @@
                 /* roundedTopDrawable */,
                 getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
                 /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning*/);
         mDisplayInfo.rotation = Surface.ROTATION_270;
 
         mScreenDecorations.onConfigurationChanged(null);
@@ -1007,7 +1071,8 @@
     public void testOnlyRoundedCornerRadiusTop() {
         setupResources(0 /* radius */, 10 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         mScreenDecorations.start();
 
@@ -1028,7 +1093,8 @@
     public void testOnlyRoundedCornerRadiusBottom() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 20 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         mScreenDecorations.start();
 
@@ -1099,7 +1165,8 @@
     public void testSupportHwcLayer_SwitchFrom_NotSupport() {
         setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // top cutout
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -1127,7 +1194,8 @@
     public void testNotSupportHwcLayer_SwitchFrom_Support() {
         setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
         final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
         decorationSupport.format = PixelFormat.R_8;
         doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
@@ -1165,7 +1233,8 @@
                 /* roundedTopDrawable */,
                 getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
                 /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */,
+                true /* faceScanning */);
         final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
         decorationSupport.format = PixelFormat.R_8;
         doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
@@ -1182,19 +1251,33 @@
         // Make sure view found and window visibility changed as well
         final View view = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
                 .findViewById(R.id.privacy_dot_bottom_right_container);
+        view.setVisibility(View.VISIBLE);
         mPrivacyDotShowingListener.onPrivacyDotShown(view);
         assertEquals(View.VISIBLE,
                 mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView().getVisibility());
+        view.setVisibility(View.INVISIBLE);
         mPrivacyDotShowingListener.onPrivacyDotHidden(view);
         assertEquals(View.INVISIBLE,
                 mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView().getVisibility());
+
+        // Make sure face scanning view found and window visibility updates on camera protection
+        // update
+        final View faceScanView = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
+                .findViewById(mFaceScanningDecorProvider.getViewId());
+        when(mFaceScanningProviderFactory.shouldShowFaceScanningAnim()).thenReturn(true);
+        faceScanView.setVisibility(View.VISIBLE);
+        mScreenDecorations.showCameraProtection(new Path(), new Rect());
+        mExecutor.runAllReady();
+        assertEquals(View.VISIBLE,
+                mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView().getVisibility());
     }
 
     @Test
     public void testAutoShowHideOverlayWindowWhenNoRoundedAndNoCutout() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */,
+                true /* faceScanning */);
 
         // no cutout
         doReturn(null).when(mScreenDecorations).getCutout();
@@ -1206,19 +1289,33 @@
         // Make sure view found and window visibility changed as well
         final View view = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
                 .findViewById(R.id.privacy_dot_bottom_right_container);
+        view.setVisibility(View.VISIBLE);
         mPrivacyDotShowingListener.onPrivacyDotShown(view);
         assertEquals(View.VISIBLE,
                 mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView().getVisibility());
+        view.setVisibility(View.INVISIBLE);
         mPrivacyDotShowingListener.onPrivacyDotHidden(view);
         assertEquals(View.INVISIBLE,
                 mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView().getVisibility());
+
+        // Make sure face scanning view found and window visibility updates on camera protection
+        // update
+        final View faceScanView = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
+                .findViewById(mFaceScanningDecorProvider.getViewId());
+        faceScanView.setVisibility(View.VISIBLE);
+        when(mFaceScanningProviderFactory.shouldShowFaceScanningAnim()).thenReturn(true);
+        mScreenDecorations.showCameraProtection(new Path(), new Rect());
+        mExecutor.runAllReady();
+        assertEquals(View.VISIBLE,
+                mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView().getVisibility());
     }
 
     @Test
-    public void testHwcLayer_noPrivacyDot() {
+    public void testHwcLayer_noPrivacyDot_noFaceScanning() {
         setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
         final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
         decorationSupport.format = PixelFormat.R_8;
         doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
@@ -1236,10 +1333,11 @@
     }
 
     @Test
-    public void testHwcLayer_PrivacyDot() {
+    public void testHwcLayer_PrivacyDot_FaceScanning() {
         setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */,
+                true /* faceScanning */);
         final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
         decorationSupport.format = PixelFormat.R_8;
         doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
@@ -1257,13 +1355,16 @@
         verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
         verify(mDotViewController, times(1)).setShowingListener(
                 mScreenDecorations.mPrivacyDotShowingListener);
+
+        verifyFaceScanningViewExists(true);
     }
 
     @Test
     public void testOnDisplayChanged_hwcLayer() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
         final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
         decorationSupport.format = PixelFormat.R_8;
         doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
@@ -1288,7 +1389,8 @@
     public void testOnDisplayChanged_nonHwcLayer() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         // top cutout
         final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
@@ -1311,7 +1413,8 @@
     public void testHasSameProvidersWithNullOverlays() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */);
+                0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */,
+                false /* faceScanning */);
 
         mScreenDecorations.start();
 
@@ -1329,7 +1432,8 @@
     public void testHasSameProvidersWithPrivacyDots() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */);
+                0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */,
+                false /* faceScanning */);
 
         mScreenDecorations.start();
 
@@ -1366,7 +1470,7 @@
 
     private void setupResources(int radius, int radiusTop, int radiusBottom,
             @Nullable Drawable roundedTopDrawable, @Nullable Drawable roundedBottomDrawable,
-            int roundedPadding, boolean fillCutout, boolean privacyDot) {
+            int roundedPadding, boolean fillCutout, boolean privacyDot, boolean faceScanning) {
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.array.config_displayUniqueIdArray,
                 new String[]{});
@@ -1418,6 +1522,13 @@
         }
         when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mPrivacyDecorProviders);
         when(mPrivacyDotDecorProviderFactory.getHasProviders()).thenReturn(privacyDot);
+
+        mFaceScanningProviders = new ArrayList<>();
+        if (faceScanning) {
+            mFaceScanningProviders.add(mFaceScanningDecorProvider);
+        }
+        when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
+        when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(faceScanning);
     }
 
     private DisplayCutout getDisplayCutoutForRotation(Insets safeInsets, Rect[] cutoutBounds) {