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