chromium/chrome/android/javatests/src/org/chromium/chrome/browser/automotive/BackButtonToolbarTest.java

// Copyright 2023 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.automotive;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;

import static androidx.appcompat.app.ActionBar.DISPLAY_HOME_AS_UP;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;

import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import static org.chromium.chrome.browser.flags.ChromeFeatureList.VERTICAL_AUTOMOTIVE_BACK_BUTTON_TOOLBAR;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup.LayoutParams;

import androidx.activity.ComponentDialog;
import androidx.activity.OnBackPressedCallback;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.Toolbar;
import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags.Add;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.settings.MainSettings;
import org.chromium.chrome.browser.settings.SettingsActivity;
import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
import org.chromium.chrome.test.AutomotiveContextWrapperTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.components.browser_ui.widget.ChromeDialog;
import org.chromium.components.browser_ui.widget.FullscreenAlertDialog;
import org.chromium.ui.test.util.BlankUiTestActivityTestCase;
import org.chromium.ui.test.util.DeviceRestriction;

/** Instrumentation tests for the persistent back button toolbar in automotive. */
@RunWith(ChromeJUnit4ClassRunner.class)
@DoNotBatch(reason = "Each test case launches different Activities.")
@Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class BackButtonToolbarTest extends BlankUiTestActivityTestCase {
    @Rule
    public final ChromeTabbedActivityTestRule mChromeTabbedActivityTestRule =
            new ChromeTabbedActivityTestRule();

    @Rule
    public SettingsActivityTestRule<MainSettings> mSettingsActivityTestRule =
            new SettingsActivityTestRule<>(MainSettings.class);

    @Rule
    public AutomotiveContextWrapperTestRule mAutomotiveContextWrapperTestRule =
            new AutomotiveContextWrapperTestRule();

    private static final int TEST_DIALOG_LAYOUT = R.layout.image_zoom_view;
    private CallbackHelper mBackPressCallbackHelper;

    @Before
    public void setUp() {
        mAutomotiveContextWrapperTestRule.setIsAutomotive(true);
        mBackPressCallbackHelper = new CallbackHelper();
    }

    @Test
    @SmallTest
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_AUTO)
    @Feature({"Automotive Toolbar"})
    @DisableFeatures(VERTICAL_AUTOMOTIVE_BACK_BUTTON_TOOLBAR)
    public void testAutomotiveToolbar_ActionBar() throws Exception {
        mChromeTabbedActivityTestRule.startMainActivityOnBlankPage();
        ChromeTabbedActivity chromeTabbedActivity = mChromeTabbedActivityTestRule.getActivity();

        // Check that the automotive toolbar is present with only a back button.
        assertTrue(chromeTabbedActivity.getSupportActionBar().isShowing());
        assertEquals(
                "Automotive toolbar should only contain a back button",
                DISPLAY_HOME_AS_UP,
                chromeTabbedActivity.getSupportActionBar().getDisplayOptions());

        // Simulate a back button press on the automotive toolbar.
        addOnBackPressedCallback(chromeTabbedActivity, mBackPressCallbackHelper);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    MenuItem backButton = mock(MenuItem.class);
                    when(backButton.getItemId()).thenReturn(android.R.id.home);
                    chromeTabbedActivity.onOptionsItemSelected(backButton);
                });

        // Verify that #onBackPressed was called.
        mBackPressCallbackHelper.waitForOnly();
    }

    @Test
    @SmallTest
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_AUTO)
    @Feature({"Automotive Toolbar"})
    public void testAutomotiveToolbar_ToolbarView() throws Exception {
        // Launch Settings Activity, which uses a Toolbar View to implement the automotive toolbar.
        mSettingsActivityTestRule.startSettingsActivity();
        SettingsActivity settingsActivity = mSettingsActivityTestRule.getActivity();

        // Check that the automotive toolbar is present with only a back button.
        Toolbar toolbar = settingsActivity.findViewById(R.id.back_button_toolbar);
        assertNotNull(toolbar);
        assertEquals("Toolbar not visible", View.VISIBLE, toolbar.getVisibility());
        assertEquals("Toolbar should only contain a back button", 1, toolbar.getChildCount());
        assertThat(toolbar.getChildAt(0), instanceOf(AppCompatImageButton.class));

        // Click the back button in the automotive toolbar.
        addOnBackPressedCallback(settingsActivity, mBackPressCallbackHelper);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    toolbar.getChildAt(0).performClick();
                });

        // Verify that #onBackPressed was called.
        mBackPressCallbackHelper.waitForOnly();
    }

    @Test
    @SmallTest
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_AUTO)
    @Feature({"Automotive Toolbar"})
    public void testAutomotiveToolbar_FullscreenAlertDialog() throws Exception {
        // Display a FullscreenAlertDialog.
        FullscreenAlertDialog dialog = createAndShowFullscreenAlertDialog(getActivity());

        // Check that the automotive toolbar is present with only a back button.
        Toolbar toolbar = dialog.findViewById(R.id.back_button_toolbar);
        assertNotNull(toolbar);
        assertEquals("Toolbar not visible", toolbar.getVisibility(), View.VISIBLE);
        assertEquals("Toolbar should only contain a back button", 1, toolbar.getChildCount());
        assertThat(toolbar.getChildAt(0), instanceOf(AppCompatImageButton.class));

        // Click the back button in the automotive toolbar.
        addOnBackPressedCallback(dialog, mBackPressCallbackHelper);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    toolbar.getChildAt(0).performClick();
                });

        // Verify that #onBackPressed was called.
        mBackPressCallbackHelper.waitForOnly();
    }

    @Test
    @SmallTest
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_AUTO)
    @Feature({"Automotive Toolbar"})
    public void testAutomotiveToolbar_FullscreenAlertDialogBuilder() throws Exception {
        // Display a full screen AlertDialog created using FullscreenAlertDialog.Builder.
        AlertDialog dialog = createAndShowFullscreenAlertDialogFromBuilder(getActivity());

        // Check that the automotive toolbar is present with only a back button.
        Toolbar toolbar = dialog.findViewById(R.id.back_button_toolbar);
        assertNotNull(toolbar);
        assertEquals("Toolbar not visible", toolbar.getVisibility(), View.VISIBLE);
        assertEquals("Toolbar should only contain a back button", 1, toolbar.getChildCount());
        assertThat(toolbar.getChildAt(0), instanceOf(AppCompatImageButton.class));

        // Click the back button in the automotive toolbar.
        addOnBackPressedCallback(dialog, mBackPressCallbackHelper);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    toolbar.getChildAt(0).performClick();
                });

        // Verify that #onBackPressed was called.
        mBackPressCallbackHelper.waitForOnly();
    }

    @Test
    @SmallTest
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_AUTO)
    @Feature({"Automotive Toolbar"})
    public void testAutomotiveToolbar_FullscreenChromeDialog_setContentView() throws Exception {
        // Display a full screen AlertDialog created using FullscreenAlertDialog.Builder.
        ChromeDialog dialog = createAndShowFullscreenChromeDialog(getActivity(), true);

        // Check that the automotive toolbar is present with only a back button.
        Toolbar toolbar = dialog.findViewById(R.id.back_button_toolbar);
        assertNotNull(toolbar);
        assertEquals("Toolbar not visible", toolbar.getVisibility(), View.VISIBLE);
        assertEquals("Toolbar should only contain a back button", 1, toolbar.getChildCount());
        assertThat(toolbar.getChildAt(0), instanceOf(AppCompatImageButton.class));

        // Click the back button in the automotive toolbar.
        addOnBackPressedCallback(dialog, mBackPressCallbackHelper);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    toolbar.getChildAt(0).performClick();
                });

        // Verify that #onBackPressed was called.
        mBackPressCallbackHelper.waitForOnly();
    }

    @Test
    @SmallTest
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_AUTO)
    @Feature({"Automotive Toolbar"})
    public void testAutomotiveToolbar_FullscreenChromeDialog_addContentView() throws Exception {
        // Display a full screen AlertDialog created using FullscreenAlertDialog.Builder.
        ChromeDialog dialog = createAndShowFullscreenChromeDialog(getActivity(), false);

        // Check that the automotive toolbar is present with only a back button.
        Toolbar toolbar = dialog.findViewById(R.id.back_button_toolbar);
        assertNotNull(toolbar);
        assertEquals("Toolbar not visible", toolbar.getVisibility(), View.VISIBLE);
        assertEquals("Toolbar should only contain a back button", 1, toolbar.getChildCount());
        assertThat(toolbar.getChildAt(0), instanceOf(AppCompatImageButton.class));

        // Click the back button in the automotive toolbar.
        addOnBackPressedCallback(dialog, mBackPressCallbackHelper);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    toolbar.getChildAt(0).performClick();
                });

        // Verify that #onBackPressed was called.
        mBackPressCallbackHelper.waitForOnly();
    }

    private void addOnBackPressedCallback(
            AppCompatActivity activity, CallbackHelper backPressCallback) {
        activity.getOnBackPressedDispatcher()
                .addCallback(
                        new OnBackPressedCallback(true) {
                            @Override
                            public void handleOnBackPressed() {
                                backPressCallback.notifyCalled();
                            }
                        });
    }

    private void addOnBackPressedCallback(AlertDialog dialog, CallbackHelper backPressCallback) {
        dialog.getOnBackPressedDispatcher()
                .addCallback(
                        new OnBackPressedCallback(true) {
                            @Override
                            public void handleOnBackPressed() {
                                backPressCallback.notifyCalled();
                            }
                        });
    }

    private void addOnBackPressedCallback(
            ComponentDialog dialog, CallbackHelper backPressCallback) {
        dialog.getOnBackPressedDispatcher()
                .addCallback(
                        new OnBackPressedCallback(true) {
                            @Override
                            public void handleOnBackPressed() {
                                backPressCallback.notifyCalled();
                            }
                        });
    }

    private FullscreenAlertDialog createAndShowFullscreenAlertDialog(Context context)
            throws Exception {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    final FullscreenAlertDialog dialog = new FullscreenAlertDialog(context);
                    View testView = LayoutInflater.from(context).inflate(TEST_DIALOG_LAYOUT, null);
                    dialog.setView(testView);
                    dialog.show();
                    return dialog;
                });
    }

    private AlertDialog createAndShowFullscreenAlertDialogFromBuilder(Context context)
            throws Exception {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    final AlertDialog dialog =
                            new FullscreenAlertDialog.Builder(context)
                                    .setView(TEST_DIALOG_LAYOUT)
                                    .create();
                    dialog.show();
                    return dialog;
                });
    }

    private ChromeDialog createAndShowFullscreenChromeDialog(
            Context context, boolean setContentView) throws Exception {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    final ChromeDialog dialog =
                            new ChromeDialog(context, R.style.ThemeOverlay_BrowserUI_Fullscreen);
                    if (setContentView) {
                        dialog.setContentView(TEST_DIALOG_LAYOUT);
                    } else {
                        dialog.addContentView(
                                LayoutInflater.from(context).inflate(TEST_DIALOG_LAYOUT, null),
                                new LayoutParams(MATCH_PARENT, MATCH_PARENT));
                    }
                    dialog.show();
                    return dialog;
                });
    }
}