chromium/chrome/android/junit/src/org/chromium/chrome/browser/ui/IncognitoRestoreAppLaunchDrawBlockerUnitTest.java

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.ui;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import android.content.Intent;
import android.os.Bundle;

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

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;

import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.crypto.CipherFactory;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthControllerImpl;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.LifecycleObserver;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;

/** Robolectric tests for {@link IncognitoRestoreAppLaunchDrawBlocker}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@LooperMode(Mode.LEGACY)
public class IncognitoRestoreAppLaunchDrawBlockerUnitTest {
    @Mock private Bundle mSavedInstanceStateMock;
    @Mock private Intent mIntentMock;
    @Mock private CipherFactory mCipherFactoryMock;
    @Mock private TabModelSelector mTabModelSelectorMock;
    @Mock private ActivityLifecycleDispatcher mActivityLifecycleDispatcherMock;
    @Mock private Runnable mUnblockDrawRunnableMock;
    @Captor private ArgumentCaptor<LifecycleObserver> mLifecycleObserverArgumentCaptor;

    @Captor
    private ArgumentCaptor<TabModelSelectorObserver> mTabModelSelectorObserverArgumentCaptor;

    private ObservableSupplierImpl<TabModelSelector> mTabModelSelectorObservableSupplier =
            new ObservableSupplierImpl<>();
    private Supplier<Intent> mIntentSupplier =
            new Supplier<Intent>() {
                @Nullable
                @Override
                public Intent get() {
                    return mIntentMock;
                }
            };
    private Supplier<Boolean> mShouldIgnoreIntentSupplier =
            new Supplier<Boolean>() {
                @Nullable
                @Override
                public Boolean get() {
                    return mShouldIgnoreIntent;
                }
            };

    private boolean mShouldIgnoreIntent;
    private IncognitoRestoreAppLaunchDrawBlocker mIncognitoRestoreAppLaunchDrawBlocker;
    private NativeInitObserver mNativeInitObserver;
    private TabModelSelectorObserver mTabModelSelectorObserver;

    private Bundle getSavedInstanceStateMock() {
        return mSavedInstanceStateMock;
    }

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(
                /* isAvailable= */ false);
        CipherFactory.resetInstanceForTesting(mCipherFactoryMock);
        mTabModelSelectorObservableSupplier.set(mTabModelSelectorMock);
        mIncognitoRestoreAppLaunchDrawBlocker =
                new IncognitoRestoreAppLaunchDrawBlocker(
                        this::getSavedInstanceStateMock,
                        mTabModelSelectorObservableSupplier,
                        mIntentSupplier,
                        mShouldIgnoreIntentSupplier,
                        mActivityLifecycleDispatcherMock,
                        mUnblockDrawRunnableMock);

        // Check that the we added the native init observer.
        verify(mActivityLifecycleDispatcherMock, times(1))
                .register(mLifecycleObserverArgumentCaptor.capture());
        mNativeInitObserver = (NativeInitObserver) mLifecycleObserverArgumentCaptor.getValue();
        assertNotNull("Didn't register the NativeInitObserver", mNativeInitObserver);

        verify(mTabModelSelectorMock, times(1))
                .addObserver(mTabModelSelectorObserverArgumentCaptor.capture());
        // This is called by TabModelUtils.runOnTabStateInitialized.
        verify(mTabModelSelectorMock, times(1)).isTabStateInitialized();
        mTabModelSelectorObserver = mTabModelSelectorObserverArgumentCaptor.getValue();
        assertNotNull("Didn't add any observer.", mTabModelSelectorObserver);
    }

    @After
    public void tearDown() {
        verifyNoMoreInteractions(
                mSavedInstanceStateMock,
                mIntentMock,
                mCipherFactoryMock,
                mTabModelSelectorMock,
                mUnblockDrawRunnableMock,
                mActivityLifecycleDispatcherMock);
    }

    @Test
    @SmallTest
    public void testShouldNotBlockDraw_WhenReauthFeatureNotAvailable() {
        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(
                /* isAvailable= */ false);
        assertFalse(
                "Shouldn't block draw.", mIncognitoRestoreAppLaunchDrawBlocker.shouldBlockDraw());
    }

    @Test
    @SmallTest
    @CommandLineFlags.Add({ChromeSwitches.NO_RESTORE_STATE})
    public void testShouldNotBlockDraw_WhenNoRestoreStateSwitchIsPresent() {
        // Premise conditions.
        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(
                /* isAvailable= */ true);

        // Test condition
        assertFalse(
                "Shouldn't block draw.", mIncognitoRestoreAppLaunchDrawBlocker.shouldBlockDraw());
    }

    @Test
    @SmallTest
    public void testShouldNotBlockDraw_WhenNoCipherDataIsFound() {
        // Premise conditions.
        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(
                /* isAvailable= */ true);

        // Test condition
        doReturn(false).when(mCipherFactoryMock).restoreFromBundle(mSavedInstanceStateMock);
        assertFalse(
                "Shouldn't block draw.", mIncognitoRestoreAppLaunchDrawBlocker.shouldBlockDraw());

        verify(mCipherFactoryMock, times(1)).restoreFromBundle(mSavedInstanceStateMock);
    }

    @Test
    @SmallTest
    public void testShouldNotBlockDraw_WhenReauthIsNotPending() {
        // Premise conditions.
        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(
                /* isAvailable= */ true);
        doReturn(true).when(mCipherFactoryMock).restoreFromBundle(mSavedInstanceStateMock);

        // Test condition
        doReturn(false)
                .when(mSavedInstanceStateMock)
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);
        assertFalse(
                "Shouldn't block draw.", mIncognitoRestoreAppLaunchDrawBlocker.shouldBlockDraw());

        verify(mCipherFactoryMock, times(1)).restoreFromBundle(mSavedInstanceStateMock);
        verify(mSavedInstanceStateMock, times(1))
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);
    }

    @Test
    @SmallTest
    public void testShouldNotBlockDraw_WhenIntentingToRegularTab_AndLastTabModelWasNotIncognito() {
        // Premise conditions
        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(
                /* isAvailable= */ true);
        doReturn(true).when(mCipherFactoryMock).restoreFromBundle(mSavedInstanceStateMock);
        doReturn(true)
                .when(mSavedInstanceStateMock)
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);

        // Test conditions
        doReturn(false)
                .when(mSavedInstanceStateMock)
                .getBoolean(IncognitoRestoreAppLaunchDrawBlocker.IS_INCOGNITO_SELECTED, false);
        doReturn(false)
                .when(mIntentMock)
                .getBooleanExtra(IntentHandler.EXTRA_INVOKED_FROM_LAUNCH_NEW_INCOGNITO_TAB, false);
        assertFalse(
                "Shouldn't block draw.", mIncognitoRestoreAppLaunchDrawBlocker.shouldBlockDraw());

        verify(mCipherFactoryMock, times(1)).restoreFromBundle(mSavedInstanceStateMock);
        verify(mSavedInstanceStateMock, times(1))
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);
        verify(mSavedInstanceStateMock, times(1))
                .getBoolean(IncognitoRestoreAppLaunchDrawBlocker.IS_INCOGNITO_SELECTED, false);
        verify(mIntentMock, times(1))
                .getBooleanExtra(IntentHandler.EXTRA_INVOKED_FROM_LAUNCH_NEW_INCOGNITO_TAB, false);
    }

    @Test
    @SmallTest
    public void testShouldNotBlockDraw_WhenBothTabStateIsInitialized_And_NativeIsInitialized() {
        // Premise conditions
        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(
                /* isAvailable= */ true);
        mIncognitoRestoreAppLaunchDrawBlocker.resetIsUnblockDrawRunnableInvokedForTesting();
        doReturn(true).when(mCipherFactoryMock).restoreFromBundle(mSavedInstanceStateMock);
        doReturn(true)
                .when(mSavedInstanceStateMock)
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);
        doReturn(true)
                .when(mSavedInstanceStateMock)
                .getBoolean(IncognitoRestoreAppLaunchDrawBlocker.IS_INCOGNITO_SELECTED, false);
        doReturn(true)
                .when(mIntentMock)
                .getBooleanExtra(IntentHandler.EXTRA_INVOKED_FROM_LAUNCH_NEW_INCOGNITO_TAB, false);

        // Test condition
        doReturn(true).when(mTabModelSelectorMock).isTabStateInitialized();
        mNativeInitObserver.onFinishNativeInitialization();
        assertFalse(
                "Should not block draw.", mIncognitoRestoreAppLaunchDrawBlocker.shouldBlockDraw());

        // Verify all the mocks were called.
        verify(mCipherFactoryMock, times(1)).restoreFromBundle(mSavedInstanceStateMock);
        verify(mSavedInstanceStateMock, times(1))
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);
        verify(mSavedInstanceStateMock, times(1))
                .getBoolean(IncognitoRestoreAppLaunchDrawBlocker.IS_INCOGNITO_SELECTED, false);
        verify(mIntentMock, times(1))
                .getBooleanExtra(IntentHandler.EXTRA_INVOKED_FROM_LAUNCH_NEW_INCOGNITO_TAB, false);
        // This is called again when we call mNativeInitObserver.onFinishNativeInitialization();
        verify(mTabModelSelectorMock, times(3)).isTabStateInitialized();
        // This is called when we call mNativeInitObserver.onFinishNativeInitialization() and since
        // tab state is initialized as well, we will invoke the unblock runnable.
        verify(mUnblockDrawRunnableMock, times(1)).run();
    }

    @Test
    @SmallTest
    public void testShouldBlockDraw_WhenTabStateIsNotInitialized_And_NativeIsInitialized() {
        // Premise conditions
        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(
                /* isAvailable= */ true);
        doReturn(true).when(mCipherFactoryMock).restoreFromBundle(mSavedInstanceStateMock);
        doReturn(true)
                .when(mSavedInstanceStateMock)
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);
        doReturn(true)
                .when(mSavedInstanceStateMock)
                .getBoolean(IncognitoRestoreAppLaunchDrawBlocker.IS_INCOGNITO_SELECTED, false);
        doReturn(true)
                .when(mIntentMock)
                .getBooleanExtra(IntentHandler.EXTRA_INVOKED_FROM_LAUNCH_NEW_INCOGNITO_TAB, false);

        // Test condition
        doReturn(false).when(mTabModelSelectorMock).isTabStateInitialized();
        mNativeInitObserver.onFinishNativeInitialization();
        assertTrue("Should block draw.", mIncognitoRestoreAppLaunchDrawBlocker.shouldBlockDraw());

        // Verify all the mocks were called.
        verify(mCipherFactoryMock, times(1)).restoreFromBundle(mSavedInstanceStateMock);
        verify(mSavedInstanceStateMock, times(1))
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);
        verify(mSavedInstanceStateMock, times(1))
                .getBoolean(IncognitoRestoreAppLaunchDrawBlocker.IS_INCOGNITO_SELECTED, false);
        verify(mIntentMock, times(1))
                .getBooleanExtra(IntentHandler.EXTRA_INVOKED_FROM_LAUNCH_NEW_INCOGNITO_TAB, false);
        // This is called again when we call mNativeInitObserver.onFinishNativeInitialization();
        verify(mTabModelSelectorMock, times(3)).isTabStateInitialized();
    }

    @Test
    @SmallTest
    public void testShouldBlockDraw_WhenTabStateIsInitialized_And_WhenNativeIsNotInitialized() {
        // Premise conditions
        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(
                /* isAvailable= */ true);
        doReturn(true).when(mCipherFactoryMock).restoreFromBundle(mSavedInstanceStateMock);
        doReturn(true)
                .when(mSavedInstanceStateMock)
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);
        doReturn(true)
                .when(mSavedInstanceStateMock)
                .getBoolean(IncognitoRestoreAppLaunchDrawBlocker.IS_INCOGNITO_SELECTED, false);
        doReturn(true)
                .when(mIntentMock)
                .getBooleanExtra(IntentHandler.EXTRA_INVOKED_FROM_LAUNCH_NEW_INCOGNITO_TAB, false);

        // Test condition
        doReturn(true).when(mTabModelSelectorMock).isTabStateInitialized();
        assertTrue(
                "Should block draw as native has not finished initialization.",
                mIncognitoRestoreAppLaunchDrawBlocker.shouldBlockDraw());

        // Verify all the mocks were called.
        verify(mCipherFactoryMock, times(1)).restoreFromBundle(mSavedInstanceStateMock);
        verify(mSavedInstanceStateMock, times(1))
                .getBoolean(IncognitoReauthControllerImpl.KEY_IS_INCOGNITO_REAUTH_PENDING, false);
        verify(mSavedInstanceStateMock, times(1))
                .getBoolean(IncognitoRestoreAppLaunchDrawBlocker.IS_INCOGNITO_SELECTED, false);
        verify(mIntentMock, times(1))
                .getBooleanExtra(IntentHandler.EXTRA_INVOKED_FROM_LAUNCH_NEW_INCOGNITO_TAB, false);
        verify(mTabModelSelectorMock, times(2)).isTabStateInitialized();
    }

    @Test
    @SmallTest
    public void testUnblockDrawRunnableNotInvoked_WhenTabStateNotInitialized() {
        doReturn(false).when(mTabModelSelectorMock).isTabStateInitialized();
        mNativeInitObserver.onFinishNativeInitialization();

        verify(mTabModelSelectorMock, times(2)).isTabStateInitialized();
        verifyNoMoreInteractions(mUnblockDrawRunnableMock);
    }

    @Test
    @SmallTest
    public void testUnblockDrawRunnableNotInvoked_WhenNativeNotInitialized() {
        doReturn(true).when(mTabModelSelectorMock).isTabStateInitialized();
        mTabModelSelectorObserver.onTabStateInitialized();

        verify(mTabModelSelectorMock, times(1)).removeObserver(mTabModelSelectorObserver);
        verify(mTabModelSelectorMock, times(2)).isTabStateInitialized();
        verifyNoMoreInteractions(mUnblockDrawRunnableMock);
    }

    @Test
    @SmallTest
    public void testUnblockDrawRunnableInvoked_WhenTabStateAndNativeIsInitialized() {
        // We need to reset the boolean so that we can verify mUnblockDrawRunnable is invoked.
        mIncognitoRestoreAppLaunchDrawBlocker.resetIsUnblockDrawRunnableInvokedForTesting();
        doReturn(true).when(mTabModelSelectorMock).isTabStateInitialized();
        mNativeInitObserver.onFinishNativeInitialization();

        verify(mTabModelSelectorMock, times(2)).isTabStateInitialized();
        verify(mUnblockDrawRunnableMock, times(1)).run();
    }
}