chromium/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTrackerUnitTest.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.customtabs;

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

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.SystemClock;
import android.text.TextUtils;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowSystemClock;

import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.shared_preferences.SharedPreferencesManager;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.customtabs.CustomTabActivityLifecycleUmaTracker.ClientIdentifierType;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;

import java.util.concurrent.TimeUnit;

/** Unit test for {@link CustomTabActivityLifecycleUmaTracker}. */
@RunWith(BaseRobolectricTestRunner.class)
@Batch(Batch.UNIT_TESTS)
@Config(shadows = {ShadowSystemClock.class})
public class CustomTabActivityLifecycleUmaTrackerUnitTest {
    private static final String PACKAGE_A = "com.example.test.package";
    private static final String PACKAGE_B = "org.test.mypackage";
    private static final String REFERRER_A = "android-app://" + PACKAGE_A;
    private static final String REFERRER_B = "android-app://" + PACKAGE_B;
    private static final int TASK_ID_123 = 123;

    private SharedPreferencesManager mPref;

    @Before
    public void setUp() {
        mPref = ChromeSharedPreferences.getInstance();
        mPref.writeLong(
                ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TIMESTAMP, SystemClock.uptimeMillis());
        mPref.writeBoolean(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION, true);

        ShadowSystemClock.advanceBy(1, TimeUnit.MINUTES);
    }

