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) {