1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.data; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ActivityInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import androidx.test.filters.SmallTest; 29 import androidx.test.runner.AndroidJUnit4; 30 import com.android.tv.data.api.Channel; 31 import com.android.tv.testing.ComparatorTester; 32 import com.android.tv.util.TvInputManagerHelper; 33 import java.util.Comparator; 34 import org.junit.Before; 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 import org.mockito.Matchers; 38 import org.mockito.Mockito; 39 import org.mockito.invocation.InvocationOnMock; 40 import org.mockito.stubbing.Answer; 41 42 /** Tests for {@link ChannelImpl}. */ 43 @SmallTest 44 @RunWith(AndroidJUnit4.class) 45 public class ChannelImplTest { 46 // Used for testing TV inputs with invalid input package. This could happen when a TV input is 47 // uninstalled while drawing an app link card. 48 private static final String INVALID_TV_INPUT_PACKAGE_NAME = "com.android.tv.invalid_tv_input"; 49 // Used for testing TV inputs defined inside of Live TV. 50 private static final String LIVE_CHANNELS_PACKAGE_NAME = "com.android.tv"; 51 // Used for testing a TV input which doesn't have its leanback launcher activity. 52 private static final String NONE_LEANBACK_TV_INPUT_PACKAGE_NAME = 53 "com.android.tv.none_leanback_tv_input"; 54 // Used for testing a TV input which has its leanback launcher activity. 55 private static final String LEANBACK_TV_INPUT_PACKAGE_NAME = "com.android.tv.leanback_tv_input"; 56 private static final String TEST_APP_LINK_TEXT = "test_app_link_text"; 57 private static final String PARTNER_INPUT_ID = "partner"; 58 private static final ActivityInfo TEST_ACTIVITY_INFO = new ActivityInfo(); 59 60 private Context mMockContext; 61 private Intent mInvalidIntent; 62 private Intent mValidIntent; 63 64 @Before setUp()65 public void setUp() throws NameNotFoundException { 66 mInvalidIntent = new Intent(Intent.ACTION_VIEW); 67 mInvalidIntent.setComponent(new ComponentName(INVALID_TV_INPUT_PACKAGE_NAME, ".test")); 68 mValidIntent = new Intent(Intent.ACTION_VIEW); 69 mValidIntent.setComponent(new ComponentName(LEANBACK_TV_INPUT_PACKAGE_NAME, ".test")); 70 Intent liveChannelsIntent = new Intent(Intent.ACTION_VIEW); 71 liveChannelsIntent.setComponent( 72 new ComponentName(LIVE_CHANNELS_PACKAGE_NAME, ".MainActivity")); 73 Intent leanbackTvInputIntent = new Intent(Intent.ACTION_VIEW); 74 leanbackTvInputIntent.setComponent( 75 new ComponentName(LEANBACK_TV_INPUT_PACKAGE_NAME, ".test")); 76 77 PackageManager mockPackageManager = Mockito.mock(PackageManager.class); 78 Mockito.when( 79 mockPackageManager.getLeanbackLaunchIntentForPackage( 80 INVALID_TV_INPUT_PACKAGE_NAME)) 81 .thenReturn(null); 82 Mockito.when( 83 mockPackageManager.getLeanbackLaunchIntentForPackage( 84 LIVE_CHANNELS_PACKAGE_NAME)) 85 .thenReturn(liveChannelsIntent); 86 Mockito.when( 87 mockPackageManager.getLeanbackLaunchIntentForPackage( 88 NONE_LEANBACK_TV_INPUT_PACKAGE_NAME)) 89 .thenReturn(null); 90 Mockito.when( 91 mockPackageManager.getLeanbackLaunchIntentForPackage( 92 LEANBACK_TV_INPUT_PACKAGE_NAME)) 93 .thenReturn(leanbackTvInputIntent); 94 95 // Channel.getAppLinkIntent() calls initAppLinkTypeAndIntent() which calls 96 // Intent.resolveActivityInfo() which calls PackageManager.getActivityInfo(). 97 Mockito.doAnswer( 98 new Answer<ActivityInfo>() { 99 @Override 100 public ActivityInfo answer(InvocationOnMock invocation) { 101 // We only check the package name, since the class name can be 102 // changed 103 // when an intent is changed to an uri and created from the uri. 104 // (ex, ".className" -> "packageName.className") 105 return mValidIntent 106 .getComponent() 107 .getPackageName() 108 .equals( 109 ((ComponentName) 110 invocation 111 .getArguments()[0]) 112 .getPackageName()) 113 ? TEST_ACTIVITY_INFO 114 : null; 115 } 116 }) 117 .when(mockPackageManager) 118 .getActivityInfo(Mockito.<ComponentName>any(), Mockito.anyInt()); 119 120 mMockContext = Mockito.mock(Context.class); 121 Mockito.when(mMockContext.getApplicationContext()).thenReturn(mMockContext); 122 Mockito.when(mMockContext.getPackageName()).thenReturn(LIVE_CHANNELS_PACKAGE_NAME); 123 Mockito.when(mMockContext.getPackageManager()).thenReturn(mockPackageManager); 124 } 125 126 @Test testGetAppLinkType_NoText_NoIntent()127 public void testGetAppLinkType_NoText_NoIntent() { 128 assertAppLinkType(Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, null); 129 assertAppLinkType(Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, null); 130 assertAppLinkType( 131 Channel.APP_LINK_TYPE_NONE, NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, null, null); 132 assertAppLinkType(Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, null); 133 } 134 135 @Test testGetAppLinkType_NoText_InvalidIntent()136 public void testGetAppLinkType_NoText_InvalidIntent() { 137 assertAppLinkType( 138 Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, mInvalidIntent); 139 assertAppLinkType( 140 Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, mInvalidIntent); 141 assertAppLinkType( 142 Channel.APP_LINK_TYPE_NONE, 143 NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, 144 null, 145 mInvalidIntent); 146 assertAppLinkType( 147 Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, mInvalidIntent); 148 } 149 150 @Test testGetAppLinkType_NoText_ValidIntent()151 public void testGetAppLinkType_NoText_ValidIntent() { 152 assertAppLinkType( 153 Channel.APP_LINK_TYPE_NONE, INVALID_TV_INPUT_PACKAGE_NAME, null, mValidIntent); 154 assertAppLinkType( 155 Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, null, mValidIntent); 156 assertAppLinkType( 157 Channel.APP_LINK_TYPE_NONE, 158 NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, 159 null, 160 mValidIntent); 161 assertAppLinkType( 162 Channel.APP_LINK_TYPE_APP, LEANBACK_TV_INPUT_PACKAGE_NAME, null, mValidIntent); 163 } 164 165 @Test testGetAppLinkType_HasText_NoIntent()166 public void testGetAppLinkType_HasText_NoIntent() { 167 assertAppLinkType( 168 Channel.APP_LINK_TYPE_NONE, 169 INVALID_TV_INPUT_PACKAGE_NAME, 170 TEST_APP_LINK_TEXT, 171 null); 172 assertAppLinkType( 173 Channel.APP_LINK_TYPE_NONE, LIVE_CHANNELS_PACKAGE_NAME, TEST_APP_LINK_TEXT, null); 174 assertAppLinkType( 175 Channel.APP_LINK_TYPE_NONE, 176 NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, 177 TEST_APP_LINK_TEXT, 178 null); 179 assertAppLinkType( 180 Channel.APP_LINK_TYPE_APP, 181 LEANBACK_TV_INPUT_PACKAGE_NAME, 182 TEST_APP_LINK_TEXT, 183 null); 184 } 185 186 @Test testGetAppLinkType_HasText_InvalidIntent()187 public void testGetAppLinkType_HasText_InvalidIntent() { 188 assertAppLinkType( 189 Channel.APP_LINK_TYPE_NONE, 190 INVALID_TV_INPUT_PACKAGE_NAME, 191 TEST_APP_LINK_TEXT, 192 mInvalidIntent); 193 assertAppLinkType( 194 Channel.APP_LINK_TYPE_NONE, 195 LIVE_CHANNELS_PACKAGE_NAME, 196 TEST_APP_LINK_TEXT, 197 mInvalidIntent); 198 assertAppLinkType( 199 Channel.APP_LINK_TYPE_NONE, 200 NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, 201 TEST_APP_LINK_TEXT, 202 mInvalidIntent); 203 assertAppLinkType( 204 Channel.APP_LINK_TYPE_APP, 205 LEANBACK_TV_INPUT_PACKAGE_NAME, 206 TEST_APP_LINK_TEXT, 207 mInvalidIntent); 208 } 209 210 @Test testGetAppLinkType_HasText_ValidIntent()211 public void testGetAppLinkType_HasText_ValidIntent() { 212 assertAppLinkType( 213 Channel.APP_LINK_TYPE_CHANNEL, 214 INVALID_TV_INPUT_PACKAGE_NAME, 215 TEST_APP_LINK_TEXT, 216 mValidIntent); 217 assertAppLinkType( 218 Channel.APP_LINK_TYPE_CHANNEL, 219 LIVE_CHANNELS_PACKAGE_NAME, 220 TEST_APP_LINK_TEXT, 221 mValidIntent); 222 assertAppLinkType( 223 Channel.APP_LINK_TYPE_CHANNEL, 224 NONE_LEANBACK_TV_INPUT_PACKAGE_NAME, 225 TEST_APP_LINK_TEXT, 226 mValidIntent); 227 assertAppLinkType( 228 Channel.APP_LINK_TYPE_CHANNEL, 229 LEANBACK_TV_INPUT_PACKAGE_NAME, 230 TEST_APP_LINK_TEXT, 231 mValidIntent); 232 } 233 assertAppLinkType( int expectedType, String inputPackageName, String appLinkText, Intent appLinkIntent)234 private void assertAppLinkType( 235 int expectedType, String inputPackageName, String appLinkText, Intent appLinkIntent) { 236 // In ChannelImpl, Intent.URI_INTENT_SCHEME is used to parse the URI. So the same flag 237 // should be 238 // used when the URI is created. 239 ChannelImpl testChannel = 240 new ChannelImpl.Builder() 241 .setPackageName(inputPackageName) 242 .setAppLinkText(appLinkText) 243 .setAppLinkIntentUri( 244 appLinkIntent == null 245 ? null 246 : appLinkIntent.toUri(Intent.URI_INTENT_SCHEME)) 247 .build(); 248 assertWithMessage("Unexpected app-link type for for " + testChannel) 249 .that(testChannel.getAppLinkType(mMockContext)) 250 .isEqualTo(expectedType); 251 } 252 253 @Test testComparator()254 public void testComparator() { 255 TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); 256 Mockito.when(manager.isPartnerInput(Matchers.anyString())) 257 .thenAnswer( 258 new Answer<Boolean>() { 259 @Override 260 public Boolean answer(InvocationOnMock invocation) throws Throwable { 261 String inputId = (String) invocation.getArguments()[0]; 262 return PARTNER_INPUT_ID.equals(inputId); 263 } 264 }); 265 Comparator<Channel> comparator = new TestChannelComparator(manager); 266 ComparatorTester<Channel> comparatorTester = ComparatorTester.withoutEqualsTest(comparator); 267 comparatorTester.addComparableGroup( 268 new ChannelImpl.Builder().setInputId(PARTNER_INPUT_ID).build()); 269 comparatorTester.addComparableGroup(new ChannelImpl.Builder().setInputId("1").build()); 270 comparatorTester.addComparableGroup( 271 new ChannelImpl.Builder().setInputId("1").setDisplayNumber("2").build()); 272 comparatorTester.addComparableGroup( 273 new ChannelImpl.Builder().setInputId("2").setDisplayNumber("1.0").build()); 274 275 // display name does not affect comparator 276 comparatorTester.addComparableGroup( 277 new ChannelImpl.Builder() 278 .setInputId("2") 279 .setDisplayNumber("1.62") 280 .setDisplayName("test1") 281 .build(), 282 new ChannelImpl.Builder() 283 .setInputId("2") 284 .setDisplayNumber("1.62") 285 .setDisplayName("test2") 286 .build(), 287 new ChannelImpl.Builder() 288 .setInputId("2") 289 .setDisplayNumber("1.62") 290 .setDisplayName("test3") 291 .build()); 292 comparatorTester.addComparableGroup( 293 new ChannelImpl.Builder().setInputId("2").setDisplayNumber("2.0").build()); 294 // Numeric display number sorting 295 comparatorTester.addComparableGroup( 296 new ChannelImpl.Builder().setInputId("2").setDisplayNumber("12.2").build()); 297 comparatorTester.test(); 298 } 299 300 /** 301 * Test Input Label handled by {@link ChannelImpl.DefaultComparator}. 302 * 303 * <p>Sort partner inputs first, then sort by input label, then by input id. See <a 304 * href="http://b/23031603">b/23031603</a>. 305 */ 306 @Test testComparatorLabel()307 public void testComparatorLabel() { 308 TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class); 309 Mockito.when(manager.isPartnerInput(Matchers.anyString())) 310 .thenAnswer( 311 new Answer<Boolean>() { 312 @Override 313 public Boolean answer(InvocationOnMock invocation) throws Throwable { 314 String inputId = (String) invocation.getArguments()[0]; 315 return PARTNER_INPUT_ID.equals(inputId); 316 } 317 }); 318 Comparator<Channel> comparator = new ChannelComparatorWithDescriptionAsLabel(manager); 319 ComparatorTester<Channel> comparatorTester = ComparatorTester.withoutEqualsTest(comparator); 320 321 comparatorTester.addComparableGroup( 322 new ChannelImpl.Builder().setInputId(PARTNER_INPUT_ID).setDescription("A").build()); 323 324 // The description is used as a label for this test. 325 comparatorTester.addComparableGroup( 326 new ChannelImpl.Builder().setDescription("A").setInputId("1").build()); 327 comparatorTester.addComparableGroup( 328 new ChannelImpl.Builder().setDescription("A").setInputId("2").build()); 329 comparatorTester.addComparableGroup( 330 new ChannelImpl.Builder().setDescription("B").setInputId("1").build()); 331 332 comparatorTester.test(); 333 } 334 335 @Test testNormalizeChannelNumber()336 public void testNormalizeChannelNumber() { 337 assertNormalizedDisplayNumber(null, null); 338 assertNormalizedDisplayNumber("", ""); 339 assertNormalizedDisplayNumber("1", "1"); 340 assertNormalizedDisplayNumber("abcde", "abcde"); 341 assertNormalizedDisplayNumber("1-1", "1-1"); 342 assertNormalizedDisplayNumber("1.1", "1-1"); 343 assertNormalizedDisplayNumber("1 1", "1-1"); 344 assertNormalizedDisplayNumber("1\u058a1", "1-1"); 345 assertNormalizedDisplayNumber("1\u05be1", "1-1"); 346 assertNormalizedDisplayNumber("1\u14001", "1-1"); 347 assertNormalizedDisplayNumber("1\u18061", "1-1"); 348 assertNormalizedDisplayNumber("1\u20101", "1-1"); 349 assertNormalizedDisplayNumber("1\u20111", "1-1"); 350 assertNormalizedDisplayNumber("1\u20121", "1-1"); 351 assertNormalizedDisplayNumber("1\u20131", "1-1"); 352 assertNormalizedDisplayNumber("1\u20141", "1-1"); 353 } 354 assertNormalizedDisplayNumber(String displayNumber, String normalized)355 private void assertNormalizedDisplayNumber(String displayNumber, String normalized) { 356 assertThat(ChannelImpl.normalizeDisplayNumber(displayNumber)).isEqualTo(normalized); 357 } 358 359 private static final class TestChannelComparator extends ChannelImpl.DefaultComparator { TestChannelComparator(TvInputManagerHelper manager)360 public TestChannelComparator(TvInputManagerHelper manager) { 361 super(null, manager); 362 } 363 364 @Override getInputLabelForChannel(Channel channel)365 public String getInputLabelForChannel(Channel channel) { 366 return channel.getInputId(); 367 } 368 } 369 370 private static final class ChannelComparatorWithDescriptionAsLabel 371 extends ChannelImpl.DefaultComparator { ChannelComparatorWithDescriptionAsLabel(TvInputManagerHelper manager)372 public ChannelComparatorWithDescriptionAsLabel(TvInputManagerHelper manager) { 373 super(null, manager); 374 } 375 376 @Override getInputLabelForChannel(Channel channel)377 public String getInputLabelForChannel(Channel channel) { 378 return channel.getDescription(); 379 } 380 } 381 } 382