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