/*
 * Copyright (C) 2020 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 android.server.wm;

import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;

import static androidx.test.InstrumentationRegistry.getInstrumentation;

import static org.junit.Assert.assertEquals;

import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.WindowMetrics;

import androidx.annotation.Nullable;
import androidx.test.filters.FlakyTest;

import com.android.compatibility.common.util.PollingCheck;

import org.junit.Test;

/**
 * Test whether WindowManager performs the correct layout while the app applies the fit-window-
 * insets APIs.
 *
 * Build/Install/Run:
 *     atest CtsWindowManagerDeviceTestCases:WindowInsetsLayoutTests
 */
@FlakyTest(detail = "Promote once confirmed non-flaky")
@Presubmit
public class WindowInsetsLayoutTests extends WindowManagerTestBase {

    private final static long TIMEOUT = 1000; // milliseconds

    @Test
    public void testSetFitInsetsTypes() {
        // Start the Activity in fullscreen windowing mode for its bounds to match display bounds.
        final TestActivity activity =
                startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN);

        // Make sure the main window has been laid out.
        final View mainWindowRoot = activity.getWindow().getDecorView();
        PollingCheck.waitFor(TIMEOUT, () -> mainWindowRoot.getWidth() > 0);

        getInstrumentation().runOnMainSync(() -> {
            activity.assertMatchesWindowBounds();
        });

