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