chromium/components/page_info/android/javatests/src/org/chromium/components/page_info/TrackingProtectionStatusPreferenceTest.java

// Copyright 2024 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.components.page_info;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.not;

import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.IntDef;
import androidx.preference.PreferenceScreen;
import androidx.test.filters.LargeTest;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
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.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.components.browser_ui.settings.BlankUiTestActivitySettingsTestRule;
import org.chromium.components.browser_ui.settings.PlaceholderSettingsForTest;
import org.chromium.components.content_settings.CookieControlsBridge.TrackingProtectionFeature;
import org.chromium.components.content_settings.CookieControlsEnforcement;
import org.chromium.components.content_settings.TrackingProtectionBlockingStatus;
import org.chromium.components.content_settings.TrackingProtectionFeatureType;

import java.util.Arrays;
import java.util.List;

/** Tests for TrackingProtectionStatusPreference. */
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
public class TrackingProtectionStatusPreferenceTest {
    private static class TestElement {
        public @TrackingProtectionFeatureType int feature;
        public String protectionOn;
        public String protectionOff;
        public @TrackingProtectionBlockingStatus int statusOn;
        public @TrackingProtectionBlockingStatus int statusOff;

        public TestElement(
                @TrackingProtectionFeatureType int feature,
                String protectionOn,
                String protectionOff,
                @TrackingProtectionBlockingStatus int statusOn,
                @TrackingProtectionBlockingStatus int statusOff) {
            this.feature = feature;
            this.protectionOn = protectionOn;
            this.protectionOff = protectionOff;
            this.statusOn = statusOn;
            this.statusOff = statusOff;
        }
    }

    @Rule
    public final BlankUiTestActivitySettingsTestRule mSettingsRule =
            new BlankUiTestActivitySettingsTestRule();

    private Activity mActivity;
    private PreferenceScreen mPreferenceScreen;
    private List<TestElement> mTestElements;

    @Before
    public void setUp() {
        mSettingsRule.launchPreference(PlaceholderSettingsForTest.class);
        mActivity = mSettingsRule.getActivity();
        mPreferenceScreen = mSettingsRule.getPreferenceScreen();
        populateTestElements();
    }

    void populateTestElements() {
        mTestElements =
                Arrays.asList(
                        new TestElement(
                                TrackingProtectionFeatureType.THIRD_PARTY_COOKIES,
                                "Third-party cookies limited",
                                "Third-party cookies allowed",
                                TrackingProtectionBlockingStatus.LIMITED,
                                TrackingProtectionBlockingStatus.ALLOWED),
                        new TestElement(
                                TrackingProtectionFeatureType.FINGERPRINTING_PROTECTION,
                                "Digital fingerprinting limited",
                                "Digital fingerprinting allowed",
                                TrackingProtectionBlockingStatus.LIMITED,
                                TrackingProtectionBlockingStatus.ALLOWED),
                        new TestElement(
                                TrackingProtectionFeatureType.IP_PROTECTION,
                                "IP address hidden",
                                "IP address visible",
                                TrackingProtectionBlockingStatus.HIDDEN,
                                TrackingProtectionBlockingStatus.VISIBLE));
    }