    @After
    public void tearDown() {
        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION);
        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE);
        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TIMESTAMP);
        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_REFERRER);
        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID);
        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_URL);

        ShadowSystemClock.reset();
    }

    @Test
    public void testRecord_SamePackageName() {
        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
                PACKAGE_A, REFERRER_A, TASK_ID_123, mPref, /* launchWithSameUri= */ true);
        assertInteractionRecorded(ClientIdentifierType.PACKAGE_NAME);
    }

    @Test
    public void testRecord_DiffPackageName() {
        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
                PACKAGE_B, REFERRER_B, TASK_ID_123, mPref, /* launchWithSameUri= */ true);
        assertInteractionRecorded(ClientIdentifierType.DIFFERENT);
    }

    @Test
    public void testRecord_SameReferrer() {
        recordPrefForTesting(null, REFERRER_A, TASK_ID_123);
        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
                null, REFERRER_A, TASK_ID_123, mPref, /* launchWithSameUri= */ true);
        assertInteractionRecorded(ClientIdentifierType.REFERRER);
    }

    @Test
    public void testRecord_DiffReferrer() {
        recordPrefForTesting(null, REFERRER_A, TASK_ID_123);
        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
                null, REFERRER_B, TASK_ID_123, mPref, /* launchWithSameUri= */ true);
        assertInteractionRecorded(ClientIdentifierType.DIFFERENT);
    }

    @Test
    public void testRecord_Mixed_ReferrerThenPackage() {
        recordPrefForTesting(null, REFERRER_A, TASK_ID_123);
        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
                PACKAGE_A, "Random referral", TASK_ID_123, mPref, /* launchWithSameUri= */ true);
        assertInteractionRecorded(ClientIdentifierType.MIXED);
    }

    @Test
    public void testRecord_Mixed_PackageThenReferrer() {
        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
                null, REFERRER_A, TASK_ID_123, mPref, /* launchWithSameUri= */ true);
        assertInteractionRecorded(ClientIdentifierType.MIXED);
    }

    @Test
    public void testRecord_DiffUri() {
        recordPrefForTesting(null, REFERRER_A, TASK_ID_123);
        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
                PACKAGE_A, REFERRER_A, TASK_ID_123, mPref, /* launchWithSameUri= */ false);
        assertNoInteractionRecorded();
    }

    @Test
    public void testRecord_DiffTaskId() {
        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
                PACKAGE_A, REFERRER_A, 99, mPref, /* launchWithSameUri= */ true);
        assertInteractionRecorded(ClientIdentifierType.PACKAGE_NAME);
    }

    @Test
    public void testRecord_NoInteraction() {
        mPref.writeBoolean(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION, false);

        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
                PACKAGE_A, REFERRER_A, TASK_ID_123, mPref, /* launchWithSameUri= */ true);
        assertNoInteractionRecorded();
    }

    @Test
    public void testGetReferrer() {
        String extraActivityReferrer = "android-app://extra.activity.referrer";
        Uri activityReferrer = Uri.parse("android-app://activity.referrer");
        String extraReferrerName = "android-app://extra.referrer.name";

        Assert.assertEquals(
                "IntentHandler.EXTRA_ACTIVITY_REFERRER should be used.",
                extraActivityReferrer,
                getReferrer(
                        buildMockActivity(
                                extraActivityReferrer, activityReferrer, extraReferrerName)));
        Assert.assertEquals(
                "Activity#getReferrer should be used.",
                activityReferrer.toString(),
                getReferrer(buildMockActivity(null, activityReferrer, extraReferrerName)));
        Assert.assertEquals(
                "Intent.EXTRA_REFERRER should be used.",
                extraReferrerName,
                getReferrer(buildMockActivity(null, null, extraReferrerName)));
    }

    @Test
    public void testGetReferrer_InvalidInputs() {
        Assert.assertTrue(TextUtils.isEmpty(getReferrer(null)));
        Assert.assertTrue(TextUtils.isEmpty(getReferrer(mock(Activity.class))));

        Activity activityWithNoReferral = buildMockActivity(null, null, null);
        Assert.assertTrue(TextUtils.isEmpty(getReferrer(activityWithNoReferral)));
    }

    private void recordPrefForTesting(String prefPackageName, String prefReferrer, int prefTaskId) {
        mPref.writeString(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE, prefPackageName);
        mPref.writeString(ChromePreferenceKeys.CUSTOM_TABS_LAST_REFERRER, prefReferrer);
        mPref.writeInt(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID, prefTaskId);
    }

    private void assertInteractionRecorded(@ClientIdentifierType String expectedSuffix) {
        String prefix = "CustomTabs.RetainableSessionsV2.TimeBetweenLaunch";
        String[] suffixes = {
            ClientIdentifierType.REFERRER,
            ClientIdentifierType.PACKAGE_NAME,
            ClientIdentifierType.MIXED,
            ClientIdentifierType.DIFFERENT
        };

        for (String suffix : suffixes) {
            String histogram = prefix + suffix;
            Assert.assertEquals(
                    "<" + histogram + "> record is different.",
                    expectedSuffix.equals(suffix) ? 1 : 0,
                    RecordHistogram.getHistogramTotalCountForTesting(histogram));
        }
    }

    private void assertNoInteractionRecorded() {
        assertInteractionRecorded("");
    }

    // For test readability
    private String getReferrer(Activity activity) {
        return CustomTabActivityLifecycleUmaTracker.getReferrerUriString(activity);
    }

    private Activity buildMockActivity(
            String extraActivityReferrer, Uri activityReferrer, String extraReferrerName) {
        Activity activity = mock(Activity.class);
        Intent intent = mock(Intent.class);

        doReturn(intent).when(activity).getIntent();
        doReturn(activityReferrer).when(activity).getReferrer();
        doReturn(extraActivityReferrer)
                .when(intent)
                .getStringExtra(IntentHandler.EXTRA_ACTIVITY_REFERRER);
        doReturn(extraReferrerName).when(intent).getStringExtra(Intent.EXTRA_REFERRER_NAME);

        return activity;
    }

    @Test
    public void testClientId_SamePackageName() {
        Assert.assertEquals(
                "ClientIdentifierType mismatch.",
                CustomTabActivityLifecycleUmaTracker.ClientIdentifierType.PACKAGE_NAME,
                CustomTabActivityLifecycleUmaTracker.getClientIdentifierType(
                        PACKAGE_A, PACKAGE_A, REFERRER_A, null, TASK_ID_123, 99));
    }

    @Test
    public void testClientId_DiffPackageName() {
        Assert.assertEquals(
                "ClientIdentifierType mismatch.",
                CustomTabActivityLifecycleUmaTracker.ClientIdentifierType.DIFFERENT,
                CustomTabActivityLifecycleUmaTracker.getClientIdentifierType(
                        PACKAGE_A, PACKAGE_B, REFERRER_B, null, TASK_ID_123, TASK_ID_123));
    }

    @Test
    public void testClientId_SameReferrer() {
        Assert.assertEquals(
                "ClientIdentifierType mismatch.",
                CustomTabActivityLifecycleUmaTracker.ClientIdentifierType.REFERRER,
                CustomTabActivityLifecycleUmaTracker.getClientIdentifierType(
                        null, null, REFERRER_A, REFERRER_A, TASK_ID_123, TASK_ID_123));
    }

    @Test
    public void testClientId_DiffReferrer() {
        Assert.assertEquals(
                "ClientIdentifierType mismatch.",
                CustomTabActivityLifecycleUmaTracker.ClientIdentifierType.DIFFERENT,
                CustomTabActivityLifecycleUmaTracker.getClientIdentifierType(
                        null, null, REFERRER_A, REFERRER_B, TASK_ID_123, TASK_ID_123));
    }

    @Test
    public void testClientId_Mixed_ReferrerThenPackage() {
        Assert.assertEquals(
                "ClientIdentifierType mismatch.",
                CustomTabActivityLifecycleUmaTracker.ClientIdentifierType.MIXED,
                CustomTabActivityLifecycleUmaTracker.getClientIdentifierType(
                        PACKAGE_A, null, "Random referral", REFERRER_A, TASK_ID_123, TASK_ID_123));
    }

    @Test
    public void testClientId_Mixed_PackageThenReferrer() {
        Assert.assertEquals(
                "ClientIdentifierType mismatch.",
                CustomTabActivityLifecycleUmaTracker.ClientIdentifierType.MIXED,
                CustomTabActivityLifecycleUmaTracker.getClientIdentifierType(
                        null, PACKAGE_A, REFERRER_A, null, TASK_ID_123, TASK_ID_123));
    }

    @Test
    public void testClientId_DiffTaskId() {
        Assert.assertEquals(
                "ClientIdentifierType mismatch.",
                CustomTabActivityLifecycleUmaTracker.ClientIdentifierType.PACKAGE_NAME,
                CustomTabActivityLifecycleUmaTracker.getClientIdentifierType(
                        PACKAGE_A, PACKAGE_A, REFERRER_A, null, 99, TASK_ID_123));
    }
}