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