/*
 * Copyright (C) 2016 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.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.server.wm.CliIntentExtra.extraString;
import static android.server.wm.MockImeHelper.createManagedMockImeSession;
import static android.server.wm.UiDeviceUtils.pressBackButton;
import static android.server.wm.app.Components.DISMISS_KEYGUARD_ACTIVITY;
import static android.server.wm.app.Components.DISMISS_KEYGUARD_METHOD_ACTIVITY;
import static android.server.wm.app.Components.PIP_ACTIVITY;
import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
import static android.server.wm.app.Components.PipActivity.EXTRA_DISMISS_KEYGUARD;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.wm.app.Components.PipActivity.EXTRA_SHOW_OVER_KEYGUARD;
import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_ACTIVITY;
import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY;
import static android.server.wm.app.Components.TURN_SCREEN_ON_ACTIVITY;
import static android.server.wm.app.Components.TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;

import static androidx.test.InstrumentationRegistry.getInstrumentation;

import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;

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

import android.app.Activity;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.server.wm.app.Components;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;

import com.android.compatibility.common.util.CtsTouchUtils;
import com.android.compatibility.common.util.PollingCheck;
import com.android.cts.mockime.ImeEventStream;
import com.android.cts.mockime.MockImeSession;

import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * Build/Install/Run:
 *     atest CtsWindowManagerDeviceTestCases:KeyguardLockedTests
 */
@Presubmit
@android.server.wm.annotation.Group2
public class KeyguardLockedTests extends KeyguardTestBase {

    private final static long TIMEOUT_IME = TimeUnit.SECONDS.toMillis(5);

    @Before
    @Override
    public void setUp() throws Exception {
        super.setUp();
        assumeTrue(supportsSecureLock());
    }

    @Test
    public void testLockAndUnlock() {
        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential().gotoKeyguard();

        assertTrue(mKeyguardManager.isKeyguardLocked());
        assertTrue(mKeyguardManager.isDeviceLocked());
        assertTrue(mKeyguardManager.isDeviceSecure());
        assertTrue(mKeyguardManager.isKeyguardSecure());
        mWmState.assertKeyguardShowingAndNotOccluded();

        lockScreenSession.unlockDevice().enterAndConfirmLockCredential();

        mWmState.waitAndAssertKeyguardGone();
        assertFalse(mKeyguardManager.isDeviceLocked());
        assertFalse(mKeyguardManager.isKeyguardLocked());
    }

