/*
 * Copyright (C) 2023 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.dreams;

import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.server.wm.ComponentNameUtils.getWindowName;
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
import static android.server.wm.WindowManagerState.STATE_STOPPED;
import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.server.wm.app.Components.TEST_DREAM_SERVICE;
import static android.server.wm.app.Components.TEST_STUBBORN_DREAM_SERVICE;
import static android.view.Display.DEFAULT_DISPLAY;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;

import android.content.ComponentName;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.server.wm.ActivityManagerTestBase;
import android.server.wm.Condition;
import android.server.wm.DreamCoordinator;
import android.server.wm.LockScreenSession;
import android.server.wm.RotationSession;
import android.server.wm.app.Components;
import android.server.wm.settings.SettingsSession;
import android.view.Surface;

import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
import com.android.bedstead.harrier.annotations.RequireNotAutomotive;
import com.android.systemui.Flags;

import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

/** Build/Install/Run: atest CtsWindowManagerDeviceDreams:DreamManagerServiceTests */
@Presubmit
@RunWith(BedsteadJUnit4.class)
public class DreamManagerServiceTests extends ActivityManagerTestBase {
    @Rule
    public final CheckFlagsRule mCheckFlagsRule =
            DeviceFlagsValueProvider.createCheckFlagsRule();

    // Maximum timeout in seconds after which the dream should have finished willingly
    private static final int ACTIVITY_STOP_MAX_TIMEOUT = 3;

    // Maximum timeout in seconds after which the dream should have been forcefully stopped
    private static final int DREAM_FORCE_STOP_MAX_TIMEOUT = 15;

    private ComponentName mDreamActivityName;

    private SettingsSession<Integer> mCommunalHubSetting;

    private DreamCoordinator mDreamCoordinator = new DreamCoordinator(mContext);

    @ClassRule
    @Rule
    public static final DeviceState sDeviceState = new DeviceState();

    @Before
    public void setup() {
        mCommunalHubSetting =
                new SettingsSession<>(
                        Settings.Secure.getUriFor(Settings.Secure.GLANCEABLE_HUB_ENABLED),
                        Settings.Secure::getInt,
                        Settings.Secure::putInt);
        mCommunalHubSetting.set(0);

        mDreamCoordinator.setup();
        pressWakeupButton();
        mWmState.waitForDreamGone();
    }

    @After
    public void tearDown() {
        mDreamCoordinator.restoreDefaults();
        mDreamCoordinator.stopDream();
        mWmState.waitForDreamGone();
        if (mCommunalHubSetting != null) mCommunalHubSetting.close();
        Components.forceStopPackage();
    }

    private void waitAndAssertDreamActivityGone(int maxTimeOutInSeconds) {
        String msg = "Dream Activity must be gone";
        Condition.waitFor(
                new Condition<>(msg, this::isDreamActivityGone)
                        .setRetryLimit(maxTimeOutInSeconds)
                        .setOnFailure(o -> fail(msg)));
    }

    private boolean isDreamActivityGone() {
        mWmState.computeState();
        return !mWmState.containsWindow(getWindowName(mDreamActivityName))
                && !mWmState.containsActivity(mDreamActivityName);
    }

    private void startFullscreenTestActivity() {
        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        waitAndAssertResumedAndFocusedActivityOnDisplay(
                TEST_ACTIVITY, DEFAULT_DISPLAY, "Test activity should be the top resumed activity");
        mWmState.assertVisibility(TEST_ACTIVITY, true);
    }

