chromium/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialogTest.java

// Copyright 2016 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.device_dialog;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.app.Dialog;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;

import androidx.test.filters.SmallTest;

import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.components.security_state.ConnectionSecurityLevel;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.widget.TextViewWithClickableSpans;

import java.lang.ref.WeakReference;

/** Tests for the UsbChooserDialog class. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
// TODO(crbug.com/344665244): Failing when batched, batch this again.
public class UsbChooserDialogTest {
    @ClassRule
    public static final ChromeTabbedActivityTestRule sActivityTestRule =
            new ChromeTabbedActivityTestRule();

    @Rule
    public final BlankCTATabInitialStateRule mInitialStateRule =
            new BlankCTATabInitialStateRule(sActivityTestRule, false);

    @Rule public JniMocker mocker = new JniMocker();

    private String mSelectedDeviceId = "";

    private UsbChooserDialog mChooserDialog;

    // Unused member variables to avoid Java optimizer issues with Mockito.
    @Mock ModalDialogManager mMockModalDialogManager;
    @Mock Activity mMockActivity;
    @Mock WindowAndroid mMockWindowAndroid;

    private class TestUsbChooserDialogJni implements UsbChooserDialog.Natives {
        @Override
        public void onItemSelected(long nativeUsbChooserDialogAndroid, String deviceId) {
            mSelectedDeviceId = deviceId;
        }

        @Override
        public void onDialogCancelled(long nativeUsbChooserDialogAndroid) {}

        @Override
        public void loadUsbHelpPage(long nativeUsbChooserDialogAndroid) {}
    }

    @Before
    public void setUp() throws Exception {
        mocker.mock(UsbChooserDialogJni.TEST_HOOKS, new TestUsbChooserDialogJni());
        mChooserDialog = createDialog();
    }

    private UsbChooserDialog createDialog() {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    UsbChooserDialog dialog =
                            new UsbChooserDialog(
                                    /* nativeUsbChooserDialogPtr= */ 42,
                                    ProfileManager.getLastUsedRegularProfile());
                    dialog.show(
                            sActivityTestRule.getActivity(),
                            "https://origin.example.com/",
                            ConnectionSecurityLevel.SECURE);
                    return dialog;
                });
    }

    private void selectItem(int position) {
        final Dialog dialog = mChooserDialog.mItemChooserDialog.getDialogForTesting();
        final ListView items = dialog.findViewById(R.id.items);
        final Button button = dialog.findViewById(R.id.positive);

        CriteriaHelper.pollUiThread(
                () -> Criteria.checkThat(items.getChildAt(0), Matchers.notNullValue()));

        // The actual index for the first item displayed on screen.
        int firstVisiblePosition = items.getFirstVisiblePosition();
        TouchCommon.singleClickView(items.getChildAt(position - firstVisiblePosition));

        CriteriaHelper.pollUiThread(() -> button.isEnabled());
        // Make sure the button is properly rendered before clicking.
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(button.getHeight(), Matchers.greaterThan(0));
                });
        TouchCommon.singleClickView(button);

        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(mSelectedDeviceId, Matchers.not(Matchers.isEmptyString()));
                });
    }

    /**
     * The messages include <link> ... </link> or <link1> ... </link1>, <link2> ... </link2>
     * sections that are used to create clickable spans. For testing the messages, this function
     * returns the raw string without the tags.
     */
    private static String removeLinkTags(String message) {
        return message.replaceAll("</?link1>", "")
                .replaceAll("</?link2>", "")
                .replaceAll("</?link>", "");
    }

    @Test
    @SmallTest
    public void testCancel() {
        Dialog dialog = mChooserDialog.mItemChooserDialog.getDialogForTesting();
        Assert.assertTrue(dialog.isShowing());

        final ListView items = dialog.findViewById(R.id.items);
        final Button button = dialog.findViewById(R.id.positive);

        // The 'Connect' button should be disabled and the list view should be hidden.
        Assert.assertFalse(button.isEnabled());
        Assert.assertEquals(View.GONE, items.getVisibility());

        dialog.cancel();

        CriteriaHelper.pollUiThread(
                () -> Criteria.checkThat(mSelectedDeviceId, Matchers.isEmptyString()));
    }

    @Test
    @SmallTest
    public void testSelectItem() {
        Dialog dialog = mChooserDialog.mItemChooserDialog.getDialogForTesting();
        Assert.assertTrue(dialog.isShowing());

        TextViewWithClickableSpans statusView = dialog.findViewById(R.id.status);
        final ListView items = dialog.findViewById(R.id.items);
        final Button button = dialog.findViewById(R.id.positive);
        final int position = 1;

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mChooserDialog.addDevice("device_id_0", "device_name_0");
                    mChooserDialog.addDevice("device_id_1", "device_name_1");
                    mChooserDialog.addDevice("device_id_2", "device_name_2");
                    // Show the desired position at the top of the ListView (in case
                    // not all the items can fit on small devices' screens).
                    items.setSelection(position);
                });

        // After adding items to the dialog, the help message should be showing,
        // the 'Connect' button should still be disabled (since nothing's selected),
        // and the list view should show.
        Assert.assertEquals(
                removeLinkTags(
                        sActivityTestRule
                                .getActivity()
                                .getString(R.string.usb_chooser_dialog_footnote_text)),
                statusView.getText().toString());
        Assert.assertFalse(button.isEnabled());
        Assert.assertEquals(View.VISIBLE, items.getVisibility());

        selectItem(position);

        Assert.assertEquals("device_id_1", mSelectedDeviceId);
    }

    @Test
    @SmallTest
    @DisabledTest(message = "b/343347280")
    public void testChooserBlockedByModalDialogManager() {
        ModalDialogManager mockModalDialogManager = mock(ModalDialogManager.class);
        when(mockModalDialogManager.isSuspended(ModalDialogManager.ModalDialogType.APP))
                .thenReturn(true);
        when(mockModalDialogManager.isSuspended(ModalDialogManager.ModalDialogType.TAB))
                .thenReturn(true);
        Activity mockActivity = mock(Activity.class);
        WindowAndroid mockWindowAndroid = mock(WindowAndroid.class);
        when(mockWindowAndroid.getActivity()).thenReturn(new WeakReference<>(mockActivity));
        when(mockWindowAndroid.getModalDialogManager()).thenReturn(mockModalDialogManager);

        UsbChooserDialog dialog;
        dialog =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return UsbChooserDialog.create(
                                    mockWindowAndroid,
                                    "https://origin.example.com/",
                                    ConnectionSecurityLevel.SECURE,
                                    ProfileManager.getLastUsedRegularProfile(),
                                    /* nativeUsbChooserDialogPtr= */ 42);
                        });
        Assert.assertNull(dialog);
    }
}