chromium/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/ClientManagerTest.java

// Copyright 2015 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 android.os.Looper.getMainLooper;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;

import static org.chromium.chrome.browser.browserservices.verification.ChromeOriginVerifierUnitTestSupport.addVerification;

import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Process;

import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSessionToken;
import androidx.browser.customtabs.PostMessageServiceConnection;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowPackageManager;

import org.chromium.base.IntentUtils;
import org.chromium.base.SysUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.UmaRecorderHolder;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.browserservices.verification.ChromeOriginVerifier;
import org.chromium.chrome.browser.browserservices.verification.ChromeOriginVerifierFactoryImpl;
import org.chromium.chrome.browser.browserservices.verification.ChromeOriginVerifierJni;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.components.content_relationship_verification.OriginVerifier;
import org.chromium.components.content_relationship_verification.OriginVerifierJni;
import org.chromium.components.content_relationship_verification.OriginVerifierUnitTestSupport;
import org.chromium.components.embedder_support.util.Origin;
import org.chromium.components.embedder_support.util.ShadowUrlUtilities;

/** Tests for ClientManager. */
@RunWith(BaseRobolectricTestRunner.class)
@Batch(Batch.UNIT_TESTS)
@Config(
        manifest = Config.NONE,
        shadows = {
            ShadowUrlUtilities.class,
            ShadowPackageManager.class,
            ClientManagerTest.ShadowSysUtils.class
        })
public class ClientManagerTest {
    @Implements(SysUtils.class)
    static class ShadowSysUtils {
        public static boolean sIsLowMemory;

        @Implementation
        public static boolean isCurrentlyLowMemory() {
            return sIsLowMemory;
        }
    }

    private static final String URL = "https://www.android.com";
    private static final String PACKAGE_NAME = "org.chromium.chrome";

    private ClientManager mClientManager;
    private CustomTabsSessionToken mSession =
            CustomTabsSessionToken.createMockSessionTokenForTesting();
    private int mUid = Process.myUid();

    @Mock private ClientManager.InstalledAppProviderWrapper mInstalledAppProviderWrapper;

    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private OriginVerifier.Natives mMockOriginVerifierJni;

    @Mock private ChromeOriginVerifier.Natives mMockChromeOriginVerifierJni;

    @Mock private Profile mProfile;

    @Mock private ChromeBrowserInitializer mChromeBrowserInitializer;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mJniMocker.mock(OriginVerifierJni.TEST_HOOKS, mMockOriginVerifierJni);

        mJniMocker.mock(ChromeOriginVerifierJni.TEST_HOOKS, mMockChromeOriginVerifierJni);
        Mockito.doAnswer(
                        args -> {
                            return 100L;
                        })
                .when(mMockChromeOriginVerifierJni)
                .init(Mockito.any(), Mockito.any());

        Mockito.doAnswer(
                        args -> {
                            ((Runnable) args.getArgument(0)).run();
                            return null;
                        })
                .when(mChromeBrowserInitializer)
                .runNowOrAfterFullBrowserStarted(Mockito.any());

        ProfileManager.setLastUsedProfileForTesting(mProfile);

        RequestThrottler.purgeAllEntriesForTesting();

        OriginVerifierUnitTestSupport.registerPackageWithSignature(
                shadowOf(ApplicationProvider.getApplicationContext().getPackageManager()),
                PACKAGE_NAME,
                mUid);

        mClientManager =
                new ClientManager(
                        new ChromeOriginVerifierFactoryImpl(),
                        mInstalledAppProviderWrapper,
                        mChromeBrowserInitializer);

        ChromeOriginVerifier.clearCachedVerificationsForTesting();
        UmaRecorderHolder.resetForTesting();

