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
This commit is contained in:
Beverly
2022-05-13 18:45:40 +00:00
parent 53661edb7f
commit c2be0bcad7
13 changed files with 813 additions and 141 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -1040,7 +1040,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
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

View File

@@ -279,7 +279,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView {
}
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,

View File

@@ -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
}
}

View File

@@ -44,7 +44,7 @@ import kotlin.math.ceil
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)

View File

@@ -69,12 +69,14 @@ import android.widget.FrameLayout;
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 com.android.systemui.util.settings.SecureSettings;
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
@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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
continue;
}
if (overlay.getView(view.getId()) != null) {
overlay.getRootView().setVisibility(visibility);
overlay.getRootView().setVisibility(getWindowVisibility(overlay, true));
return;
}
}
@@ -258,7 +301,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
UserTracker userTracker,
PrivacyDotViewController dotViewController,
ThreadFactory threadFactory,
PrivacyDotDecorProviderFactory dotFactory) {
PrivacyDotDecorProviderFactory dotFactory,
FaceScanningProviderFactory faceScanningFactory) {
super(context);
mMainExecutor = mainExecutor;
mSecureSettings = secureSettings;
@@ -268,6 +312,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
mDotViewController = dotViewController;
mThreadFactory = threadFactory;
mDotFactory = dotFactory;
mFaceScanningFactory = faceScanningFactory;
mFaceScanningViewId = com.android.systemui.R.id.face_scanning_anim;
}
@Override
@@ -289,6 +335,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
@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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
if (overlay == null) {
continue;
}
final View view = overlay.getView(id);
if (view != null) {
return view;
@@ -503,7 +549,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
} 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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
mColorInversionSetting.setListening(true);
mColorInversionSetting.onChange(false);
updateColorInversion(mColorInversionSetting.getValue());
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -626,59 +678,61 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
@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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
// 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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
&& mHwcScreenDecorationSupport == null);
}
private boolean isOnlyPrivacyDotInSwLayer() {
return isPrivacyDotEnabled()
private boolean shouldOptimizeVisibility() {
return (isPrivacyDotEnabled() || mFaceScanningFactory.getHasProviders())
&& (mHwcScreenDecorationSupport != null
|| (!hasRoundedCorners() && !shouldDrawCutout())
);
@@ -1201,9 +1285,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
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 class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
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);

View File

@@ -79,4 +79,16 @@ abstract class CornerDecorProvider : DecorProvider() {
override val alignedBounds: List<Int> by lazy {
listOf(alignedBound1, alignedBound2)
}
}
}
/**
* 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)
}
}

View File

@@ -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"

View File

@@ -28,6 +28,10 @@ import com.android.systemui.dagger.SysUISingleton
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

View File

@@ -80,6 +80,9 @@ public class Flags {
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 =

View File

@@ -195,7 +195,6 @@ class PrivacyDotViewController @Inject constructor(
@UiThread
private fun showDotView(dot: View, animate: Boolean) {
showingListener?.onPrivacyDotShown(dot)
dot.clearAnimation()
if (animate) {
dot.visibility = View.VISIBLE
@@ -209,6 +208,7 @@ class PrivacyDotViewController @Inject constructor(
dot.visibility = View.VISIBLE
dot.alpha = 1f
}
showingListener?.onPrivacyDotShown(dot)
}
// Update the gravity and margins of the privacy views

View File

@@ -50,6 +50,7 @@ import android.content.pm.PackageManager;
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.NonNull;
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
@Mock
private PrivacyDotDecorProviderFactory mPrivacyDotDecorProviderFactory;
@Mock
private FaceScanningProviderFactory mFaceScanningProviderFactory;
@Mock
private FaceScanningOverlayProviderImpl mFaceScanningDecorProvider;
@Mock
private CornerDecorProvider mPrivacyDotTopLeftDecorProvider;
@Mock
private CornerDecorProvider mPrivacyDotTopRightDecorProvider;
@@ -189,9 +206,15 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
}
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
/* 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
/* 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
/* 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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
/* 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
/* 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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
/* 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
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) {