    @Test
    public void testDisableKeyguard_thenSettingCredential_reenablesKeyguard_b119322269() {
        final KeyguardManager.KeyguardLock keyguardLock = mContext.getSystemService(
                KeyguardManager.class).newKeyguardLock("KeyguardLockedTests");

        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
            lockScreenSession.gotoKeyguard();
            keyguardLock.disableKeyguard();

            lockScreenSession.setLockCredential();
            lockScreenSession.gotoKeyguard();

            mWmState.waitForKeyguardShowingAndNotOccluded();
            mWmState.assertKeyguardShowingAndNotOccluded();
            assertTrue(mKeyguardManager.isKeyguardLocked());
            assertTrue(mKeyguardManager.isDeviceLocked());
            assertTrue(mKeyguardManager.isDeviceSecure());
            assertTrue(mKeyguardManager.isKeyguardSecure());
        } finally {
            keyguardLock.reenableKeyguard();
        }
    }

    @Test
    public void testDismissKeyguard() {
        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential().gotoKeyguard();

        mWmState.assertKeyguardShowingAndNotOccluded();
        launchActivity(DISMISS_KEYGUARD_ACTIVITY);
        lockScreenSession.enterAndConfirmLockCredential();

        mWmState.waitAndAssertKeyguardGone();
        mWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
    }

    @Test
    public void testDismissKeyguard_whileOccluded() {
        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential().gotoKeyguard();

        mWmState.assertKeyguardShowingAndNotOccluded();
        launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
        mWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);

        launchActivity(DISMISS_KEYGUARD_ACTIVITY);
        lockScreenSession.enterAndConfirmLockCredential();
        mWmState.waitAndAssertKeyguardGone();
        mWmState.computeState(DISMISS_KEYGUARD_ACTIVITY);

        final boolean isDismissTranslucent = mWmState
                .isActivityTranslucent(DISMISS_KEYGUARD_ACTIVITY);
        mWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, isDismissTranslucent);
    }

    @Test
    public void testDismissKeyguard_fromShowWhenLocked_notAllowed() {
        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential().gotoKeyguard();

        mWmState.assertKeyguardShowingAndNotOccluded();
        launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
        mWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
        mBroadcastActionTrigger.dismissKeyguardByFlag();
        lockScreenSession.enterAndConfirmLockCredential();

        // Make sure we stay on Keyguard.
        mWmState.assertKeyguardShowingAndOccluded();
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
    }

    @Test
    public void testDismissKeyguardActivity_method() {
        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential();
        separateTestJournal();

        lockScreenSession.gotoKeyguard();
        mWmState.computeState();
        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);

        launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
        lockScreenSession.enterAndConfirmLockCredential();
        mWmState.waitForKeyguardGone();
        mWmState.computeState(DISMISS_KEYGUARD_METHOD_ACTIVITY);
        mWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, true);
        assertFalse(mWmState.getKeyguardControllerState().keyguardShowing);
        assertOnDismissSucceeded(DISMISS_KEYGUARD_METHOD_ACTIVITY);
    }

    @Test
    public void testDismissKeyguardActivity_method_cancelled() {
        // Pressing the back button does not cancel Keyguard in AAOS.
        assumeFalse(isCar());

        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential();
        separateTestJournal();

        lockScreenSession.gotoKeyguard();
        mWmState.computeState();
        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);

        launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
        pressBackButton();
        assertOnDismissCancelled(DISMISS_KEYGUARD_METHOD_ACTIVITY);
        mWmState.computeState();
        mWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, false);
        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);
    }

    @Test
    public void testTurnScreenOnActivity_withSecureKeyguardAndAod() {
        final AodSession aodSession = createManagedAodSession();
        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential();
        testTurnScreenOnActivity_withSecureKeyguard(aodSession, lockScreenSession,
                false /* enableAod */);
        testTurnScreenOnActivity_withSecureKeyguard(aodSession, lockScreenSession,
                true /* enableAod */);
    }

    private void testTurnScreenOnActivity_withSecureKeyguard(AodSession aodSession,
            LockScreenSession lockScreenSession, boolean enableAod) {
        if (enableAod) {
            assumeTrue(aodSession.isAodAvailable());
        }
        aodSession.setAodEnabled(enableAod);
        lockScreenSession.sleepDevice();
        mWmState.computeState();
        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);

        final CommandSession.ActivitySessionClient activityClient =
                createManagedActivityClientSession();
        final CommandSession.ActivitySession activity = activityClient.startActivity(
                getLaunchActivityBuilder().setUseInstrumentation().setIntentExtra(extra -> {
                    extra.putBoolean(Components.TurnScreenOnActivity.EXTRA_SHOW_WHEN_LOCKED, false);
                }).setTargetActivity(TURN_SCREEN_ON_ACTIVITY));
        mWmState.waitForKeyguardShowingAndNotOccluded();
        mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, false);
        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);
        assertFalse(isDisplayOn(DEFAULT_DISPLAY));
        activity.finish();
    }

    @Test
    public void testDismissKeyguardAttrActivity_method_turnScreenOn_withSecureKeyguard() {
        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential().sleepDevice();
        mWmState.computeState();
        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);

        launchActivity(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
        mWmState.waitForKeyguardShowingAndNotOccluded();
        mWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, false);
        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);
        assertTrue(isDisplayOn(DEFAULT_DISPLAY));
    }

    @Test
    public void testEnterPipOverKeyguard() {
        assumeTrue(supportsPip());

        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential();

        // Show the PiP activity in fullscreen.
        launchActivity(PIP_ACTIVITY, extraString(EXTRA_SHOW_OVER_KEYGUARD, "true"));

        // Lock the screen and ensure that the PiP activity showing over the LockScreen.
        lockScreenSession.gotoKeyguard(PIP_ACTIVITY);
        mWmState.waitForKeyguardShowingAndOccluded();
        mWmState.assertKeyguardShowingAndOccluded();

        // Request that the PiP activity enter picture-in-picture mode (ensure it does not).
        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
        waitForEnterPip(PIP_ACTIVITY);
        mWmState.assertDoesNotContainStack("Must not contain pinned stack.",
                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);

        // Enter the credentials and ensure that the activity actually entered picture-in-picture.
        lockScreenSession.enterAndConfirmLockCredential();
        mWmState.waitAndAssertKeyguardGone();
        waitForEnterPip(PIP_ACTIVITY);
        mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
                ACTIVITY_TYPE_STANDARD);
    }

    @Test
    public void testShowWhenLockedActivityAndPipActivity() {
        assumeTrue(supportsPip());

        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential();

        // Show an activity in PIP.
        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
        waitForEnterPip(PIP_ACTIVITY);
        mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
                ACTIVITY_TYPE_STANDARD);
        mWmState.assertVisibility(PIP_ACTIVITY, true);

        // Show an activity that will keep above the keyguard.
        launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
        mWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);

        // Lock the screen and ensure that the fullscreen activity showing over the lockscreen
        // is visible, but not the PiP activity.
        lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
        mWmState.computeState();
        mWmState.assertKeyguardShowingAndOccluded();
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
        mWmState.assertVisibility(PIP_ACTIVITY, false);
    }

    @Test
    public void testShowWhenLockedPipActivity() {
        assumeTrue(supportsPip());

        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        lockScreenSession.setLockCredential();

        // Show an activity in PIP.
        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"),
                extraString(EXTRA_SHOW_OVER_KEYGUARD, "true"));
        waitForEnterPip(PIP_ACTIVITY);
        mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
                ACTIVITY_TYPE_STANDARD);
        mWmState.assertVisibility(PIP_ACTIVITY, true);

        // Lock the screen and ensure the PiP activity is not visible on the lockscreen even
        // though it's marked as showing over the lockscreen itself.
        lockScreenSession.gotoKeyguard();
        mWmState.assertKeyguardShowingAndNotOccluded();
        mWmState.assertVisibility(PIP_ACTIVITY, false);
    }

    @Test
    public void testDismissKeyguardPipActivity() {
        assumeTrue(supportsPip());

        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        // Show an activity in PIP.
        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"),
                extraString(EXTRA_DISMISS_KEYGUARD, "true"));
        waitForEnterPip(PIP_ACTIVITY);
        mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
                ACTIVITY_TYPE_STANDARD);
        mWmState.assertVisibility(PIP_ACTIVITY, true);

        // Lock the screen and ensure the PiP activity is not visible on the lockscreen even
        // though it's marked as dismiss keyguard.
        lockScreenSession.gotoKeyguard();
        mWmState.computeState();
        mWmState.assertKeyguardShowingAndNotOccluded();
        mWmState.assertVisibility(PIP_ACTIVITY, false);
    }

    @Test
    public void testShowWhenLockedAttrImeActivityAndShowSoftInput() throws Exception {
        assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());

        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        final MockImeSession mockImeSession = createManagedMockImeSession(this);

        lockScreenSession.setLockCredential().gotoKeyguard();
        mWmState.assertKeyguardShowingAndNotOccluded();
        launchActivity(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY);
        mWmState.computeState(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY);
        mWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY, true);

        // Make sure the activity has been called showSoftInput & IME window is visible.
        final ImeEventStream stream = mockImeSession.openEventStream();
        expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
                TIMEOUT_IME);
        // Assert the IME is shown on the expected display.
        mWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
    }

    @Test
    public void testShowWhenLockedImeActivityAndShowSoftInput() throws Exception {
        assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());

        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        final MockImeSession mockImeSession = createManagedMockImeSession(this);
        final TestActivitySession<ShowWhenLockedImeActivity> imeTestActivitySession =
                createManagedTestActivitySession();

        lockScreenSession.setLockCredential().gotoKeyguard();
        mWmState.assertKeyguardShowingAndNotOccluded();
        imeTestActivitySession.launchTestActivityOnDisplaySync(ShowWhenLockedImeActivity.class,
                DEFAULT_DISPLAY);

        // Make sure the activity has been called showSoftInput & IME window is visible.
        final ImeEventStream stream = mockImeSession.openEventStream();
        expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
                TIMEOUT_IME);
        // Assert the IME is shown on the expected display.
        mWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);

    }

    @Test
    public void testImeShowsAfterLockScreenOnEditorTap() throws Exception {
        assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());

        final MockImeSession mockImeSession = createManagedMockImeSession(this);
        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
        final TestActivitySession<ShowImeAfterLockscreenActivity> imeTestActivitySession =
                createManagedTestActivitySession();
        imeTestActivitySession.launchTestActivityOnDisplaySync(ShowImeAfterLockscreenActivity.class,
                DEFAULT_DISPLAY);

        final ShowImeAfterLockscreenActivity activity = imeTestActivitySession.getActivity();
        final View rootView = activity.getWindow().getDecorView();

        CtsTouchUtils.emulateTapOnViewCenter(getInstrumentation(), null, activity.mEditor);
        PollingCheck.waitFor(
                TIMEOUT_IME,
                () -> rootView.getRootWindowInsets().isVisible(ime()));

        lockScreenSession.setLockCredential().gotoKeyguard();
        assertTrue("Keyguard is showing", mWmState.getKeyguardControllerState().keyguardShowing);
        lockScreenSession.enterAndConfirmLockCredential();
        mWmState.waitAndAssertKeyguardGone();

        final ImeEventStream stream = mockImeSession.openEventStream();

        CtsTouchUtils.emulateTapOnViewCenter(getInstrumentation(), null, activity.mEditor);

        // Make sure the activity has been called showSoftInput & IME window is visible.
        expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
                TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
        // Assert the IME is shown event on the expected display.
        mWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
        // Check if IME is actually visible.
        PollingCheck.waitFor(
                TIMEOUT_IME,
                () -> rootView.getRootWindowInsets().isVisible(ime()));
    }

    public static class ShowImeAfterLockscreenActivity extends Activity {

        EditText mEditor;

        @Override
        protected void onCreate(Bundle icicle) {
            super.onCreate(icicle);
            mEditor = createViews(this, false /* showWhenLocked */);
        }
    }

    public static class ShowWhenLockedImeActivity extends Activity {

        @Override
        protected void onCreate(Bundle icicle) {
            super.onCreate(icicle);
            createViews(this, true /* showWhenLocked */);
        }
    }

    private static EditText createViews(
            Activity activity, boolean showWhenLocked /* showWhenLocked */) {
        EditText editor = new EditText(activity);
        // Set private IME option for editorMatcher to identify which TextView received
        // onStartInput event.
        editor.setPrivateImeOptions(
                activity.getClass().getName()
                        + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
        final LinearLayout layout = new LinearLayout(activity);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(editor);
        activity.setContentView(layout);

        if (showWhenLocked) {
            // Set showWhenLocked as true & request focus for showing soft input.
            activity.setShowWhenLocked(true);
            activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
        }
        editor.requestFocus();
        return editor;
    }

    /**
     * Waits until the given activity has entered picture-in-picture mode (allowing for the
     * subsequent animation to start).
     */
    private void waitForEnterPip(ComponentName activityName) {
        mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
                .setWindowingMode(WINDOWING_MODE_PINNED)
                .setActivityType(ACTIVITY_TYPE_STANDARD)
                .build());
    }
}