        ShadowUrlUtilities.setTestImpl(
                new ShadowUrlUtilities.TestImpl() {
                    @Override
                    public boolean urlsMatchIgnoringFragments(String url1, String url2) {
                        // Limited implementation that is good enough for these tests.
                        int index1 = url1.indexOf('#');
                        int index2 = url2.indexOf('#');

                        if (index1 != -1) url1 = url1.substring(0, index1);
                        if (index2 != -1) url2 = url2.substring(0, index2);

                        return url1.equals(url2);
                    }
                });
    }

    @Test
    @SmallTest
    public void testNoSessionNoWarmup() {
        Assert.assertEquals(
                ClientManager.CalledWarmup.NO_SESSION_NO_WARMUP,
                mClientManager.getWarmupState(null));
    }

    @Test
    @SmallTest
    public void testNoSessionWarmup() {
        mClientManager.recordUidHasCalledWarmup(mUid);
        Assert.assertEquals(
                ClientManager.CalledWarmup.NO_SESSION_WARMUP, mClientManager.getWarmupState(null));
    }

    @Test
    @SmallTest
    public void testInvalidSessionNoWarmup() {
        Assert.assertEquals(
                ClientManager.CalledWarmup.NO_SESSION_NO_WARMUP,
                mClientManager.getWarmupState(mSession));
    }

    @Test
    @SmallTest
    public void testInvalidSessionWarmup() {
        mClientManager.recordUidHasCalledWarmup(mUid);
        Assert.assertEquals(
                ClientManager.CalledWarmup.NO_SESSION_WARMUP,
                mClientManager.getWarmupState(mSession));
    }

    @Test
    @SmallTest
    public void testValidSessionNoWarmup() {
        mClientManager.newSession(mSession, mUid, null, null, null, null);
        Assert.assertEquals(
                ClientManager.CalledWarmup.SESSION_NO_WARMUP_NOT_CALLED,
                mClientManager.getWarmupState(mSession));
    }

    @Test
    @SmallTest
    public void testValidSessionOtherWarmup() {
        mClientManager.recordUidHasCalledWarmup(mUid + 1);
        mClientManager.newSession(mSession, mUid, null, null, null, null);
        Assert.assertEquals(
                ClientManager.CalledWarmup.SESSION_NO_WARMUP_ALREADY_CALLED,
                mClientManager.getWarmupState(mSession));
    }

    @Test
    @SmallTest
    public void testValidSessionWarmup() {
        mClientManager.recordUidHasCalledWarmup(mUid);
        mClientManager.newSession(mSession, mUid, null, null, null, null);
        Assert.assertEquals(
                ClientManager.CalledWarmup.SESSION_WARMUP, mClientManager.getWarmupState(mSession));
    }

    @Test
    @SmallTest
    public void testValidSessionWarmupSeveralCalls() {
        mClientManager.recordUidHasCalledWarmup(mUid);
        mClientManager.newSession(mSession, mUid, null, null, null, null);
        Assert.assertEquals(
                ClientManager.CalledWarmup.SESSION_WARMUP, mClientManager.getWarmupState(mSession));

        CustomTabsSessionToken token = CustomTabsSessionToken.createMockSessionTokenForTesting();
        mClientManager.newSession(token, mUid, null, null, null, null);
        Assert.assertEquals(
                ClientManager.CalledWarmup.SESSION_WARMUP, mClientManager.getWarmupState(token));
    }

    @Test
    @SmallTest
    public void testPredictionOutcomeSuccess() {
        Assert.assertTrue(mClientManager.newSession(mSession, mUid, null, null, null, null));
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, URL, false));
        Assert.assertEquals(
                ClientManager.PredictionStatus.GOOD,
                mClientManager.getPredictionOutcome(mSession, URL));
    }

    @Test
    @SmallTest
    public void testPredictionOutcomeNoPrediction() {
        Assert.assertTrue(mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.recordUidHasCalledWarmup(mUid);
        Assert.assertEquals(
                ClientManager.PredictionStatus.NONE,
                mClientManager.getPredictionOutcome(mSession, URL));
    }

    @Test
    @SmallTest
    public void testPredictionOutcomeBadPrediction() {
        Assert.assertTrue(mClientManager.newSession(mSession, mUid, null, null, null, null));
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, URL, false));
        Assert.assertEquals(
                ClientManager.PredictionStatus.BAD,
                mClientManager.getPredictionOutcome(mSession, URL + "#fragment"));
    }

    @Test
    @SmallTest
    public void testPredictionOutcomeIgnoreFragment() {
        Assert.assertTrue(mClientManager.newSession(mSession, mUid, null, null, null, null));
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, URL, false));
        mClientManager.setIgnoreFragmentsForSession(mSession, true);
        Assert.assertEquals(
                ClientManager.PredictionStatus.GOOD,
                mClientManager.getPredictionOutcome(mSession, URL + "#fragment"));
    }

    @Test
    @SmallTest
    public void testPostMessageOriginVerification() {
        final ClientManager cm = mClientManager;

        // TODO(peconn): Get rid of this anonymous class once PostMessageServiceConnection is made
        // non-abstract. Same with the other occurrences below.
        PostMessageServiceConnection serviceConnection =
                new PostMessageServiceConnection(mSession) {};
        Assert.assertTrue(
                cm.newSession(
                        mSession,
                        mUid,
                        null,
                        MockPostMessageHandler.create(),
                        serviceConnection,
                        null));
        // Should always start with no origin.
        Assert.assertNull(cm.getPostMessageOriginForSessionForTesting(mSession));

        // With no prepopulated origins, this verification should fail.
        cm.verifyAndInitializeWithPostMessageOriginForSession(
                mSession, Origin.create(URL), null, CustomTabsService.RELATION_USE_AS_ORIGIN);
        shadowOf(getMainLooper()).idle();
        Assert.assertNull(cm.getPostMessageOriginForSessionForTesting(mSession));

        // If there is a prepopulated origin, we should get a synchronous verification.
        addVerification(PACKAGE_NAME, Origin.create(URL), CustomTabsService.RELATION_USE_AS_ORIGIN);
        cm.verifyAndInitializeWithPostMessageOriginForSession(
                mSession, Origin.create(URL), null, CustomTabsService.RELATION_USE_AS_ORIGIN);
        shadowOf(getMainLooper()).idle();

        Assert.assertNotNull(cm.getPostMessageOriginForSessionForTesting(mSession));

        Uri verifiedOrigin = cm.getPostMessageOriginForSessionForTesting(mSession);
        Assert.assertEquals(IntentUtils.ANDROID_APP_REFERRER_SCHEME, verifiedOrigin.getScheme());

        // initializeWithPostMessageOriginForSession should override without checking
        // origin.
        cm.initializeWithPostMessageOriginForSession(mSession, null, null);
        Assert.assertNull(cm.getPostMessageOriginForSessionForTesting(mSession));
        Assert.assertNull(cm.getPostMessageTargetOriginForSessionForTesting(mSession));
    }

    @Test
    @SmallTest
    public void testPostMessageOriginDifferentRelations() {
        final ClientManager cm = mClientManager;
        PostMessageServiceConnection serviceConnection =
                new PostMessageServiceConnection(mSession) {};
        Assert.assertTrue(
                cm.newSession(
                        mSession,
                        mUid,
                        null,
                        MockPostMessageHandler.create(),
                        serviceConnection,
                        null));

        Origin origin = Origin.create(URL);
        when(mInstalledAppProviderWrapper.isAppInstalledAndAssociatedWithOrigin(any(), eq(origin)))
                .thenReturn(true);

        // Should always start with no origin.
        Assert.assertNull(cm.getPostMessageOriginForSessionForTesting(mSession));

        // With no prepopulated origins, this verification should fail.
        cm.verifyAndInitializeWithPostMessageOriginForSession(
                mSession, origin, null, CustomTabsService.RELATION_USE_AS_ORIGIN);
        Assert.assertNull(cm.getPostMessageOriginForSessionForTesting(mSession));

        // Prepopulated origins should depend on the relation used.
        addVerification(PACKAGE_NAME, origin, CustomTabsService.RELATION_HANDLE_ALL_URLS);
        // This uses CustomTabsService.RELATION_USE_AS_ORIGIN by default.
        Assert.assertFalse(cm.isFirstPartyOriginForSession(mSession, origin));

        cm.verifyAndInitializeWithPostMessageOriginForSession(
                mSession, origin, null, CustomTabsService.RELATION_HANDLE_ALL_URLS);

        //        ThreadUtils.runOnUiThreadBlocking(() -> {
        Uri verifiedOrigin = cm.getPostMessageOriginForSessionForTesting(mSession);
        Assert.assertEquals(IntentUtils.ANDROID_APP_REFERRER_SCHEME, verifiedOrigin.getScheme());
        // initializeWithPostMessageOriginForSession should override without checking
        // origin.
        cm.initializeWithPostMessageOriginForSession(mSession, null, null);
        Assert.assertNull(cm.getPostMessageOriginForSessionForTesting(mSession));
        //        });
    }

    @Test
    @SmallTest
    public void testFirstLowConfidencePredictionIsNotThrottled() {
        Assert.assertTrue(mClientManager.newSession(mSession, mUid, null, null, null, null));

        // Two low confidence in a row is OK.
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, null, true));
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, null, true));
        mClientManager.registerLaunch(mSession, URL);

        // Low -> High as well.
        RequestThrottler.purgeAllEntriesForTesting();
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, null, true));
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, URL, false));
        mClientManager.registerLaunch(mSession, URL);

        // High -> Low as well.
        RequestThrottler.purgeAllEntriesForTesting();
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, URL, false));
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, null, true));
        mClientManager.registerLaunch(mSession, URL);
    }

    @Test
    @SmallTest
    public void testMayLaunchUrlAccounting() {
        String name = "CustomTabs.MayLaunchUrlType";
        Assert.assertTrue(mClientManager.newSession(mSession, mUid, null, null, null, null));

        // No prediction;
        mClientManager.registerLaunch(mSession, URL);
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        name, ClientManager.MayLaunchUrlType.NO_MAY_LAUNCH_URL));

        // Low confidence.
        RequestThrottler.purgeAllEntriesForTesting();
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, null, true));
        mClientManager.registerLaunch(mSession, URL);
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        name, ClientManager.MayLaunchUrlType.LOW_CONFIDENCE));

        // High confidence.
        RequestThrottler.purgeAllEntriesForTesting();
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, URL, false));
        mClientManager.registerLaunch(mSession, URL);
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        name, ClientManager.MayLaunchUrlType.HIGH_CONFIDENCE));

        // Low and High confidence.
        RequestThrottler.purgeAllEntriesForTesting();
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, URL, false));
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, null, true));
        mClientManager.registerLaunch(mSession, URL);
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        name, ClientManager.MayLaunchUrlType.BOTH));

        // Low and High confidence, same call.
        RequestThrottler.purgeAllEntriesForTesting();
        UmaRecorderHolder.resetForTesting();
        Assert.assertTrue(
                mClientManager.updateStatsAndReturnWhetherAllowed(mSession, mUid, URL, true));
        mClientManager.registerLaunch(mSession, URL);
        Assert.assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        name, ClientManager.MayLaunchUrlType.BOTH));
    }

    @Test
    @SmallTest
    public void testPostMessageWithTargetOrigin() {
        final ClientManager cm = mClientManager;

        PostMessageServiceConnection serviceConnection =
                new PostMessageServiceConnection(mSession) {};
        Assert.assertTrue(
                cm.newSession(
                        mSession,
                        mUid,
                        null,
                        MockPostMessageHandler.create(),
                        serviceConnection,
                        null));
        // Should always start with no origin.
        Assert.assertNull(cm.getPostMessageOriginForSessionForTesting(mSession));
        Assert.assertNull(cm.getPostMessageTargetOriginForSessionForTesting(mSession));

        // If there is a prepopulated origin, we should get a synchronous verification.
        addVerification(PACKAGE_NAME, Origin.create(URL), CustomTabsService.RELATION_USE_AS_ORIGIN);
        cm.verifyAndInitializeWithPostMessageOriginForSession(
                mSession,
                Origin.create(URL),
                Origin.create(URL),
                CustomTabsService.RELATION_USE_AS_ORIGIN);
        shadowOf(getMainLooper()).idle();

        Assert.assertEquals(
                URL, cm.getPostMessageTargetOriginForSessionForTesting(mSession).toString());

        // initializeWithPostMessageOriginForSession should override without checking
        // origin.
        cm.initializeWithPostMessageOriginForSession(mSession, null, null);
        Assert.assertNull(cm.getPostMessageOriginForSessionForTesting(mSession));
        Assert.assertNull(cm.getPostMessageTargetOriginForSessionForTesting(mSession));
    }

    @Test
    @SmallTest
    public void testLogConnectionClosedCTForeground() {
        String histogramName = "CustomTabs.SessionDisconnectStatus";
        ShadowSysUtils.sIsLowMemory = false;

        Assert.assertTrue(
                "A new session should have been created.",
                mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.setCustomTabIsInForeground(mSession, true);
        mClientManager.dontKeepAliveForSession(mSession);

        mClientManager.cleanupSession(mSession);

        Assert.assertEquals(
                "Only one histogram value should have been logged",
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        histogramName, ClientManager.SessionDisconnectStatus.CT_FOREGROUND));
    }

    @Test
    @SmallTest
    public void testLogConnectionClosedCTForegroundKeepAlive() {
        String histogramName = "CustomTabs.SessionDisconnectStatus";
        ShadowSysUtils.sIsLowMemory = false;
        Intent intent =
                new Intent()
                        .setComponent(
                                new ComponentName(
                                        ApplicationProvider.getApplicationContext(),
                                        ChromeLauncherActivity.class));

        Assert.assertTrue(
                "A new session should have been created.",
                mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.setCustomTabIsInForeground(mSession, true);
        mClientManager.keepAliveForSession(mSession, intent);

        mClientManager.cleanupSession(mSession);

        Assert.assertEquals(
                "Only one histogram value should have been logged",
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        histogramName,
                        ClientManager.SessionDisconnectStatus.CT_FOREGROUND_KEEP_ALIVE));
    }

    @Test
    @SmallTest
    public void testLogConnectionClosedCTBackground() {
        String histogramName = "CustomTabs.SessionDisconnectStatus";
        ShadowSysUtils.sIsLowMemory = false;

        Assert.assertTrue(
                "A new session should have been created.",
                mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.setCustomTabIsInForeground(mSession, false);
        mClientManager.dontKeepAliveForSession(mSession);

        mClientManager.cleanupSession(mSession);

        Assert.assertEquals(
                "Only one histogram value should have been logged",
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        histogramName, ClientManager.SessionDisconnectStatus.CT_BACKGROUND));
    }

    @Test
    @SmallTest
    public void testLogConnectionClosedCTBackgroundKeepAlive() {
        String histogramName = "CustomTabs.SessionDisconnectStatus";
        ShadowSysUtils.sIsLowMemory = false;
        Intent intent =
                new Intent()
                        .setComponent(
                                new ComponentName(
                                        ApplicationProvider.getApplicationContext(),
                                        ChromeLauncherActivity.class));

        Assert.assertTrue(
                "A new session should have been created.",
                mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.setCustomTabIsInForeground(mSession, false);
        mClientManager.keepAliveForSession(mSession, intent);

        mClientManager.cleanupSession(mSession);

        Assert.assertEquals(
                "Only one histogram value should have been logged",
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        histogramName,
                        ClientManager.SessionDisconnectStatus.CT_BACKGROUND_KEEP_ALIVE));
    }

    @Test
    @SmallTest
    public void testLogConnectionClosedLowMemoryCTForeground() {
        String histogramName = "CustomTabs.SessionDisconnectStatus";
        ShadowSysUtils.sIsLowMemory = true;

        Assert.assertTrue(
                "A new session should have been created.",
                mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.setCustomTabIsInForeground(mSession, true);
        mClientManager.dontKeepAliveForSession(mSession);

        mClientManager.cleanupSession(mSession);

        Assert.assertEquals(
                "Only one histogram value should have been logged",
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        histogramName,
                        ClientManager.SessionDisconnectStatus.LOW_MEMORY_CT_FOREGROUND));
    }

    @Test
    @SmallTest
    public void testLogConnectionClosedLowMemoryCTForegroundKeepAlive() {
        String histogramName = "CustomTabs.SessionDisconnectStatus";
        ShadowSysUtils.sIsLowMemory = true;
        Intent intent =
                new Intent()
                        .setComponent(
                                new ComponentName(
                                        ApplicationProvider.getApplicationContext(),
                                        ChromeLauncherActivity.class));

        Assert.assertTrue(
                "A new session should have been created.",
                mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.setCustomTabIsInForeground(mSession, true);
        mClientManager.keepAliveForSession(mSession, intent);

        mClientManager.cleanupSession(mSession);

        Assert.assertEquals(
                "Only one histogram value should have been logged",
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        histogramName,
                        ClientManager.SessionDisconnectStatus.LOW_MEMORY_CT_FOREGROUND_KEEP_ALIVE));
    }

    @Test
    @SmallTest
    public void testLogConnectionClosedLowMemoryCTBackground() {
        String histogramName = "CustomTabs.SessionDisconnectStatus";
        ShadowSysUtils.sIsLowMemory = true;

        Assert.assertTrue(
                "A new session should have been created.",
                mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.setCustomTabIsInForeground(mSession, false);
        mClientManager.dontKeepAliveForSession(mSession);

        mClientManager.cleanupSession(mSession);

        Assert.assertEquals(
                "Only one histogram value should have been logged",
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        histogramName,
                        ClientManager.SessionDisconnectStatus.LOW_MEMORY_CT_BACKGROUND));
    }

    @Test
    @SmallTest
    public void testLogConnectionClosedLowMemoryCTBackgroundKeepAlive() {
        String histogramName = "CustomTabs.SessionDisconnectStatus";
        ShadowSysUtils.sIsLowMemory = true;
        Intent intent =
                new Intent()
                        .setComponent(
                                new ComponentName(
                                        ApplicationProvider.getApplicationContext(),
                                        ChromeLauncherActivity.class));

        Assert.assertTrue(
                "A new session should have been created.",
                mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.setCustomTabIsInForeground(mSession, false);
        mClientManager.keepAliveForSession(mSession, intent);

        mClientManager.cleanupSession(mSession);

        Assert.assertEquals(
                "Only one histogram value should have been logged",
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        histogramName,
                        ClientManager.SessionDisconnectStatus.LOW_MEMORY_CT_BACKGROUND_KEEP_ALIVE));
    }

    @Test
    @SmallTest
    public void testLogConnectionClosedCleanupCalledTwiceLogsOnce() {
        String histogramName = "CustomTabs.SessionDisconnectStatus";
        ShadowSysUtils.sIsLowMemory = false;

        Assert.assertTrue(
                "A new session should have been created.",
                mClientManager.newSession(mSession, mUid, null, null, null, null));
        mClientManager.setCustomTabIsInForeground(mSession, true);
        mClientManager.dontKeepAliveForSession(mSession);

        mClientManager.cleanupSession(mSession);
        mClientManager.cleanupSession(mSession);

        Assert.assertEquals(
                "Only one histogram value should have been logged",
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        histogramName, ClientManager.SessionDisconnectStatus.CT_FOREGROUND));
    }
}