    private void startFullscreenTestActivityWithShowWhenLocked() {
        launchActivity(SHOW_WHEN_LOCKED_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        waitAndAssertResumedAndFocusedActivityOnDisplay(
                SHOW_WHEN_LOCKED_ACTIVITY,
                DEFAULT_DISPLAY,
                "Test activity with android:showWhenLocked should be the top resumed activity");
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
    }

    @Test
    @Ignore("b/428777826")
    public void testStartAndStopDream() throws Exception {
        startFullscreenTestActivity();
        mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_DREAM_SERVICE);

        mDreamCoordinator.startDream();
        waitAndAssertResumedAndFocusedActivityOnDisplay(
                mDreamActivityName,
                DEFAULT_DISPLAY,
                "Dream activity should be the top resumed activity");
        mWmState.waitForValidState(mWmState.getHomeActivityName());
        mWmState.assertVisibility(mWmState.getHomeActivityName(), false);
        mWmState.waitForValidState(TEST_ACTIVITY);
        mWmState.assertVisibility(TEST_ACTIVITY, false);

        assertTrue(mDreamCoordinator.isDreaming());

        mDreamCoordinator.stopDream();
        mWmState.waitAndAssertActivityRemoved(mDreamActivityName);

        // To reduce flakiness, dismiss keyguard in case the device locked while test was running
        pressUnlockButton();

        waitAndAssertResumedAndFocusedActivityOnDisplay(
                TEST_ACTIVITY,
                DEFAULT_DISPLAY,
                "Previous top activity should show when dream is stopped");
    }

    @Test
    public void testShowWhenLockedActivityUnderDream() throws Exception {
        startFullscreenTestActivityWithShowWhenLocked();
        mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_DREAM_SERVICE);