        testSetFitInsetsTypesInner(Type.statusBars(), activity, mainWindowRoot);
        testSetFitInsetsTypesInner(Type.navigationBars(), activity, mainWindowRoot);
        testSetFitInsetsTypesInner(Type.systemBars() & ~Type.captionBar(), activity,
                mainWindowRoot);
    }

    private void testSetFitInsetsTypesInner(
            int types, TestActivity activity, View mainWindowRoot) {
        getInstrumentation().runOnMainSync(() -> {
            activity.addChildWindow(types, Side.all(), false);
        });

        // Make sure the child window has been laid out.
        final View childWindowRoot = activity.getChildWindowRoot();
        PollingCheck.waitFor(TIMEOUT, () -> childWindowRoot.getWidth() > 0);

        getInstrumentation().runOnMainSync(() -> {
            final WindowInsets windowInsets = mainWindowRoot.getRootWindowInsets();
            final Insets insets = windowInsets.getInsets(types);
            final int[] locationOnScreen = new int[2];
            childWindowRoot.getLocationOnScreen(locationOnScreen);
            assertEquals(insets.left, locationOnScreen[0]);
            assertEquals(insets.top, locationOnScreen[1]);
            assertEquals(insets.right,
                    mainWindowRoot.getWidth() - locationOnScreen[0] - childWindowRoot.getWidth());
            assertEquals(insets.bottom,
                    mainWindowRoot.getHeight()- locationOnScreen[1] - childWindowRoot.getHeight());
            activity.removeChildWindow();
        });
    }

    @Test
    public void testSetFitInsetsSides() {
        // Start the Activity in fullscreen windowing mode for its bounds to match display bounds.
        final TestActivity activity =
                startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN);

        // Make sure the main window has been laid out.
        final View mainWindowRoot = activity.getWindow().getDecorView();
        PollingCheck.waitFor(TIMEOUT, () -> mainWindowRoot.getWidth() > 0);

        getInstrumentation().runOnMainSync(() -> {
            activity.assertMatchesWindowBounds();
        });

        testSetFitInsetsSidesInner(Side.LEFT, activity, mainWindowRoot);
        testSetFitInsetsSidesInner(Side.TOP, activity, mainWindowRoot);
        testSetFitInsetsSidesInner(Side.RIGHT, activity, mainWindowRoot);
        testSetFitInsetsSidesInner(Side.BOTTOM, activity, mainWindowRoot);
    }

    private void testSetFitInsetsSidesInner(
            int sides, TestActivity activity, View mainWindowRoot) {
        final int types = Type.systemBars() & ~Type.captionBar();
        getInstrumentation().runOnMainSync(() -> {
            activity.addChildWindow(types, sides, false);
        });

        // Make sure the child window has been laid out.
        final View childWindowRoot = activity.getChildWindowRoot();
        PollingCheck.waitFor(TIMEOUT, () -> childWindowRoot.getWidth() > 0);

        getInstrumentation().runOnMainSync(() -> {
            final WindowInsets windowInsets = mainWindowRoot.getRootWindowInsets();
            final Insets insets = windowInsets.getInsets(types);
            final int[] locationOnScreen = new int[2];
            childWindowRoot.getLocationOnScreen(locationOnScreen);
            assertEquals((sides & Side.LEFT) != 0 ? insets.left : 0, locationOnScreen[0]);
            assertEquals((sides & Side.TOP) != 0 ? insets.top : 0, locationOnScreen[1]);
            assertEquals((sides & Side.RIGHT) != 0 ? insets.right : 0,
                    mainWindowRoot.getWidth() - locationOnScreen[0] - childWindowRoot.getWidth());
            assertEquals((sides & Side.BOTTOM) != 0 ? insets.bottom : 0,
                    mainWindowRoot.getHeight()- locationOnScreen[1] - childWindowRoot.getHeight());
            activity.removeChildWindow();
        });
    }

    @Test
    public void testSetFitInsetsIgnoringVisibility() {
        // Start the Activity in fullscreen windowing mode for its bounds to match display bounds.
        final TestActivity activity =
                startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN);

        // Make sure the main window has been laid out.
        final View mainWindowRoot = activity.getWindow().getDecorView();
        PollingCheck.waitFor(TIMEOUT, () -> mainWindowRoot.getWidth() > 0);

        final int types = Type.systemBars();
        final int sides = Side.all();
        final int[] locationAndSize1 = new int[4];
        final int[] locationAndSize2 = new int[4];

        getInstrumentation().runOnMainSync(() -> {
            activity.assertMatchesWindowBounds();
            activity.addChildWindow(types, sides, false);
        });

        // Make sure the 1st child window has been laid out.
        final View childWindowRoot1 = activity.getChildWindowRoot();
        PollingCheck.waitFor(TIMEOUT, () -> childWindowRoot1.getWidth() > 0);

        getInstrumentation().runOnMainSync(() -> {
            childWindowRoot1.getLocationOnScreen(locationAndSize1);
            locationAndSize1[2] = childWindowRoot1.getWidth();
            locationAndSize1[3] = childWindowRoot1.getHeight();
            activity.removeChildWindow();

            mainWindowRoot.getWindowInsetsController().hide(types);

            activity.addChildWindow(types, sides, true);
        });

        // Make sure the 2nd child window has been laid out.
        final View childWindowRoot2 = activity.getChildWindowRoot();
        PollingCheck.waitFor(TIMEOUT, () -> childWindowRoot2.getWidth() > 0);

        getInstrumentation().runOnMainSync(() -> {
            childWindowRoot2.getLocationOnScreen(locationAndSize2);
            locationAndSize2[2] = childWindowRoot2.getWidth();
            locationAndSize2[3] = childWindowRoot2.getHeight();
            activity.removeChildWindow();
        });

        for (int i = 0; i < 4; i++) {
            assertEquals(locationAndSize1[i], locationAndSize2[i]);
        }
    }

    public static class TestActivity extends FocusableActivity {

        private View mChildWindowRoot;

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            WindowManager.LayoutParams lp = getWindow().getAttributes();
            lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
            getWindow().setAttributes(lp);
        }

        void addChildWindow(int types, int sides, boolean ignoreVis) {
            final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
            attrs.type = TYPE_APPLICATION_PANEL;
            attrs.width = MATCH_PARENT;
            attrs.height = MATCH_PARENT;
            attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
            attrs.flags = FLAG_NOT_FOCUSABLE;
            attrs.setFitInsetsTypes(types);
            attrs.setFitInsetsSides(sides);
            attrs.setFitInsetsIgnoringVisibility(ignoreVis);
            mChildWindowRoot = new View(this);
            getWindowManager().addView(mChildWindowRoot, attrs);
        }

        void removeChildWindow() {
            getWindowManager().removeViewImmediate(mChildWindowRoot);
        }

        View getChildWindowRoot() {
            return mChildWindowRoot;
        }

        void assertMatchesWindowBounds() {
            final View rootView = getWindow().getDecorView();
            final Rect windowMetricsBounds =
                    getWindowManager().getCurrentWindowMetrics().getBounds();
            assertEquals(windowMetricsBounds.width(), rootView.getWidth());
            assertEquals(windowMetricsBounds.height(), rootView.getHeight());
            final int[] locationOnScreen = new int[2];
            rootView.getLocationOnScreen(locationOnScreen);
            assertEquals(locationOnScreen[0] /* expected x */, windowMetricsBounds.left);
            assertEquals(locationOnScreen[1] /* expected y */, windowMetricsBounds.top);
        }
    }
}
