/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tv.data; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.tv.data.api.Channel; import com.android.tv.testing.ComparatorTester; import com.android.tv.util.TvInputManagerHelper; import java.util.Comparator; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; /** Tests for {@link ChannelImpl}. */ @SmallTest @RunWith(AndroidJUnit4.class) public class ChannelImplTest { // Used for testing TV inputs with invalid input package. This could happen when a TV input is // uninstalled while drawing an app link card. private static final String INVALID_TV_INPUT_PACKAGE_NAME = "com.android.tv.invalid_tv_input"; // Used for testing TV inputs defined inside of Live TV. private static final String LIVE_CHANNELS_PACKAGE_NAME = "com.android.tv"; // Used for testing a TV input which doesn't have its leanback launcher activity. private static final String NONE_LEANBACK_TV_INPUT_PACKAGE_NAME = "com.android.tv.none_leanback_tv_input"; // Used for testing a TV input which has its leanback launcher activity. private static final String LEANBACK_TV_INPUT_PACKAGE_NAME = "com.android.tv.leanback_tv_input"; private static final String TEST_APP_LINK_TEXT = "test_app_link_text"; private static final String PARTNER_INPUT_ID = "partner"; private static final ActivityInfo TEST_ACTIVITY_INFO = new ActivityInfo(); private Context mMockContext; private Intent mInvalidIntent; private Intent mValidIntent; @Before public void setUp() throws NameNotFoundException { mInvalidIntent = new Intent(Intent.ACTION_VIEW); mInvalidIntent.setComponent(new ComponentName(INVALID_TV_INPUT_PACKAGE_NAME, ".test")); mValidIntent = new Intent(Intent.ACTION_VIEW); mValidIntent.setComponent(new ComponentName(LEANBACK_TV_INPUT_PACKAGE_NAME, ".test")); Intent liveChannelsIntent = new Intent(Intent.ACTION_VIEW); liveChannelsIntent.setComponent( new ComponentName(LIVE_CHANNELS_PACKAGE_NAME, ".MainActivity")); Intent leanbackTvInputIntent = new Intent(Intent.ACTION_VIEW); leanbackTvInputIntent.setComponent( new ComponentName(LEANBACK_TV_INPUT_PACKAGE_NAME, ".test")); PackageManager mockPackageManager = Mockito.mock(PackageManager.class); Mockito.when( mockPackageManager.getLeanbackLaunchIntentForPackage( INVALID_TV_INPUT_PACKAGE_NAME)) .thenReturn(null); Mockito.when( mockPackageManager.getLeanbackLaunchIntentForPackage( LIVE_CHANNELS_PACKAGE_NAME)) .thenReturn(liveChannelsIntent); Mockito.when( mockPackageManager.getLeanbackLaunchIntentForPackage( NONE_LEANBACK_TV_INPUT_PACKAGE_NAME)) .thenReturn(null); Mockito.when( mockPackageManager.getLeanbackLaunchIntentForPackage( LEANBACK_TV_INPUT_PACKAGE_NAME)) .thenReturn(leanbackTvInputIntent); // Channel.getAppLinkIntent() calls initAppLinkTypeAndIntent() which calls // Intent.resolveActivityInfo() which calls PackageManager.getActivityInfo(). Mockito.doAnswer( new Answer() { @Override public ActivityInfo answer(InvocationOnMock invocation) { // We only check the package name, since the class name can be // changed // when an intent is changed to an uri and created from the uri. // (ex, ".className" -> "packageName.className") return mValidIntent .getComponent() .getPackageName() .equals( ((ComponentName) invocation .getArguments()[0]) .getPackageName()) ? TEST_ACTIVITY_INFO : null; } }) .when(mockPackageManager) .getActivityInfo(Mockito.any(), Mockito.anyInt()); mMockContext = Mockito.mock(Context.class); Mockito.when(mMockContext.getApplicationContext()).thenReturn(mMockContext); Mockito.when(mMockContext.getPackageName()).thenReturn(LIVE_CHANNELS_PACKAGE_NAME); Mockito.when(mMockContext.getPackageManager()).thenReturn(mockPackageManager); } @Test public void testGetAppLinkType_NoText_NoIntent() { assertAppLinkType(Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, null); assertAppLinkType(Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, null); assertAppLinkType( Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, null, null); assertAppLinkType(Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, null); } @Test public void testGetAppLinkType_NoText_InvalidIntent() { assertAppLinkType( Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, mInvalidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, mInvalidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, null, mInvalidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, mInvalidIntent); } @Test public void testGetAppLinkType_NoText_ValidIntent() { assertAppLinkType( Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, mValidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, mValidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, null, mValidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, mValidIntent); } @Test public void testGetAppLinkType_HasText_NoIntent() { assertAppLinkType( Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, TEST_APP_LINK_TEXT, null); assertAppLinkType( Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, TEST_APP_LINK_TEXT, null); assertAppLinkType( Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, TEST_APP_LINK_TEXT, null); assertAppLinkType( Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, TEST_APP_LINK_TEXT, null); } @Test public void testGetAppLinkType_HasText_InvalidIntent() { assertAppLinkType( Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, TEST_APP_LINK_TEXT, mInvalidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, TEST_APP_LINK_TEXT, mInvalidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, TEST_APP_LINK_TEXT, mInvalidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, TEST_APP_LINK_TEXT, mInvalidIntent); } @Test public void testGetAppLinkType_HasText_ValidIntent() { assertAppLinkType( Channel.APP_LINK_TYPE_CHANNEL, INVALID_TV_INPUT_PACKAGE_NAME, TEST_APP_LINK_TEXT, mValidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_CHANNEL, LIVE_CHANNELS_PACKAGE_NAME, TEST_APP_LINK_TEXT, mValidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_CHANNEL, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, TEST_APP_LINK_TEXT, mValidIntent); assertAppLinkType( Channel.APP_LINK_TYPE_CHANNEL, LEANBACK_TV_INPUT_PACKAGE_NAME, TEST_APP_LINK_TEXT, mValidIntent); } private void assertAppLinkType( int expectedType, String inputPackageName, String appLinkText, Intent appLinkIntent) { // In ChannelImpl, Intent.URI_INTENT_SCHEME is used to parse the URI. So the same flag // should be // used when the URI is created. ChannelImpl testChannel = new ChannelImpl.Builder() .setPackageName(inputPackageName) .setAppLinkText(appLinkText) .setAppLinkIntentUri( appLinkIntent == null ? null : appLinkIntent.toUri(Intent.URI_INTENT_SCHEME)) .build(); assertWithMessage("Unexpected app-link type for for " + testChannel) .that(testChannel.getAppLinkType(mMockContext)) .isEqualTo(expectedType); } @Test public void testComparator() { TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); Mockito.when(manager.isPartnerInput(Matchers.anyString())) .thenAnswer( new Answer() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { String inputId = (String) invocation.getArguments()[0]; return PARTNER_INPUT_ID.equals(inputId); } }); Comparator comparator = new TestChannelComparator(manager); ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest(comparator); comparatorTester.addComparableGroup( new ChannelImpl.Builder().setInputId(PARTNER_INPUT_ID).build()); comparatorTester.addComparableGroup(new ChannelImpl.Builder().setInputId("1").build()); comparatorTester.addComparableGroup( new ChannelImpl.Builder().setInputId("1").setDisplayNumber("2").build()); comparatorTester.addComparableGroup( new ChannelImpl.Builder().setInputId("2").setDisplayNumber("1.0").build()); // display name does not affect comparator comparatorTester.addComparableGroup( new ChannelImpl.Builder() .setInputId("2") .setDisplayNumber("1.62") .setDisplayName("test1") .build(), new ChannelImpl.Builder() .setInputId("2") .setDisplayNumber("1.62") .setDisplayName("test2") .build(), new ChannelImpl.Builder() .setInputId("2") .setDisplayNumber("1.62") .setDisplayName("test3") .build()); comparatorTester.addComparableGroup( new ChannelImpl.Builder().setInputId("2").setDisplayNumber("2.0").build()); // Numeric display number sorting comparatorTester.addComparableGroup( new ChannelImpl.Builder().setInputId("2").setDisplayNumber("12.2").build()); comparatorTester.test(); } /** * Test Input Label handled by {@link ChannelImpl.DefaultComparator}. * *

Sort partner inputs first, then sort by input label, then by input id. See b/23031603. */ @Test public void testComparatorLabel() { TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); Mockito.when(manager.isPartnerInput(Matchers.anyString())) .thenAnswer( new Answer() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { String inputId = (String) invocation.getArguments()[0]; return PARTNER_INPUT_ID.equals(inputId); } }); Comparator comparator = new ChannelComparatorWithDescriptionAsLabel(manager); ComparatorTester comparatorTester = ComparatorTester.withoutEqualsTest(comparator); comparatorTester.addComparableGroup( new ChannelImpl.Builder().setInputId(PARTNER_INPUT_ID).setDescription("A").build()); // The description is used as a label for this test. comparatorTester.addComparableGroup( new ChannelImpl.Builder().setDescription("A").setInputId("1").build()); comparatorTester.addComparableGroup( new ChannelImpl.Builder().setDescription("A").setInputId("2").build()); comparatorTester.addComparableGroup( new ChannelImpl.Builder().setDescription("B").setInputId("1").build()); comparatorTester.test(); } @Test public void testNormalizeChannelNumber() { assertNormalizedDisplayNumber(null, null); assertNormalizedDisplayNumber("", ""); assertNormalizedDisplayNumber("1", "1"); assertNormalizedDisplayNumber("abcde", "abcde"); assertNormalizedDisplayNumber("1-1", "1-1"); assertNormalizedDisplayNumber("1.1", "1-1"); assertNormalizedDisplayNumber("1 1", "1-1"); assertNormalizedDisplayNumber("1\u058a1", "1-1"); assertNormalizedDisplayNumber("1\u05be1", "1-1"); assertNormalizedDisplayNumber("1\u14001", "1-1"); assertNormalizedDisplayNumber("1\u18061", "1-1"); assertNormalizedDisplayNumber("1\u20101", "1-1"); assertNormalizedDisplayNumber("1\u20111", "1-1"); assertNormalizedDisplayNumber("1\u20121", "1-1"); assertNormalizedDisplayNumber("1\u20131", "1-1"); assertNormalizedDisplayNumber("1\u20141", "1-1"); } private void assertNormalizedDisplayNumber(String displayNumber, String normalized) { assertThat(ChannelImpl.normalizeDisplayNumber(displayNumber)).isEqualTo(normalized); } private static final class TestChannelComparator extends ChannelImpl.DefaultComparator { public TestChannelComparator(TvInputManagerHelper manager) { super(null, manager); } @Override public String getInputLabelForChannel(Channel channel) { return channel.getInputId(); } } private static final class ChannelComparatorWithDescriptionAsLabel extends ChannelImpl.DefaultComparator { public ChannelComparatorWithDescriptionAsLabel(TvInputManagerHelper manager) { super(null, manager); } @Override public String getInputLabelForChannel(Channel channel) { return channel.getDescription(); } } }