chromium/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/SessionDataHolderTest.java

// Copyright 2019 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.browserservices;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;

import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSessionToken;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.TranslucentCustomTabActivity;

/** Unit tests for {@link SessionDataHolder}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SessionDataHolderTest {

    private static final int TASK_ID_1 = 10;
    private static final int TASK_ID_2 = 20;

    private Intent mIntent1;
    private Intent mIntent2;
    private CustomTabsSessionToken mSession1;
    private CustomTabsSessionToken mSession2;

    @Mock CustomTabsConnection mConnection;
    @Mock SessionHandler mHandler1;
    @Mock SessionHandler mHandler2;
    @Mock Activity mActivityInTask1;
    @Mock Activity mActivityInTask2;
    @Captor ArgumentCaptor<Callback<CustomTabsSessionToken>> mDisconnectCallbackCaptor;

    private SessionDataHolder mHolder;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mIntent1 = createIntentWithSessionId(1);
        mSession1 = CustomTabsSessionToken.getSessionTokenFromIntent(mIntent1);
        mIntent2 = createIntentWithSessionId(2);
        mSession2 = CustomTabsSessionToken.getSessionTokenFromIntent(mIntent2);
        when(mHandler1.getSession()).thenReturn(mSession1);
        when(mHandler2.getSession()).thenReturn(mSession2);
        when(mHandler1.getActivityClass()).thenReturn((Class) CustomTabActivity.class);
        when(mHandler2.getActivityClass()).thenReturn((Class) TranslucentCustomTabActivity.class);
        when(mActivityInTask1.getTaskId()).thenReturn(TASK_ID_1);
        when(mActivityInTask2.getTaskId()).thenReturn(TASK_ID_2);
        doNothing().when(mConnection).setDisconnectCallback(mDisconnectCallbackCaptor.capture());
        mHolder = new SessionDataHolder(() -> mConnection);
    }

    private Intent createIntentWithSessionId(int id) {
        PendingIntent pi =
                PendingIntent.getActivity(RuntimeEnvironment.systemContext, id, new Intent(), 0);
        return new Intent().putExtra(CustomTabsIntent.EXTRA_SESSION_ID, pi);
    }

    @Test
    public void returnsActiveHandler_IfSessionsMatch() {
        startActivity1();
        assertEquals(mHandler1, mHolder.getActiveHandler(mSession1));
    }

    @Test
    public void doesntReturnActiveHandler_IfSessionsDontMatch() {
        startActivity2();
        assertNull(mHolder.getActiveHandler(mSession1));
    }

    @Test
    public void doesntReturnActiveHandler_IfItWasRemoved() {
        startActivity1();
        stopActivity1();
        assertNull(mHolder.getActiveHandler(mSession1));
    }

    @Test
    public void switchingFromOneCustomTabToAnother_MakesTheSecondOneTheActiveHandler() {
        startActivity1();

        // The order is important: onStart of foregrounded activity is called before onStop of the
        // backgrounded one.
        startActivity2();
        stopActivity1();
        assertEquals(mHandler2, mHolder.getActiveHandler(mSession2));
    }

    @Test
    public void returnsActivityClassOfActiveHandler_WithMatchingSession() {
        when(mHandler1.getTaskId()).thenReturn(TASK_ID_1);
        startActivity1();
        Class<? extends Activity> activity =
                mHolder.getActiveHandlerClassInCurrentTask(mIntent1, mActivityInTask1);
        assertEquals(CustomTabActivity.class, activity);
    }

    @Test
    public void returnsActivityClassOfActiveHandler_EvenIfItsNotFocused() {
        when(mHandler1.getTaskId()).thenReturn(TASK_ID_1);
        startActivity1();
        stopActivity1();

        // New intent arrives, bringing task 1 to foreground, but onStart was not yet called.
        Class<? extends Activity> activity =
                mHolder.getActiveHandlerClassInCurrentTask(mIntent1, mActivityInTask1);
        assertEquals(CustomTabActivity.class, activity);
    }

    @Test
    public void returnsNullActivityClass_IfSessionsDontMatch() {
        when(mHandler1.getTaskId()).thenReturn(TASK_ID_1);
        startActivity1();

        Class<? extends Activity> activity =
                mHolder.getActiveHandlerClassInCurrentTask(mIntent2, mActivityInTask1);
        assertNull(activity);
    }

    @Test
    public void returnsNullActivityClass_IfActivityWithMatchingSession_IsInAnotherTask() {
        when(mHandler1.getTaskId()).thenReturn(TASK_ID_1);
        when(mHandler2.getTaskId()).thenReturn(TASK_ID_2);

        startActivity1();
        startActivity2();

        Class<? extends Activity> activity =
                mHolder.getActiveHandlerClassInCurrentTask(mIntent2, mActivityInTask1);
        assertNull(activity);
    }

    @Test
    public void returnsNullActivityClass_IfActivityWithMatchingSession_IsNotTopmostInTask() {
        when(mHandler1.getTaskId()).thenReturn(TASK_ID_1);
        when(mHandler2.getTaskId()).thenReturn(TASK_ID_1); // Note: same task.

        startActivity1();
        startActivity2();

        Class<? extends Activity> activity =
                mHolder.getActiveHandlerClassInCurrentTask(mIntent1, mActivityInTask1);
        assertNull(activity);
    }

    @Test
    public void returnsNullActivityClass_IfSessionWasTerminated() {
        when(mHandler1.getTaskId()).thenReturn(TASK_ID_1);
        startActivity1();
        disconnect(mSession1);
        Class<? extends Activity> activity =
                mHolder.getActiveHandlerClassInCurrentTask(mIntent1, mActivityInTask1);
        assertNull(activity);
    }

    private void disconnect(CustomTabsSessionToken session) {
        Callback<CustomTabsSessionToken> callback = mDisconnectCallbackCaptor.getValue();
        if (callback != null) {
            callback.onResult(session);
        }
    }

    private void startActivity1() {
        mHolder.setActiveHandler(mHandler1);
    }

    private void startActivity2() {
        mHolder.setActiveHandler(mHandler2);
    }

    private void stopActivity1() {
        mHolder.removeActiveHandler(mHandler1);
    }
}