/*
 * Copyright (C) 2017 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 static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;

import static com.google.common.truth.Truth.assertThat;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.annotation.IdRes;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.graphics.common.DisplayDecorationSupport;
import android.os.Handler;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.util.PathParser;
import android.util.Size;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowMetrics;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository;
import com.android.systemui.decor.CornerDecorProvider;
import com.android.systemui.decor.CutoutDecorProviderFactory;
import com.android.systemui.decor.CutoutDecorProviderImpl;
import com.android.systemui.decor.DebugRoundedCornerModel;
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.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.util.ArrayList;
import java.util.List;

@RunWithLooper
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ScreenDecorationsTest extends SysuiTestCase {

    private ScreenDecorations mScreenDecorations;
    private WindowManager mWindowManager;
    private DisplayManager mDisplayManager;
    private SecureSettings mSecureSettings;
    private FakeExecutor mExecutor;
    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
    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
    private CommandRegistry mCommandRegistry;
    @Mock
    private UserTracker mUserTracker;
    @Mock
    private PrivacyDotViewController mDotViewController;
    @Mock
    private TypedArray mMockTypedArray;
    @Mock
    private PrivacyDotDecorProviderFactory mPrivacyDotDecorProviderFactory;
    @Mock
    private FaceScanningProviderFactory mFaceScanningProviderFactory;
    @Mock
    private FaceScanningOverlayProviderImpl mFaceScanningDecorProvider;
    @Mock
    private CornerDecorProvider mPrivacyDotTopLeftDecorProvider;
    @Mock
    private CornerDecorProvider mPrivacyDotTopRightDecorProvider;
    @Mock
    private CornerDecorProvider mPrivacyDotBottomLeftDecorProvider;
    @Mock
    private CornerDecorProvider mPrivacyDotBottomRightDecorProvider;
    @Mock
    private Display.Mode mDisplayMode;
    @Mock
    private DisplayInfo mDisplayInfo;
    private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener;
    @Mock
    private CutoutDecorProviderFactory mCutoutFactory;
    @Mock
    private JavaAdapter mJavaAdapter;

    private FakeFacePropertyRepository mFakeFacePropertyRepository =
            new FakeFacePropertyRepository();
    private List<DecorProvider> mMockCutoutList;
    private final CameraProtectionLoader mCameraProtectionLoader =
            new CameraProtectionLoaderImpl(mContext);
    private Handler mMainHandler;

    private final KosmosJavaAdapter mKosmosJavaAdapter = new KosmosJavaAdapter(this);
    private final KeyguardInteractor mKeyguardInteractor =
            mKosmosJavaAdapter.getKeyguardInteractor();
    private final ShadeInteractor mShadeInteractor = mKosmosJavaAdapter.getShadeInteractor();

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);

        mMainHandler = new Handler(TestableLooper.get(this).getLooper());
        mSecureSettings = new FakeSettings();
        mExecutor = new FakeExecutor(new FakeSystemClock());
        mThreadFactory = new FakeThreadFactory(mExecutor);
        mThreadFactory.setHandler(mMainHandler);

        mWindowManager = mock(WindowManager.class);
        WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
                .getMaximumWindowMetrics();
        when(mWindowManager.getMaximumWindowMetrics()).thenReturn(metrics);
        mContext.addMockSystemService(WindowManager.class, mWindowManager);
        mDisplayManager = mock(DisplayManager.class);
        mContext.addMockSystemService(DisplayManager.class, mDisplayManager);

        spyOn(mContext);
        when(mContext.getDisplay()).thenReturn(mDisplay);
        // Not support hwc layer by default
        doReturn(null).when(mDisplay).getDisplayDecorationSupport();
        doReturn(mDisplayMode).when(mDisplayInfo).getMode();

        when(mMockTypedArray.length()).thenReturn(0);
        mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
                R.id.privacy_dot_top_left_container,
                BOUNDS_POSITION_TOP,
                BOUNDS_POSITION_LEFT,
                R.layout.privacy_dot_top_left));

        mPrivacyDotTopRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
                R.id.privacy_dot_top_right_container,
                BOUNDS_POSITION_TOP,
                BOUNDS_POSITION_RIGHT,
                R.layout.privacy_dot_top_right));

        mPrivacyDotBottomLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
                R.id.privacy_dot_bottom_left_container,
                BOUNDS_POSITION_BOTTOM,
                BOUNDS_POSITION_LEFT,
                R.layout.privacy_dot_bottom_left));

        mPrivacyDotBottomRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
                R.id.privacy_dot_bottom_right_container,
                BOUNDS_POSITION_BOTTOM,
                BOUNDS_POSITION_RIGHT,
                R.layout.privacy_dot_bottom_right));

        // Default no cutout
        mMockCutoutList = new ArrayList<>();
        doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
        doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();

        mFakeFacePropertyRepository.setSensorLocation(new Point(10, 10));

        mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
                BOUNDS_POSITION_TOP,
                mAuthController,
                mStatusBarStateController,
                mKeyguardUpdateMonitor,
                mExecutor,
                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
                mFakeFacePropertyRepository,
                mKeyguardInteractor,
                mShadeInteractor));

        mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
                mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
                mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
                mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader,
                mWindowManager, mShadeInteractor, mKeyguardInteractor, mMainHandler, mExecutor) {
            @Override
            public void start() {
                super.start();
                mExecutor.runAllReady();
            }

            @Override
            public void onConfigChanged(Configuration newConfig) {
                super.onConfigChanged(newConfig);
                mExecutor.runAllReady();
            }

            @Override
            protected void updateOverlayWindowVisibilityIfViewExists(@Nullable View view) {
                super.updateOverlayWindowVisibilityIfViewExists(view);
                mExecutor.runAllReady();
            }

            @Override
            protected CutoutDecorProviderFactory getCutoutFactory() {
                return ScreenDecorationsTest.this.mCutoutFactory;
            }
        });
        mScreenDecorations.mDisplayInfo = mDisplayInfo;
        // Make sure tests are never run starting in debug mode
        mScreenDecorations.setDebug(false);
        doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
        doNothing().when(mScreenDecorations).updateOverlayProviderViews(any());

        try {
            mPrivacyDotShowingListener = mScreenDecorations.mPrivacyDotShowingListener.getClass()
                    .getDeclaredConstructor(ScreenDecorations.class)
                    .newInstance(mScreenDecorations);
        } catch (Exception e) {
            fail(e.getMessage());
        }
    }

    @NonNull
    private int[] getRoundCornerIdsFromOverlayId(@DisplayCutout.BoundsPosition int overlayId) {
        switch (overlayId) {
            case BOUNDS_POSITION_LEFT:
                return new int[] {
                        R.id.rounded_corner_top_left,
                        R.id.rounded_corner_top_left };
            case BOUNDS_POSITION_TOP:
                return new int[] {
                        R.id.rounded_corner_top_left,
                        R.id.rounded_corner_top_right };
            case BOUNDS_POSITION_RIGHT:
                return new int[] {
                        R.id.rounded_corner_top_right,
                        R.id.rounded_corner_bottom_right };
            case BOUNDS_POSITION_BOTTOM:
                return new int[] {
                        R.id.rounded_corner_bottom_left,
                        R.id.rounded_corner_bottom_right };
            default:
                throw new IllegalArgumentException("unknown overlayId: " + overlayId);
        }
    }

    private void verifyRoundedCornerViewsExist(
            @DisplayCutout.BoundsPosition final int overlayId,
            @View.Visibility final boolean isExist) {
        final View overlay = mScreenDecorations.mOverlays[overlayId].getRootView();
        for (int id: getRoundCornerIdsFromOverlayId(overlayId)) {
            final View view = overlay.findViewById(id);
            if (isExist) {
                assertNotNull(view);
                assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
            } else {
                assertNull(view);
            }
        }
    }

    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) {
            if (overlay == null) {
                continue;
            }

            View view = overlay.getRootView().findViewById(id);
            if (view != null) {
                return view;
            }
        }
        return null;
    }

    private void verifyTopDotViewsNullable(final boolean isAssertNull) {
        View tl = findViewFromOverlays(R.id.privacy_dot_top_left_container);
        View tr = findViewFromOverlays(R.id.privacy_dot_top_right_container);
        if (isAssertNull) {
            assertNull(tl);
            assertNull(tr);
        } else {
            assertNotNull(tl);
            assertNotNull(tr);
        }
    }

    private void verifyBottomDotViewsNullable(final boolean isAssertNull) {
        View bl = findViewFromOverlays(R.id.privacy_dot_bottom_left_container);
        View br = findViewFromOverlays(R.id.privacy_dot_bottom_right_container);
        if (isAssertNull) {
            assertNull(bl);
            assertNull(br);
        } else {
            assertNotNull(bl);
            assertNotNull(br);
        }
    }

    private void verifyDotViewsNullable(final boolean isAssertNull) {
        verifyTopDotViewsNullable(isAssertNull);
        verifyBottomDotViewsNullable(isAssertNull);
    }

    private void verifyTopDotViewsVisibility(@View.Visibility final int visibility) {
        verifyTopDotViewsNullable(false);
        View tl = findViewFromOverlays(R.id.privacy_dot_top_left_container);
        View tr = findViewFromOverlays(R.id.privacy_dot_top_right_container);
        assertThat(tl.getVisibility()).isEqualTo(visibility);
        assertThat(tr.getVisibility()).isEqualTo(visibility);
    }

    private void verifyBottomDotViewsVisibility(@View.Visibility final int visibility) {
        verifyBottomDotViewsNullable(false);
        View bl = findViewFromOverlays(R.id.privacy_dot_bottom_left_container);
        View br = findViewFromOverlays(R.id.privacy_dot_bottom_right_container);
        assertThat(bl.getVisibility()).isEqualTo(visibility);
        assertThat(br.getVisibility()).isEqualTo(visibility);
    }

    private void verifyDotViewsVisibility(@View.Visibility final int visibility) {
        verifyTopDotViewsVisibility(visibility);
        verifyBottomDotViewsVisibility(visibility);
    }

    private void verifyOverlaysExistAndAdded(boolean left, boolean top, boolean right,
            boolean bottom, @Nullable Integer visibilityIfExist) {
        if (left || top || right || bottom) {
            assertNotNull(mScreenDecorations.mOverlays);
        } else {
            assertNull(mScreenDecorations.mOverlays);
            return;
        }

        if (left) {
            final OverlayWindow overlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT];
            assertNotNull(overlay);
            verify(mWindowManager, times(1)).addView(
                    eq(overlay.getRootView()), any());
            if (visibilityIfExist != null) {
                assertEquals(visibilityIfExist.intValue(), overlay.getRootView().getVisibility());
            }
        } else {
            assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
        }

        if (top) {
            final OverlayWindow overlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP];
            assertNotNull(overlay);
            verify(mWindowManager, times(1)).addView(
                    eq(overlay.getRootView()), any());
            if (visibilityIfExist != null) {
                assertEquals(visibilityIfExist.intValue(), overlay.getRootView().getVisibility());
            }
        } else {
            assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
        }

        if (right) {
            final OverlayWindow overlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT];
            assertNotNull(overlay);
            verify(mWindowManager, times(1)).addView(
                    eq(overlay.getRootView()), any());
            if (visibilityIfExist != null) {
                assertEquals(visibilityIfExist.intValue(), overlay.getRootView().getVisibility());
            }
        } else {
            assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
        }

        if (bottom) {
            final OverlayWindow overlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM];
            assertNotNull(overlay);
            verify(mWindowManager, times(1)).addView(
                    eq(overlay.getRootView()), any());
            if (visibilityIfExist != null) {
                assertEquals(visibilityIfExist.intValue(), overlay.getRootView().getVisibility());
            }
        } else {
            assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
        }
    }

    @Test
    public void testNoRounding_NoCutout_NoPrivacyDot_NoFaceScanning() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // no cutout (default)

        mScreenDecorations.start();
        // No views added.
        verifyOverlaysExistAndAdded(false, false, false, false, null);
        // No dot controller init
        verify(mDotViewController, never()).initialize(any(), any(), any(), any());
    }

    @Test
    public void testNoRounding_NoCutout_PrivacyDot_NoFaceScanning() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // no cutout (default)

        mScreenDecorations.start();

        // Top and bottom windows are created with INVISIBLE because of privacy dot only
        // Left and right window should be null.
        verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(
                mScreenDecorations.mPrivacyDotShowingListener);

        // Rounded corner views shall not exist
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);

        // Privacy dots shall exist but invisible
        verifyDotViewsVisibility(View.INVISIBLE);

        // Face scanning doesn't exist
        verifyFaceScanningViewExists(false);

        // Dot controller init
        verify(mDotViewController, times(1)).initialize(
                isA(View.class), isA(View.class), isA(View.class), isA(View.class));
    }

    @Test
    public void testRounding_NoCutout_NoPrivacyDot_NoFaceScanning() {
        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // no cutout (default)

        mScreenDecorations.start();

        // Top and bottom windows are created for rounded corners.
        // Left and right window should be null.
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);

        // Rounded corner views shall exist
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);

        // Privacy dots shall not exist
        verifyDotViewsNullable(true);

        // Face scanning doesn't exist
        verifyFaceScanningViewExists(false);

        // No dot controller init
        verify(mDotViewController, never()).initialize(any(), any(), any(), any());
    }

    @Test
    public void testRounding_NoCutout_PrivacyDot_NoFaceScanning() {
        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // no cutout (default)

        mScreenDecorations.start();

        // Top and bottom windows are created for rounded corners.
        // Left and right window should be null.
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(null);

        // Rounded corner views shall exist
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);

        // Privacy dots shall exist but invisible
        verifyDotViewsVisibility(View.INVISIBLE);

        // Face scanning doesn't exist
        verifyFaceScanningViewExists(false);

        // Dot controller init
        verify(mDotViewController, times(1)).initialize(
                isA(View.class), isA(View.class), isA(View.class), isA(View.class));
    }

    @Test
    public void testRoundingRadius_NoCutout() {
        final Size testRadiusPoint = new Size(3, 3);
        setupResources(1 /* radius */, 1 /* radiusTop */, 1 /* radiusBottom */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px)
                /* roundedTopDrawable */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px)
                /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // no cutout (default)

        mScreenDecorations.start();
        // Size of corner view should same as rounded_corner_radius{_top|_bottom}
        final RoundedCornerResDelegate resDelegate = mScreenDecorations.mRoundedCornerResDelegate;
        assertThat(resDelegate.getTopRoundedSize()).isEqualTo(testRadiusPoint);
        assertThat(resDelegate.getBottomRoundedSize()).isEqualTo(testRadiusPoint);
    }

    @Test
    public void testRoundingTopBottomRadius_OnTopBottomOverlay() {
        setupResources(1 /* radius */, 1 /* radiusTop */, 1 /* radiusBottom */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
                /* roundedTopDrawable */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px)
                /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // no cutout (default)

        mScreenDecorations.start();
        View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
                .findViewById(R.id.rounded_corner_top_left);
        View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
                .findViewById(R.id.rounded_corner_top_right);
        ViewGroup.LayoutParams leftParams = leftRoundedCorner.getLayoutParams();
        ViewGroup.LayoutParams rightParams = rightRoundedCorner.getLayoutParams();
        assertEquals(4, leftParams.width);
        assertEquals(4, leftParams.height);
        assertEquals(4, rightParams.width);
        assertEquals(4, rightParams.height);

        leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
                .findViewById(R.id.rounded_corner_bottom_left);
        rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
                .findViewById(R.id.rounded_corner_bottom_right);
        leftParams = leftRoundedCorner.getLayoutParams();
        rightParams = rightRoundedCorner.getLayoutParams();
        assertEquals(3, leftParams.width);
        assertEquals(3, leftParams.height);
        assertEquals(3, rightParams.width);
        assertEquals(3, rightParams.height);
    }

    @Test
    public void testRoundingTopBottomRadius_OnLeftRightOverlay() {
        setupResources(1 /* radius */, 1 /* radiusTop */, 1 /* radiusBottom */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px)
                /* roundedTopDrawable */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
                /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // left cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));

        mScreenDecorations.start();
        View topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
                .findViewById(R.id.rounded_corner_top_left);
        View bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
                .findViewById(R.id.rounded_corner_bottom_left);
        ViewGroup.LayoutParams topParams = topRoundedCorner.getLayoutParams();
        ViewGroup.LayoutParams bottomParams = bottomRoundedCorner.getLayoutParams();
        assertEquals(3, topParams.width);
        assertEquals(3, topParams.height);
        assertEquals(5, bottomParams.width);
        assertEquals(5, bottomParams.height);

        topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
                .findViewById(R.id.rounded_corner_top_right);
        bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
                .findViewById(R.id.rounded_corner_bottom_right);
        topParams = topRoundedCorner.getLayoutParams();
        bottomParams = bottomRoundedCorner.getLayoutParams();
        assertEquals(3, topParams.width);
        assertEquals(3, topParams.height);
        assertEquals(5, bottomParams.width);
        assertEquals(5, bottomParams.height);
    }

    @Test
    public void testNoRounding_CutoutShortEdge_NoPrivacyDot() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        // Top window is created for top cutout.
        // Bottom, left, or right window should be null.
        verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);

        // Privacy dots shall not exist because of no privacy
        verifyDotViewsNullable(true);

        // No dot controller init
        verify(mDotViewController, never()).initialize(any(), any(), any(), any());
    }

    @Test
    public void testNoRounding_CutoutShortEdge_PrivacyDot() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        // Top window is created for top cutout.
        // Bottom window is created for privacy dot.
        // Left or right window should be null.
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(null);

        // Top rounded corner views shall exist because of cutout
        // but be gone because of no rounded corner
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
        // Bottom rounded corner views shall exist because of privacy dot
        // but be gone because of no rounded corner
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);

        // Privacy dots shall exist but invisible
        verifyDotViewsVisibility(View.INVISIBLE);

        // Dot controller init
        verify(mDotViewController, times(1)).initialize(
                isA(View.class), isA(View.class), isA(View.class), isA(View.class));
    }

    @Test
    public void testNoRounding_CutoutLongEdge_NoPrivacyDot() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // left cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));

        mScreenDecorations.start();
        // Left window is created for left cutout.
        // Bottom, top, or right window should be null.
        verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE);

        // Left rounded corner views shall exist because of cutout
        // but be gone because of no rounded corner
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_LEFT, false);

        // Top privacy dots shall not exist because of no privacy
        verifyDotViewsNullable(true);

        // No dot controller init
        verify(mDotViewController, never()).initialize(any(), any(), any(), any());
    }

    @Test
    public void testNoRounding_CutoutLongEdge_PrivacyDot() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // left cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));

        mScreenDecorations.start();
        // Left window is created for left cutout.
        // Right window is created for privacy.
        // Bottom, or top window should be null.
        verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(null);

        // Privacy dots shall exist but invisible
        verifyDotViewsVisibility(View.INVISIBLE);

        // Dot controller init
        verify(mDotViewController, times(1)).initialize(
                isA(View.class), isA(View.class), isA(View.class), isA(View.class));
    }

    @Test
    public void testRounding_CutoutShortEdge_NoPrivacyDot() {
        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        // Top window is created for rounded corner and top cutout.
        // Bottom window is created for rounded corner.
        // Left, or right window should be null.
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);

        // Rounded corner views shall exist
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);

        // Top privacy dots shall not exist because of no privacy dot
        verifyDotViewsNullable(true);

        // No dot controller init
        verify(mDotViewController, never()).initialize(any(), any(), any(), any());
    }

    @Test
    public void testRounding_CutoutShortEdge_PrivacyDot() {
        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        // Top window is created for rounded corner and top cutout.
        // Bottom window is created for rounded corner.
        // Left, or right window should be null.
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(null);

        // Rounded corner views shall exist
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);

        // Top privacy dots shall exist but invisible
        verifyDotViewsVisibility(View.INVISIBLE);

        // Dot controller init
        verify(mDotViewController, times(1)).initialize(
                isA(View.class), isA(View.class), isA(View.class), isA(View.class));
    }

    @Test
    public void testRounding_CutoutLongEdge_NoPrivacyDot() {
        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // left cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));

        mScreenDecorations.start();
        // Left window is created for rounded corner and left cutout.
        // Right window is created for rounded corner.
        // Top, or bottom window should be null.
        verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
    }

    @Test
    public void testRounding_CutoutLongEdge_PrivacyDot() {
        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // left cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));

        mScreenDecorations.start();
        // Left window is created for rounded corner, left cutout, and privacy.
        // Right window is created for rounded corner and privacy dot.
        // Top, or bottom window should be null.
        verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(null);
    }

    @Test
    public void testRounding_CutoutShortAndLongEdge_NoPrivacyDot() {
        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // top and left cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        // Top window is created for rounded corner and top cutout.
        // Bottom window is created for rounded corner.
        // Left window is created for left cutout.
        // Right window should be null.
        verifyOverlaysExistAndAdded(true, true, false, true, View.VISIBLE);
    }

    @Test
    public void testRounding_CutoutShortAndLongEdge_PrivacyDot() {
        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // top and left cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        // Top window is created for rounded corner and top cutout.
        // Bottom window is created for rounded corner.
        // Left window is created for left cutout.
        // Right window should be null.
        verifyOverlaysExistAndAdded(true, true, false, true, View.VISIBLE);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(null);
    }

    @Test
    public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_NoPrivacyDot() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // Set to short edge cutout(top).
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);

        // Switch to long edge cutout(left).
        mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));

        mScreenDecorations.onConfigChanged(new Configuration());
        verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE);
    }

    @Test
    public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_PrivacyDot() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // Set to short edge cutout(top).
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(null);

        // Switch to long edge cutout(left).
        mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));

        mScreenDecorations.onConfigChanged(new Configuration());
        verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
        verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(2)).setShowingListener(null);

        // Verify each privacy dot id appears only once
        mPrivacyDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> {
            int findCount = 0;
            for (OverlayWindow overlay: mScreenDecorations.mOverlays) {
                if (overlay == null) {
                    continue;
                }
                final View view = overlay.getRootView().findViewById(viewId);
                if (view != null) {
                    findCount++;
                }
            }
            assertEquals(1, findCount);
        });

    }

    @Test
    public void testDelayedCutout_NoPrivacyDot() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // No cutout (default)

        mScreenDecorations.start();
        verifyOverlaysExistAndAdded(false, false, false, false, null);

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.onConfigChanged(new Configuration());

        // Only top windows should be added.
        verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);
    }

    @Test
    public void testDelayedCutout_PrivacyDot() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        // no cutout (default)

        mScreenDecorations.start();
        // Both top and bottom windows should be added with INVISIBLE because of only privacy dot,
        // but rounded corners visibility shall be gone because of no rounding.
        verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(
                mScreenDecorations.mPrivacyDotShowingListener);

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.onConfigChanged(new Configuration());

        // Both top and bottom windows should be added with VISIBLE because of privacy dot and
        // cutout, but rounded corners visibility shall be gone because of no rounding.
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
        verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(null);
    }

    @Test
    public void hasRoundedCornerOverlayFlagSet() {
        assertThat(mScreenDecorations.getWindowLayoutParams(1).privateFlags
                        & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
                is(PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY));
    }

    @Test
    public void testUpdateRoundedCorners() {
        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px)
                /* roundedTopDrawable */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
                /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/);
        mDisplayInfo.rotation = Surface.ROTATION_0;

        mScreenDecorations.start();

        final RoundedCornerResDelegate resDelegate = mScreenDecorations.mRoundedCornerResDelegate;
        assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize());
        assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize());

        doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
        mDisplayInfo.rotation = Surface.ROTATION_270;

        mScreenDecorations.onConfigChanged(null);

        assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
        assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
    }

    @Test
    public void testOnlyRoundedCornerRadiusTop() {
        setupResources(0 /* radius */, 10 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        mScreenDecorations.start();

        final RoundedCornerResDelegate resDelegate = mScreenDecorations.mRoundedCornerResDelegate;
        assertEquals(true, resDelegate.getHasTop());
        assertEquals(false, resDelegate.getHasBottom());
        assertEquals(getDrawableIntrinsicSize(R.drawable.rounded_corner_top),
                resDelegate.getTopRoundedSize());

        final DecorProviderFactory mRoundedCornerFactory = mScreenDecorations.mRoundedCornerFactory;
        assertEquals(true, mRoundedCornerFactory.getHasProviders());
        final List<DecorProvider> providers = mRoundedCornerFactory.getProviders();
        assertEquals(2, providers.size());
        assertEquals(true, providers.get(0).getAlignedBounds().contains(BOUNDS_POSITION_TOP));
        assertEquals(true, providers.get(1).getAlignedBounds().contains(BOUNDS_POSITION_TOP));    }

    @Test
    public void testOnlyRoundedCornerRadiusBottom() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 20 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        mScreenDecorations.start();

        final RoundedCornerResDelegate resDelegate = mScreenDecorations.mRoundedCornerResDelegate;
        assertEquals(false, resDelegate.getHasTop());
        assertEquals(true, resDelegate.getHasBottom());
        assertEquals(getDrawableIntrinsicSize(R.drawable.rounded_corner_bottom),
                resDelegate.getBottomRoundedSize());

        final DecorProviderFactory mRoundedCornerFactory = mScreenDecorations.mRoundedCornerFactory;
        assertEquals(true, mRoundedCornerFactory.getHasProviders());
        final List<DecorProvider> providers = mRoundedCornerFactory.getProviders();
        assertEquals(2, providers.size());
        assertEquals(true, providers.get(0).getAlignedBounds().contains(BOUNDS_POSITION_BOTTOM));
        assertEquals(true, providers.get(1).getAlignedBounds().contains(BOUNDS_POSITION_BOTTOM));
    }

    @Test
    public void testDebugRoundedCorners_noDeviceCornersSet() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        mScreenDecorations.start();
        // No rounded corners exist at this point
        verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);

        // Path from rounded.xml, scaled by 10x to produce 80x80 corners
        Path debugPath = PathParser.createPathFromPathData("M8,0H0v8C0,3.6,3.6,0,8,0z");
        // WHEN debug corners are added to the delegate
        DebugRoundedCornerModel debugCorner = new DebugRoundedCornerModel(
                debugPath,
                80,
                80,
                10f,
                10f
        );
        mScreenDecorations.mDebugRoundedCornerDelegate
                .applyNewDebugCorners(debugCorner, debugCorner);

        // AND debug mode is entered
        mScreenDecorations.setDebug(true);
        mExecutor.runAllReady();

        // THEN the debug corners provide decor
        List<DecorProvider> providers = mScreenDecorations.getProviders(false);
        assertEquals(4, providers.size());

        // Top and bottom overlays contain the debug rounded corners
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
    }

    @Test
    public void testDebugRoundedCornersRemoved_noDeviceCornersSet() {
        // GIVEN a device with no rounded corners defined
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        mScreenDecorations.start();
        // No rounded corners exist at this point
        verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);

        // Path from rounded.xml, scaled by 10x to produce 80x80 corners
        Path debugPath = PathParser.createPathFromPathData("M8,0H0v8C0,3.6,3.6,0,8,0z");
        // WHEN debug corners are added to the delegate
        DebugRoundedCornerModel debugCorner = new DebugRoundedCornerModel(
                debugPath,
                80,
                80,
                10f,
                10f
        );
        mScreenDecorations.mDebugRoundedCornerDelegate
                .applyNewDebugCorners(debugCorner, debugCorner);

        // AND debug mode is entered
        mScreenDecorations.setDebug(true);
        mExecutor.runAllReady();

        // Top and bottom overlays contain the debug rounded corners
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);

        // WHEN debug is exited
        mScreenDecorations.setDebug(false);
        mExecutor.runAllReady();

        // THEN the decor is removed
        verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
        assertThat(mScreenDecorations.mDebugRoundedCornerDelegate.getHasBottom()).isFalse();
        assertThat(mScreenDecorations.mDebugRoundedCornerDelegate.getHasTop()).isFalse();
    }

    @Test
    public void testRegistration_From_NoOverlay_To_HasOverlays() {
        doReturn(false).when(mScreenDecorations).hasOverlays();
        mScreenDecorations.start();
        assertThat(mScreenDecorations.mIsRegistered, is(false));

        doReturn(true).when(mScreenDecorations).hasOverlays();
        mScreenDecorations.onConfigChanged(new Configuration());
        assertThat(mScreenDecorations.mIsRegistered, is(true));
    }

    @Test
    public void testRegistration_From_HasOverlays_To_HasOverlays() {
        doReturn(true).when(mScreenDecorations).hasOverlays();

        mScreenDecorations.start();
        assertThat(mScreenDecorations.mIsRegistered, is(true));

        mScreenDecorations.onConfigChanged(new Configuration());
        assertThat(mScreenDecorations.mIsRegistered, is(true));
    }

    @Test
    public void testRegistration_From_HasOverlays_To_NoOverlay() {
        doReturn(true).when(mScreenDecorations).hasOverlays();

        mScreenDecorations.start();
        assertThat(mScreenDecorations.mIsRegistered, is(true));

        doReturn(false).when(mScreenDecorations).hasOverlays();
        mScreenDecorations.onConfigChanged(new Configuration());
        assertThat(mScreenDecorations.mIsRegistered, is(false));
    }

    @Test
    public void testUpdateOverlayProviderViews_PendingConfigChange() {
        final DecorProvider cutout = new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP);
        spyOn(cutout);
        doNothing().when(cutout).onReloadResAndMeasure(any(), anyInt(), anyInt(), anyInt(), any());
        mMockCutoutList.add(cutout);
        mScreenDecorations.start();
        doCallRealMethod().when(mScreenDecorations).updateOverlayProviderViews(any());

        mScreenDecorations.mPendingConfigChange = true;
        mScreenDecorations.updateOverlayProviderViews(null /* filterIds */);
        verify(cutout, never()).onReloadResAndMeasure(any(), anyInt(), anyInt(), anyInt(), any());

        mScreenDecorations.mPendingConfigChange = false;
        mScreenDecorations.updateOverlayProviderViews(null /* filterIds */);
        verify(cutout).onReloadResAndMeasure(any(), anyInt(), anyInt(), anyInt(), any());
    }

    @Test
    public void testSupportHwcLayer_SwitchFrom_NotSupport() {
        setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        // should only inflate mOverlays when the hwc doesn't support screen decoration
        assertNull(mScreenDecorations.mScreenDecorHwcWindow);
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);

        final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
        decorationSupport.format = PixelFormat.R_8;
        doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
        // Trigger the support hwc screen decoration change by changing the display unique id
        mScreenDecorations.mDisplayUniqueId = "test";
        mScreenDecorations.mDisplayListener.onDisplayChanged(1);

        // should only inflate hwc layer when the hwc supports screen decoration
        assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
        verifyOverlaysExistAndAdded(false, false, false, false, null);
    }

    @Test
    public void testNotSupportHwcLayer_SwitchFrom_Support() {
        setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
        final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
        decorationSupport.format = PixelFormat.R_8;
        doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        // should only inflate hwc layer when the hwc supports screen decoration
        assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
        verifyOverlaysExistAndAdded(false, false, false, false, null);

        doReturn(null).when(mDisplay).getDisplayDecorationSupport();
        // Trigger the support hwc screen decoration change by changing the display unique id
        mScreenDecorations.mDisplayUniqueId = "test";
        mScreenDecorations.mDisplayListener.onDisplayChanged(1);

        // should only inflate mOverlays when the hwc doesn't support screen decoration
        assertNull(mScreenDecorations.mScreenDecorHwcWindow);
        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
    }

    @Test
    public void faceSensorLocationChangesReloadsFaceScanningOverlay() {
        mFaceScanningProviders = new ArrayList<>();
        mFaceScanningProviders.add(mFaceScanningDecorProvider);
        when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
        when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
        ScreenDecorations screenDecorations = new ScreenDecorations(mContext,
                mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker,
                mDotViewController,
                mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
                mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader,
                mWindowManager, mShadeInteractor, mKeyguardInteractor, mMainHandler, mExecutor);
        screenDecorations.start();
        when(mContext.getDisplay()).thenReturn(mDisplay);
        when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
            @Override
            public Boolean answer(InvocationOnMock invocation) throws Throwable {
                DisplayInfo displayInfo = invocation.getArgument(0);
                int modeId = 1;
                displayInfo.modeId = modeId;
                displayInfo.supportedModes = new Display.Mode[]{new Display.Mode(modeId, 1024, 1024,
                        90)};
                return false;
            }
        });
        mExecutor.runAllReady();
        clearInvocations(mFaceScanningDecorProvider);
        final Point location = new Point();
        mFakeFacePropertyRepository.setSensorLocation(location);
        screenDecorations.onFaceSensorLocationChanged(location);
        mExecutor.runAllReady();

        verify(mFaceScanningDecorProvider).onReloadResAndMeasure(any(),
                anyInt(),
                anyInt(),
                anyInt(),
                any());
    }

    @Test
    public void delayedFaceSensorLocationChangesAddsFaceScanningOverlay() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);
        mScreenDecorations.start();
        verifyFaceScanningViewExists(false); // face scanning view not added yet

        // WHEN the sensor location is updated
        mFaceScanningProviders = new ArrayList<>();
        mFaceScanningProviders.add(mFaceScanningDecorProvider);
        when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
        when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
        final Point location = new Point();
        mFakeFacePropertyRepository.setSensorLocation(location);
        mScreenDecorations.onFaceSensorLocationChanged(location);
        mExecutor.runAllReady();

        // THEN the face scanning view is added
        verifyFaceScanningViewExists(true);
    }

    @Test
    public void testPrivacyDotShowingListenerWorkWellWithNullParameter() {
        mPrivacyDotShowingListener.onPrivacyDotShown(null);
        mPrivacyDotShowingListener.onPrivacyDotHidden(null);
    }

    @Test
    public void testAutoShowHideOverlayWindowWhenSupportHwcLayer() {
        setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px)
                /* roundedTopDrawable */,
                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
                /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, true /* faceScanning */);
        final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
        decorationSupport.format = PixelFormat.R_8;
        doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();
        // Inflate top and bottom overlay with INVISIBLE because of only privacy dots on sw layer
        verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);

        // 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 */, true /* privacyDot */, true /* faceScanning */);

        // no cutout (default)

        mScreenDecorations.start();
        // Inflate top and bottom overlay with INVISIBLE because of only privacy dots on sw layer
        verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);

        // 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_noFaceScanning() {
        setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
        final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
        decorationSupport.format = PixelFormat.R_8;
        doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();

        // Should only inflate hwc layer.
        assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
        verifyOverlaysExistAndAdded(false, false, false, false, null);
    }

    @Test
    public void testHwcLayer_PrivacyDot_FaceScanning() {
        setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, true /* faceScanning */);
        final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
        decorationSupport.format = PixelFormat.R_8;
        doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();

        // top cutout
        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));

        mScreenDecorations.start();

        assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
        // mOverlays are inflated but the visibility should be INVISIBLE.
        verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
        verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
        verify(mDotViewController, times(1)).setShowingListener(
                mScreenDecorations.mPrivacyDotShowingListener);

        verifyFaceScanningViewExists(true);
    }

    @Test
    public void testHasSameProvidersWithNullOverlays() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);

        mScreenDecorations.start();

        final ArrayList<DecorProvider> newProviders = new ArrayList<>();
        assertTrue(mScreenDecorations.hasSameProviders(newProviders));

        newProviders.add(mPrivacyDotTopLeftDecorProvider);
        assertFalse(mScreenDecorations.hasSameProviders(newProviders));

        newProviders.add(mPrivacyDotTopRightDecorProvider);
        assertFalse(mScreenDecorations.hasSameProviders(newProviders));
    }

    @Test
    public void testHasSameProvidersWithPrivacyDots() {
        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */);

        mScreenDecorations.start();

        final ArrayList<DecorProvider> newProviders = new ArrayList<>();
        assertFalse(mScreenDecorations.hasSameProviders(newProviders));

        newProviders.add(mPrivacyDotTopLeftDecorProvider);
        assertFalse(mScreenDecorations.hasSameProviders(newProviders));

        newProviders.add(mPrivacyDotTopRightDecorProvider);
        assertFalse(mScreenDecorations.hasSameProviders(newProviders));

        newProviders.add(mPrivacyDotBottomLeftDecorProvider);
        assertFalse(mScreenDecorations.hasSameProviders(newProviders));

        newProviders.add(mPrivacyDotBottomRightDecorProvider);
        assertTrue(mScreenDecorations.hasSameProviders(newProviders));
    }

    private Size getDrawableIntrinsicSize(@DrawableRes int drawableResId) {
        final Drawable d = mContext.getDrawable(drawableResId);
        return new Size(d.getIntrinsicWidth(), d.getIntrinsicHeight());
    }

    @Nullable
    private Drawable getTestsDrawable(@DrawableRes int drawableId) {
        try {
            return mContext.createPackageContext("com.android.systemui.tests", 0)
                    .getDrawable(drawableId);
        } catch (PackageManager.NameNotFoundException exception) {
            return null;
        }
    }

    private void setupResources(int radius, int radiusTop, int radiusBottom,
            @Nullable Drawable roundedTopDrawable, @Nullable Drawable roundedBottomDrawable,
            int roundedPadding, boolean privacyDot, boolean faceScanning) {
        mContext.getOrCreateTestableResources().addOverride(
                com.android.internal.R.array.config_displayUniqueIdArray,
                new String[]{});
        mContext.getOrCreateTestableResources().addOverride(
                com.android.internal.R.array.config_roundedCornerRadiusArray,
                mMockTypedArray);
        mContext.getOrCreateTestableResources().addOverride(
                com.android.internal.R.array.config_roundedCornerTopRadiusArray,
                mMockTypedArray);
        mContext.getOrCreateTestableResources().addOverride(
                com.android.internal.R.array.config_roundedCornerBottomRadiusArray,
                mMockTypedArray);
        mContext.getOrCreateTestableResources().addOverride(
                R.array.config_roundedCornerDrawableArray,
                mMockTypedArray);
        mContext.getOrCreateTestableResources().addOverride(
                R.array.config_roundedCornerTopDrawableArray,
                mMockTypedArray);
        mContext.getOrCreateTestableResources().addOverride(
                R.array.config_roundedCornerBottomDrawableArray,
                mMockTypedArray);
        mContext.getOrCreateTestableResources().addOverride(
                com.android.internal.R.dimen.rounded_corner_radius, radius);
        mContext.getOrCreateTestableResources().addOverride(
                com.android.internal.R.dimen.rounded_corner_radius_top, radiusTop);
        mContext.getOrCreateTestableResources().addOverride(
                com.android.internal.R.dimen.rounded_corner_radius_bottom, radiusBottom);
        if (roundedTopDrawable != null) {
            mContext.getOrCreateTestableResources().addOverride(
                    R.drawable.rounded_corner_top,
                    roundedTopDrawable);
        }
        if (roundedBottomDrawable != null) {
            mContext.getOrCreateTestableResources().addOverride(
                    R.drawable.rounded_corner_bottom,
                    roundedBottomDrawable);
        }
        mContext.getOrCreateTestableResources().addOverride(
                R.dimen.rounded_corner_content_padding, roundedPadding);

        mPrivacyDecorProviders = new ArrayList<>();
        if (privacyDot) {
            mPrivacyDecorProviders.add(mPrivacyDotTopLeftDecorProvider);
            mPrivacyDecorProviders.add(mPrivacyDotTopRightDecorProvider);
            mPrivacyDecorProviders.add(mPrivacyDotBottomLeftDecorProvider);
            mPrivacyDecorProviders.add(mPrivacyDotBottomRightDecorProvider);
        }
        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);
    }
}
