chromium/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsServiceTest.java

// Copyright 2018 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.chromecast.shell;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Notification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.PatternMatcher;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.android.controller.ServiceController;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowService;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chromecast.base.ReactiveRecorder;
import org.chromium.content_public.browser.MediaSession;
import org.chromium.content_public.browser.WebContents;

/**
 * Tests for CastWebContentsService.
 */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class CastWebContentsServiceTest {
    private static final String WEBCONTENTS_TITLE = "CastWebContentsServiceTest_title";

    private @Mock WebContents mWebContents;
    private @Mock MediaSession mMediaSession;
    private String mInstanceId;
    private Intent mIntent;
    private ServiceController<CastWebContentsService> mServiceLifecycle;
    private CastWebContentsService mService;
    private ShadowService mShadowService;

    private IntentFilter filterFor(String action) {
        IntentFilter filter = new IntentFilter();
        Uri instanceUri = CastWebContentsIntentUtils.getInstanceUri(mInstanceId);
        filter.addDataScheme(instanceUri.getScheme());
        filter.addDataAuthority(instanceUri.getAuthority(), null);
        filter.addDataPath(instanceUri.getPath(), PatternMatcher.PATTERN_LITERAL);
        filter.addAction(action);
        return filter;
    }

    private void expectBroadcastedIntent(IntentFilter filter, Runnable runnable) {
        BroadcastReceiver receiver = mock(BroadcastReceiver.class);
        CastWebContentsIntentUtils.getLocalBroadcastManager().registerReceiver(receiver, filter);
        try {
            runnable.run();
        } finally {
            CastWebContentsIntentUtils.getLocalBroadcastManager().unregisterReceiver(receiver);
            verify(receiver).onReceive(any(Context.class), any(Intent.class));
        }
    }

    @Before
    public void setUp() {
        mWebContents = mock(WebContents.class);
        when(mWebContents.getTitle()).thenReturn(WEBCONTENTS_TITLE);
        mMediaSession = mock(MediaSession.class);
        mInstanceId = "1";
        mIntent = CastWebContentsIntentUtils.requestStartCastService(
                RuntimeEnvironment.application, mWebContents, mInstanceId);
        mServiceLifecycle =
                Robolectric.buildService(CastWebContentsService.class).withIntent(mIntent);
        mService = mServiceLifecycle.get();
        mService.setMediaSessionGetterForTesting(
                webContents -> {
                    assertEquals(webContents, mWebContents);
                    return mMediaSession;
                });
        mShadowService = Shadows.shadowOf(mService);
    }

    @After
    public void tearDown() {
        mServiceLifecycle.unbind();
        mServiceLifecycle.destroy();
    }

    @Test
    public void testForegroundedAfterBind() {
        mServiceLifecycle.bind();
        assertNotNull(mShadowService.getLastForegroundNotification());
        assertFalse(mShadowService.isForegroundStopped());
        assertFalse(mShadowService.getNotificationShouldRemoved());
    }

    @Test
    public void testForegroundNotificationHasUniqueChannelId() {
        mServiceLifecycle.bind();
        Notification notification = mShadowService.getLastForegroundNotification();
        String notificationChannelId = notification.getChannelId();
        assertNotNull(notificationChannelId);
        assertEquals("org.chromium.chromecast.shell.CastWebContentsService.channel",
                notificationChannelId);
    }

    @Test
    public void testForegroundNotificationHasCorrectSmallIcon() {
        mServiceLifecycle.bind();
        Notification notification = mShadowService.getLastForegroundNotification();
        assertNotNull(notification.getSmallIcon());
        assertEquals(R.drawable.ic_settings_cast, notification.getSmallIcon().getResId());
    }

    @Test
    public void testNotificationTitleMatchesWebContentsTitle() {
        mServiceLifecycle.bind();
        Notification notification = mShadowService.getLastForegroundNotification();
        assertEquals(notification.extras.getCharSequence(Notification.EXTRA_TITLE).toString(),
                WEBCONTENTS_TITLE);
    }

    @Test
    public void testBackgroundedAfterUnbind() {
        mServiceLifecycle.bind().unbind();
        assertTrue(mShadowService.getNotificationShouldRemoved());
        assertTrue(mShadowService.isForegroundStopped());
    }

    @Test
    public void testBroadcastsComponentClosedWhenUnbind() {
        mServiceLifecycle.bind();
        IntentFilter filter = filterFor(CastWebContentsIntentUtils.ACTION_ACTIVITY_STOPPED);
        expectBroadcastedIntent(filter, () -> mServiceLifecycle.unbind());
    }

    @Test
    public void testDisplaysContentsOnBindAndReleasesOnUnbind() {
        ReactiveRecorder recordWebContentsPresentation =
                ReactiveRecorder.record(mService.observeWebContentsStateForTesting());
        mServiceLifecycle.bind();
        recordWebContentsPresentation.verify().opened(mWebContents).end();
        mServiceLifecycle.unbind();
        recordWebContentsPresentation.verify().closed(mWebContents).end();
    }

    @Test
    public void testDoesNotDisplayNullWebContents() {
        ReactiveRecorder recordWebContentsPresentation =
                ReactiveRecorder.record(mService.observeWebContentsStateForTesting());
        mIntent = CastWebContentsIntentUtils.requestStartCastService(
                RuntimeEnvironment.application, null, mInstanceId);
        mServiceLifecycle.withIntent(mIntent).bind();
        recordWebContentsPresentation.verify().end();
    }

    @Test
    public void testRequestsSystemAudioFocusOnBind() {
        mServiceLifecycle.bind();
        verify(mMediaSession).requestSystemAudioFocus();
    }
}