        mDreamCoordinator.startDream();
        waitAndAssertResumedAndFocusedActivityOnDisplay(
                mDreamActivityName,
                DEFAULT_DISPLAY,
                "Dream activity should be the top resumed activity");
        mWmState.waitForValidState(mWmState.getHomeActivityName());
        mWmState.assertVisibility(mWmState.getHomeActivityName(), false);
        mWmState.waitForValidState(SHOW_WHEN_LOCKED_ACTIVITY);
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, false);

        assertTrue(mDreamCoordinator.isDreaming());

        mDreamCoordinator.stopDream();
        mWmState.waitAndAssertActivityRemoved(mDreamActivityName);

        waitAndAssertResumedAndFocusedActivityOnDisplay(
                SHOW_WHEN_LOCKED_ACTIVITY,
                DEFAULT_DISPLAY,
                "ShowWhenLocked activity should be resumed when dream is stopped");
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
    }

    @Test
    public void testDreamServiceStopsTimely() throws Exception {
        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
            mDreamCoordinator.stopDream();
            waitAndAssertDreamActivityGone(ACTIVITY_STOP_MAX_TIMEOUT);
            assertFalse(mDreamCoordinator.isDreaming());
        }
    }

    @Test
    @Ignore("b/428777826")
    public void testForceStopStubbornDream() throws Exception {
        startFullscreenTestActivity();
        mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_STUBBORN_DREAM_SERVICE);

        mDreamCoordinator.startDream();
        waitAndAssertResumedAndFocusedActivityOnDisplay(
                mDreamActivityName,
                DEFAULT_DISPLAY,
                "Dream activity should be the top resumed activity");
        mWmState.waitForValidState(mWmState.getHomeActivityName());
        mWmState.assertVisibility(mWmState.getHomeActivityName(), false);
        mWmState.waitForValidState(TEST_ACTIVITY);
        mWmState.assertVisibility(TEST_ACTIVITY, false);

        mDreamCoordinator.stopDream();

        waitAndAssertDreamActivityGone(DREAM_FORCE_STOP_MAX_TIMEOUT);

        assertFalse(mDreamCoordinator.isDreaming());
        pressUnlockButton();
        waitAndAssertResumedAndFocusedActivityOnDisplay(
                TEST_ACTIVITY,
                DEFAULT_DISPLAY,
                "Previous top activity should show when dream is stopped");
    }

    @Test
    @RequireNotAutomotive(reason = "Automotive screens don't support rotation")
    public void testDreamNotFinishAfterRotation() {
        assumeTrue("Skipping test: no rotation support", supportsRotation());

        final RotationSession rotationSession = createManagedRotationSession();
        rotationSession.set(Surface.ROTATION_0);
        mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_DREAM_SERVICE);
        mDreamCoordinator.startDream();
        rotationSession.set(Surface.ROTATION_90);

        waitAndAssertResumedAndFocusedActivityOnDisplay(
                mDreamActivityName,
                DEFAULT_DISPLAY,
                "Dream activity should be the top resumed activity");
    }

    @Test
    public void testStartActivityDoesNotWakeAndIsNotResumed() {
        assumeFalse(dismissDreamOnActivityStart());

        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
            launchActivity(Components.TEST_ACTIVITY);
            mWmState.waitForActivityState(Components.TEST_ACTIVITY, STATE_STOPPED);
            assertTrue(mDreamCoordinator.isDreaming());
        }
    }

    @Test
    public void testStartActivityWakesDevice() {
        assumeTrue(dismissDreamOnActivityStart());

        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
            launchActivity(TEST_ACTIVITY);
            state.waitForDreamGone();
            assertFalse(mDreamCoordinator.isDreaming());
            waitAndAssertResumedAndFocusedActivityOnDisplay(
                    TEST_ACTIVITY,
                    DEFAULT_DISPLAY,
                    "Test activity should be the top resumed activity");
            mWmState.assertVisibility(TEST_ACTIVITY, true);
        }
    }

    @Test
    public void testStartTurnScreenOnActivityDoesWake() {
        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
            launchActivity(Components.TURN_SCREEN_ON_ACTIVITY);

            state.waitForDreamGone();
            waitAndAssertResumedAndFocusedActivityOnDisplay(
                    Components.TURN_SCREEN_ON_ACTIVITY,
                    DEFAULT_DISPLAY,
                    "TurnScreenOnActivity should resume through dream");
        }
    }

    @Test
    @Ignore("b/428777826")
    public void testStartTurnScreenOnAttrActivityDoesWake() {
        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
            launchActivity(Components.TURN_SCREEN_ON_ATTR_ACTIVITY);
            pressUnlockButton();
            state.waitForDreamGone();
            waitAndAssertResumedAndFocusedActivityOnDisplay(
                    Components.TURN_SCREEN_ON_ATTR_ACTIVITY,
                    DEFAULT_DISPLAY,
                    "TurnScreenOnAttrActivity should resume through dream");
        }
    }

    @Test
    // Re-enabling for flexiglass is tracked in b/412769564.
    @RequiresFlagsDisabled(Flags.FLAG_SCENE_CONTAINER)
    @Ignore("b/428777826")
    public void testStartActivityOnKeyguardLocked() {
        assumeTrue(supportsLockScreen());
        assumeFalse(dismissDreamOnActivityStart());

        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential();
        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
            launchActivityNoWait(Components.TEST_ACTIVITY);
            waitAndAssertActivityState(
                    Components.TEST_ACTIVITY,
                    STATE_STOPPED,
                    "Activity must be started and stopped");
            assertTrue(mDreamCoordinator.isDreaming());

            launchActivity(Components.TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
            state.waitForDreamGone();
            waitAndAssertResumedAndFocusedActivityOnDisplay(
                    Components.TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY,
                    DEFAULT_DISPLAY,
                    "TurnScreenOnShowOnLockActivity should resume through dream");
            assertFalse(mDreamCoordinator.isDreaming());
        }
    }

    @Test
    public void testStartActivityDismissesDreamOnKeyguardLocked() {
        assumeTrue(supportsLockScreen());
        assumeTrue(dismissDreamOnActivityStart());

        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential();
        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
            state.waitForDreamGone();
            waitAndAssertResumedAndFocusedActivityOnDisplay(
                    SHOW_WHEN_LOCKED_ACTIVITY, DEFAULT_DISPLAY, "Activity should dismiss dream");
            assertFalse(mDreamCoordinator.isDreaming());
        }
    }

    private class DreamingState implements AutoCloseable {
        DreamingState(ComponentName dream) {
            mDreamActivityName = mDreamCoordinator.setActiveDream(dream);
            mDreamCoordinator.startDream();
            waitAndAssertDreaming();
        }

        @Override
        public void close() {
            mDreamCoordinator.stopDream();
        }

        public void waitAndAssertDreaming() {
            waitAndAssertResumedAndFocusedActivityOnDisplay(
                    mDreamActivityName,
                    DEFAULT_DISPLAY,
                    "Dream activity should be the top resumed activity");
            mWmState.waitForActivityState(mWmState.getHomeActivityName(), STATE_STOPPED);
            mWmState.waitAndAssertVisibilityGone(mWmState.getHomeActivityName());
            assertTrue(mDreamCoordinator.isDreaming());
        }

        public void waitForDreamGone() {
            mWmState.waitForDreamGone();
            assertFalse(mDreamCoordinator.isDreaming());
        }
    }
}
