chromium/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogTest.java

// Copyright 2021 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.download.dialogs;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.RootMatchers.isDialog;
import static androidx.test.espresso.matcher.ViewMatchers.Visibility.GONE;
import static androidx.test.espresso.matcher.ViewMatchers.Visibility.VISIBLE;
import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import androidx.annotation.StringRes;
import androidx.test.filters.MediumTest;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import org.chromium.base.FeatureList;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.DirectoryOption;
import org.chromium.chrome.browser.download.DownloadDialogBridge;
import org.chromium.chrome.browser.download.DownloadDialogBridgeJni;
import org.chromium.chrome.browser.download.DownloadDirectoryProvider;
import org.chromium.chrome.browser.download.DownloadLocationDialogType;
import org.chromium.chrome.browser.download.DownloadPromptStatus;
import org.chromium.chrome.browser.download.TestDownloadDirectoryProvider;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
import org.chromium.components.browser_ui.util.DownloadUtils;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.components.user_prefs.UserPrefsJni;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.test.util.BlankUiTestActivityTestCase;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/** Test focus on verifying UI elements in the download location dialog. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class DownloadLocationDialogTest extends BlankUiTestActivityTestCase {
    private static final long TOTAL_BYTES = 1024L;
    private static final String SUGGESTED_PATH = "download.png";
    private static final String PRIMARY_STORAGE_PATH = "/sdcard";
    private static final String SECONDARY_STORAGE_PATH = "/android/Download";

    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private DownloadLocationDialogController mController;
    @Mock private Profile mProfileMock;
    @Mock private Profile mIncognitoProfileMock;
    @Mock PrefService mPrefService;
    @Mock private UserPrefs.Natives mUserPrefsJniMock;
    @Mock private DownloadDialogBridge.Natives mDownloadDialogBridgeJniMock;

    private AppModalPresenter mAppModalPresenter;
    private ModalDialogManager mModalDialogManager;
    private DownloadLocationDialogCoordinator mDialogCoordinator;

    @Override
    public void setUpTest() throws Exception {
        super.setUpTest();
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mUserPrefsJniMock);
        when(mUserPrefsJniMock.get(any())).thenReturn(mPrefService);
        mJniMocker.mock(DownloadDialogBridgeJni.TEST_HOOKS, mDownloadDialogBridgeJniMock);
        when(mPrefService.getString(Pref.DOWNLOAD_DEFAULT_DIRECTORY))
                .thenReturn(PRIMARY_STORAGE_PATH);

        when(mProfileMock.getOriginalProfile()).thenReturn(mProfileMock);
        when(mIncognitoProfileMock.getOriginalProfile()).thenReturn(mProfileMock);
        when(mIncognitoProfileMock.isOffTheRecord()).thenReturn(true);

        mAppModalPresenter = new AppModalPresenter(getActivity());
        mModalDialogManager =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return new ModalDialogManager(
                                    mAppModalPresenter, ModalDialogManager.ModalDialogType.APP);
                        });
        Map<String, Boolean> features = new HashMap<>();
        features.put(ChromeFeatureList.SMART_SUGGESTION_FOR_LARGE_DOWNLOADS, false);
        FeatureList.setTestFeatures(features);

        setDownloadPromptStatus(DownloadPromptStatus.SHOW_INITIAL);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    // Create fake directory options.
                    ArrayList<DirectoryOption> dirs = new ArrayList<>();
                    dirs.add(
                            buildDirectoryOption(
                                    DirectoryOption.DownloadLocationDirectoryType.DEFAULT,
                                    PRIMARY_STORAGE_PATH));
                    dirs.add(
                            buildDirectoryOption(
                                    DirectoryOption.DownloadLocationDirectoryType.ADDITIONAL,
                                    SECONDARY_STORAGE_PATH));
                    DownloadDirectoryProvider.getInstance()
                            .setDirectoryProviderForTesting(
                                    new TestDownloadDirectoryProvider(dirs));
                });

        mDialogCoordinator = new DownloadLocationDialogCoordinator();
        mDialogCoordinator.initialize(mController);
    }

    private DirectoryOption buildDirectoryOption(
            @DirectoryOption.DownloadLocationDirectoryType int type, String directoryPath) {
        return new DirectoryOption("Download", directoryPath, 1024000, 1024000, type);
    }

    private void setDownloadPromptStatus(@DownloadPromptStatus int promptStatus) {
        when(mPrefService.getInteger(Pref.PROMPT_FOR_DOWNLOAD_ANDROID)).thenReturn(promptStatus);
    }

    private void setPromptForPolicy(boolean promptForPolicy) {
        when(mPrefService.getBoolean(Pref.PROMPT_FOR_DOWNLOAD)).thenReturn(promptForPolicy);
    }

    private void showDialog(
            long totalBytes,
            @DownloadLocationDialogType int dialogType,
            String suggestedPath,
            Profile profile) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mDialogCoordinator.showDialog(
                            getActivity(),
                            mModalDialogManager,
                            totalBytes,
                            dialogType,
                            suggestedPath,
                            profile);
                });
    }

    private void assertTitle(@StringRes int titleId) {
        onView(withText(getActivity().getString(titleId)))
                .inRoot(isDialog())
                .check(matches(isDisplayed()));
    }

    private void assertSubtitle(String subtitle) {
        onView(withText(subtitle)).inRoot(isDialog()).check(matches(isDisplayed()));
    }

    /**
     * Verifies the state of the "don't show again" checkbox.
     *
     * @param checked The expected state of the checkbox. If null, the checkbox is expected to be
     *     hidden.
     */
    private void assertDontShowAgainCheckbox(Boolean checked) {
        if (checked == null) {
            onView(withId(R.id.show_again_checkbox)).check(matches(withEffectiveVisibility(GONE)));
        } else if (checked) {
            onView(withId(R.id.show_again_checkbox)).check(matches(isChecked()));
        } else {
            onView(withId(R.id.show_again_checkbox)).check(matches(isNotChecked()));
        }
    }

    private void assertIncognitoWarningShown(boolean shown) {
        onView(withId(R.id.incognito_warning))
                .check(matches(withEffectiveVisibility(shown ? VISIBLE : GONE)));
    }

    @Test
    @MediumTest
    public void testDefaultLocationDialog() throws Exception {
        showDialog(TOTAL_BYTES, DownloadLocationDialogType.DEFAULT, SUGGESTED_PATH, mProfileMock);
        assertTitle(R.string.download_location_dialog_title);
        assertSubtitle(DownloadUtils.getStringForBytes(getActivity(), TOTAL_BYTES));
        assertIncognitoWarningShown(false);
        assertDontShowAgainCheckbox(true);
    }

    @Test
    @MediumTest
    public void testDefaultLocationDialogIncognito() {
        showDialog(
                TOTAL_BYTES,
                DownloadLocationDialogType.DEFAULT,
                SUGGESTED_PATH,
                mIncognitoProfileMock);
        assertTitle(R.string.download_location_dialog_title);
        assertSubtitle(DownloadUtils.getStringForBytes(getActivity(), TOTAL_BYTES));
        assertIncognitoWarningShown(true);
        assertDontShowAgainCheckbox(null);
    }

    @Test
    @MediumTest
    public void testDefaultLocationDialogUnchecked() throws Exception {
        setDownloadPromptStatus(DownloadPromptStatus.SHOW_PREFERENCE);
        showDialog(TOTAL_BYTES, DownloadLocationDialogType.DEFAULT, SUGGESTED_PATH, mProfileMock);
        assertTitle(R.string.download_location_dialog_title);
        assertSubtitle(DownloadUtils.getStringForBytes(getActivity(), TOTAL_BYTES));
        assertIncognitoWarningShown(false);
        assertDontShowAgainCheckbox(false);
    }

    @Test
    @MediumTest
    public void testLocationFull() throws Exception {
        showDialog(
                TOTAL_BYTES,
                DownloadLocationDialogType.LOCATION_FULL,
                SUGGESTED_PATH,
                mProfileMock);
        assertTitle(R.string.download_location_not_enough_space);
        assertSubtitle(
                getActivity()
                        .getResources()
                        .getString(R.string.download_location_download_to_default_folder));
        assertIncognitoWarningShown(false);
        assertDontShowAgainCheckbox(null);
    }

    @Test
    @MediumTest
    public void testLocationNotFound() throws Exception {
        showDialog(
                TOTAL_BYTES,
                DownloadLocationDialogType.LOCATION_NOT_FOUND,
                SUGGESTED_PATH,
                mProfileMock);
        assertTitle(R.string.download_location_no_sd_card);
        assertSubtitle(
                getActivity()
                        .getResources()
                        .getString(R.string.download_location_download_to_default_folder));
        assertIncognitoWarningShown(false);
        assertDontShowAgainCheckbox(null);
    }

    @Test
    @MediumTest
    public void testNameTooLong() throws Exception {
        showDialog(
                TOTAL_BYTES,
                DownloadLocationDialogType.NAME_TOO_LONG,
                SUGGESTED_PATH,
                mProfileMock);
        assertTitle(R.string.download_location_rename_file);
        assertSubtitle(
                getActivity().getResources().getString(R.string.download_location_name_too_long));
        assertIncognitoWarningShown(false);
        assertDontShowAgainCheckbox(null);
    }

    @Test
    @MediumTest
    public void testNameConflict() throws Exception {
        showDialog(
                TOTAL_BYTES,
                DownloadLocationDialogType.NAME_CONFLICT,
                SUGGESTED_PATH,
                mProfileMock);
        assertTitle(R.string.download_location_download_again);
        assertSubtitle(
                getActivity().getResources().getString(R.string.download_location_name_exists));
        assertIncognitoWarningShown(false);
        assertDontShowAgainCheckbox(true);
    }

    @Test
    @MediumTest
    public void testNameConflictIncognito() throws Exception {
        showDialog(
                TOTAL_BYTES,
                DownloadLocationDialogType.NAME_CONFLICT,
                SUGGESTED_PATH,
                mIncognitoProfileMock);
        assertTitle(R.string.download_location_download_again);
        assertSubtitle(
                getActivity().getResources().getString(R.string.download_location_name_exists));
        assertIncognitoWarningShown(true);
        assertDontShowAgainCheckbox(null);
    }

    @Test
    @MediumTest
    public void testForceShowEnterprisePolicy() throws Exception {
        when(mPrefService.isManagedPreference(Pref.PROMPT_FOR_DOWNLOAD)).thenReturn(true);
        setPromptForPolicy(true);
        setDownloadPromptStatus(DownloadPromptStatus.SHOW_PREFERENCE);
        showDialog(TOTAL_BYTES, DownloadLocationDialogType.DEFAULT, SUGGESTED_PATH, mProfileMock);
        assertTitle(R.string.download_location_dialog_title_confirm_download);
        assertSubtitle(DownloadUtils.getStringForBytes(getActivity(), TOTAL_BYTES));
        assertIncognitoWarningShown(false);
        assertDontShowAgainCheckbox(null);
    }
}