    @Test
    @LargeTest
    public void testToggleTrackingProtection() {
        var preference = new TrackingProtectionStatusPreference(mActivity);
        mPreferenceScreen.addPreference(preference);
        // Simulate updates as if Tracking Protection is on.
        for (TestElement element : mTestElements) {
            preference.updateStatus(
                    new TrackingProtectionFeature(
                            element.feature,
                            CookieControlsEnforcement.NO_ENFORCEMENT,
                            element.statusOn),
                    true);
        }

        // Check each element is there and in the correct state.
        for (TestElement element : mTestElements) {
            onView(withText(containsString(element.protectionOn))).check(matches(isDisplayed()));
        }

        // Simulate updates as if Tracking Protection is off.
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    for (TestElement element : mTestElements) {
                        preference.updateStatus(
                                new TrackingProtectionFeature(
                                        element.feature,
                                        CookieControlsEnforcement.NO_ENFORCEMENT,
                                        element.statusOff),
                                true);
                    }
                });
        // Check each element is there and in the protection off state.
        for (TestElement element : mTestElements) {
            onView(withText(containsString(element.protectionOff))).check(matches(isDisplayed()));
        }
    }

    @Test
    @LargeTest
    public void testElementVisibility() {
        var preference = new TrackingProtectionStatusPreference(mActivity);
        mPreferenceScreen.addPreference(preference);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    // Set all the features to visible.
                    for (TestElement element : mTestElements) {
                        preference.updateStatus(
                                new TrackingProtectionFeature(
                                        element.feature,
                                        CookieControlsEnforcement.NO_ENFORCEMENT,
                                        element.statusOn),
                                true);
                    }
                });

        // Check that hiding each element works.
        for (TestElement element : mTestElements) {
            ThreadUtils.runOnUiThreadBlocking(
                    () -> {
                        preference.updateStatus(
                                new TrackingProtectionFeature(
                                        element.feature,
                                        CookieControlsEnforcement.NO_ENFORCEMENT,
                                        element.statusOn),
                                false);
                    });
            onView(withText(containsString(element.protectionOn)))
                    .check(matches(not(isDisplayed())));
            // The other elements should still be displayed.
            for (TestElement element2 : mTestElements) {
                if (element.feature != element2.feature) {
                    onView(withText(containsString(element2.protectionOn)))
                            .check(matches(isDisplayed()));
                }
            }
            // Reenable.
            ThreadUtils.runOnUiThreadBlocking(
                    () -> {
                        preference.updateStatus(
                                new TrackingProtectionFeature(
                                        element.feature,
                                        CookieControlsEnforcement.NO_ENFORCEMENT,
                                        element.statusOn),
                                true);
                    });
            onView(withText(containsString(element.protectionOn))).check(matches(isDisplayed()));
        }
    }

    @Test
    @LargeTest
    public void testDelayedVisibility() {
        var preference = new TrackingProtectionStatusPreference(mActivity);
        // Set all the features to visible.
        for (TestElement element : mTestElements) {
            preference.updateStatus(
                    new TrackingProtectionFeature(
                            element.feature,
                            CookieControlsEnforcement.NO_ENFORCEMENT,
                            element.statusOn),
                    true);
        }

        // Only add to the PreferenceScreen after configuring.
        mPreferenceScreen.addPreference(preference);

        // The visibility updates should be correctly delayed until onBindViewHolder.
        for (TestElement element : mTestElements) {
            onView(withText(containsString(element.protectionOn))).check(matches(isDisplayed()));
        }
    }

    @IntDef({
        CompoundDrawable.START,
        CompoundDrawable.TOP,
        CompoundDrawable.END,
        CompoundDrawable.BOTTOM
    })
    private @interface CompoundDrawable {
        int START = 0;
        int TOP = 1;
        int END = 2;
        int BOTTOM = 3;
    }

    private static Matcher<View> compoundDrawableVisible(@CompoundDrawable int position) {
        return new TypeSafeMatcher<View>() {
            @Override
            public boolean matchesSafely(View view) {
                if (!(view instanceof TextView)) {
                    return false;
                }
                Drawable[] compoundDrawables = ((TextView) view).getCompoundDrawablesRelative();
                Drawable endDrawable = compoundDrawables[position];
                return endDrawable != null;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("with drawable in position " + position);
            }
        };
    }

    @Test
    @LargeTest
    public void testManagedIcons() {
        var preference = new TrackingProtectionStatusPreference(mActivity);
        // Cookies allowed by policy; IPP disabled by setting, FPP enabled.
        preference.updateStatus(
                new TrackingProtectionFeature(
                        TrackingProtectionFeatureType.THIRD_PARTY_COOKIES,
                        CookieControlsEnforcement.ENFORCED_BY_POLICY,
                        TrackingProtectionBlockingStatus.ALLOWED),
                true);
        preference.updateStatus(
                new TrackingProtectionFeature(
                        TrackingProtectionFeatureType.IP_PROTECTION,
                        CookieControlsEnforcement.ENFORCED_BY_COOKIE_SETTING,
                        TrackingProtectionBlockingStatus.VISIBLE),
                true);
        preference.updateStatus(
                new TrackingProtectionFeature(
                        TrackingProtectionFeatureType.FINGERPRINTING_PROTECTION,
                        CookieControlsEnforcement.NO_ENFORCEMENT,
                        TrackingProtectionBlockingStatus.LIMITED),
                true);
        mPreferenceScreen.addPreference(preference);

        // Verify the managed icons.
        onView(withId(R.id.cookie_status))
                .check(matches(compoundDrawableVisible(CompoundDrawable.END)));
        onView(withId(R.id.ip_status))
                .check(matches(compoundDrawableVisible(CompoundDrawable.END)));
        onView(withId(R.id.fingerprint_status))
                .check(matches(not(compoundDrawableVisible(CompoundDrawable.END))));
    }
}