1 /*
2  * Copyright (C) 2017 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;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.robolectric.Shadows.shadowOf;
22 
23 import android.app.Activity;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.ServiceInfo;
29 import android.media.tv.TvInputInfo;
30 import android.os.Build.VERSION;
31 import android.os.Build.VERSION_CODES;
32 import android.support.annotation.Nullable;
33 
34 import androidx.test.core.app.ApplicationProvider;
35 
36 import com.android.tv.common.CommonConstants;
37 import com.android.tv.common.dagger.ApplicationModule;
38 import com.android.tv.common.flags.impl.DefaultLegacyFlags;
39 import com.android.tv.common.flags.impl.SettableFlagsModule;
40 import com.android.tv.common.flags.proto.TypedFeatures.StringListParam;
41 import com.android.tv.common.util.CommonUtils;
42 import com.android.tv.data.ChannelDataManager;
43 import com.android.tv.data.epg.EpgFetcher;
44 import com.android.tv.features.TvFeatures;
45 import com.android.tv.modules.TvSingletonsModule;
46 import com.android.tv.testing.FakeTvInputManager;
47 import com.android.tv.testing.FakeTvInputManagerHelper;
48 import com.android.tv.testing.TestSingletonApp;
49 import com.android.tv.testing.constants.ConfigConstants;
50 import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
51 import com.android.tv.util.AsyncDbTask.DbExecutor;
52 import com.android.tv.util.SetupUtils;
53 import com.android.tv.util.TvInputManagerHelper;
54 
55 import com.google.android.tv.partner.support.EpgContract;
56 import com.google.common.base.Optional;
57 
58 import dagger.Component;
59 import dagger.Module;
60 import dagger.Provides;
61 import dagger.android.AndroidInjectionModule;
62 import dagger.android.AndroidInjector;
63 import dagger.android.DispatchingAndroidInjector;
64 import dagger.android.HasAndroidInjector;
65 
66 import org.junit.After;
67 import org.junit.Before;
68 import org.junit.Test;
69 import org.junit.runner.RunWith;
70 import org.mockito.ArgumentMatchers;
71 import org.mockito.Mockito;
72 import org.robolectric.Robolectric;
73 import org.robolectric.RobolectricTestRunner;
74 import org.robolectric.android.controller.ActivityController;
75 import org.robolectric.android.util.concurrent.RoboExecutorService;
76 import org.robolectric.annotation.Config;
77 import org.robolectric.shadows.ShadowActivity;
78 
79 import java.util.concurrent.Executor;
80 
81 import javax.inject.Inject;
82 import javax.inject.Singleton;
83 
84 /** Tests for {@link SetupPassthroughActivity}. */
85 @RunWith(RobolectricTestRunner.class)
86 @Config(sdk = ConfigConstants.SDK, application = SetupPassthroughActivityTest.MyTestApp.class)
87 public class SetupPassthroughActivityTest {
88 
89     private static final int REQUEST_START_SETUP_ACTIVITY = 200;
90 
91     private MyTestApp testSingletonApp;
92 
93     private TvInputInfo testInput;
94 
95     @Before
setup()96     public void setup() {
97         testInput = createMockInput("com.example/.Input");
98         testSingletonApp = (MyTestApp) ApplicationProvider.getApplicationContext();
99         testSingletonApp.flagsModule.legacyFlags =
100                 DefaultLegacyFlags.builder().enableQaFeatures(true).build();
101         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager();
102     }
103 
104     @After
after()105     public void after() {
106         TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.resetForTests();
107     }
108 
109     @Test
create_emptyIntent()110     public void create_emptyIntent() {
111         SetupPassthroughActivity activity =
112                 Robolectric.buildActivity(SetupPassthroughActivity.class, new Intent())
113                         .create()
114                         .get();
115         ShadowActivity.IntentForResult shadowIntent =
116                 shadowOf(activity).getNextStartedActivityForResult();
117         // Since there is no inputs, the next activity should not be started.
118         assertThat(shadowIntent).isNull();
119         assertThat(activity.isFinishing()).isTrue();
120     }
121 
122     @Test
create_noInputs()123     public void create_noInputs() {
124         SetupPassthroughActivity activity = createSetupActivityFor("com.example/.Input");
125         ShadowActivity.IntentForResult shadowIntent =
126                 shadowOf(activity).getNextStartedActivityForResult();
127         // Since there is no inputs, the next activity should not be started.
128         assertThat(shadowIntent).isNull();
129         assertThat(activity.isFinishing()).isTrue();
130     }
131 
132     @Test
create_inputNotFound()133     public void create_inputNotFound() {
134         testSingletonApp.tvInputManagerHelper = new FakeTvInputManagerHelper(testSingletonApp);
135         testSingletonApp.tvInputManagerHelper.start();
136         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
137         SetupPassthroughActivity activity = createSetupActivityFor("com.example/.Other");
138         ShadowActivity.IntentForResult shadowIntent =
139                 shadowOf(activity).getNextStartedActivityForResult();
140         // Since the input is not found, the next activity should not be started.
141         assertThat(shadowIntent).isNull();
142         assertThat(activity.isFinishing()).isTrue();
143     }
144 
145     @Test
create_validInput()146     public void create_validInput() {
147         testSingletonApp.tvInputManagerHelper.start();
148         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
149         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
150 
151         ShadowActivity.IntentForResult shadowIntent =
152                 shadowOf(activity).getNextStartedActivityForResult();
153         assertThat(shadowIntent).isNotNull();
154         assertThat(shadowIntent.options).isNull();
155         assertThat(shadowIntent.intent.getExtras()).isNotNull();
156         assertThat(shadowIntent.requestCode).isEqualTo(REQUEST_START_SETUP_ACTIVITY);
157         assertThat(activity.isFinishing()).isFalse();
158     }
159 
160     @Test
create_trustedCallingPackage()161     public void create_trustedCallingPackage() {
162         testSingletonApp.tvInputManagerHelper.start();
163         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
164 
165         ActivityController<SetupPassthroughActivity> activityController =
166                 Robolectric.buildActivity(
167                         SetupPassthroughActivity.class,
168                         CommonUtils.createSetupIntent(new Intent(), testInput.getId()));
169         SetupPassthroughActivity activity = activityController.get();
170         ShadowActivity shadowActivity = shadowOf(activity);
171         shadowActivity.setCallingActivity(createTrustedComponent());
172         activityController.create();
173 
174         ShadowActivity.IntentForResult shadowIntent =
175                 shadowActivity.getNextStartedActivityForResult();
176         assertThat(shadowIntent).isNotNull();
177         assertThat(shadowIntent.options).isNull();
178         assertThat(shadowIntent.intent.getExtras()).isNotNull();
179         assertThat(shadowIntent.requestCode).isEqualTo(REQUEST_START_SETUP_ACTIVITY);
180         assertThat(activity.isFinishing()).isFalse();
181     }
182 
183     @Test
create_nonTrustedCallingPackage()184     public void create_nonTrustedCallingPackage() {
185         testSingletonApp.tvInputManagerHelper.start();
186         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
187 
188         ActivityController<SetupPassthroughActivity> activityController =
189                 Robolectric.buildActivity(
190                         SetupPassthroughActivity.class,
191                         CommonUtils.createSetupIntent(new Intent(), testInput.getId()));
192         SetupPassthroughActivity activity = activityController.get();
193         ShadowActivity shadowActivity = shadowOf(activity);
194         shadowActivity.setCallingActivity(
195                 new ComponentName("com.notTrusted", "com.notTrusted.MyClass"));
196         activityController.create();
197 
198         ShadowActivity.IntentForResult shadowIntent =
199                 shadowActivity.getNextStartedActivityForResult();
200         // Since the calling activity is not trusted, the next activity should not be started.
201         assertThat(shadowIntent).isNull();
202         assertThat(activity.isFinishing()).isTrue();
203     }
204 
205     @Test
create_nullCallingPackage()206     public void create_nullCallingPackage() {
207         testSingletonApp.tvInputManagerHelper.start();
208         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
209 
210         ActivityController<SetupPassthroughActivity> activityController =
211                 Robolectric.buildActivity(
212                         SetupPassthroughActivity.class,
213                         CommonUtils.createSetupIntent(new Intent(), testInput.getId()));
214         SetupPassthroughActivity activity = activityController.get();
215         ShadowActivity shadowActivity = shadowOf(activity);
216         shadowActivity.setCallingActivity(null);
217         activityController.create();
218 
219         ShadowActivity.IntentForResult shadowIntent =
220                 shadowActivity.getNextStartedActivityForResult();
221         // Since the calling activity is null, the next activity should not be started.
222         assertThat(shadowIntent).isNull();
223         assertThat(activity.isFinishing()).isTrue();
224     }
225 
226     @Test
onActivityResult_canceled()227     public void onActivityResult_canceled() {
228         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
229         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
230 
231         activity.onActivityResult(0, Activity.RESULT_CANCELED, null);
232         assertThat(activity.isFinishing()).isTrue();
233         assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_CANCELED);
234     }
235 
236     @Test
onActivityResult_ok()237     public void onActivityResult_ok() {
238         TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
239         testSingletonApp.setupUtils = setupUtils;
240         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
241         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
242         activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, null);
243 
244         assertThat(testSingletonApp.epgFetcher.fetchStarted).isFalse();
245         assertThat(setupUtils.finishedId).isEqualTo("com.example/.Input");
246         assertThat(activity.isFinishing()).isFalse();
247 
248         setupUtils.finishedRunnable.run();
249         assertThat(activity.isFinishing()).isTrue();
250         assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK);
251     }
252 
253     @Test
onActivityResult_3rdPartyEpg_ok()254     public void onActivityResult_3rdPartyEpg_ok() {
255         TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest();
256         TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
257         testSingletonApp.setupUtils = setupUtils;
258         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
259         testSingletonApp
260                 .getCloudEpgFlags()
261                 .setThirdPartyEpgInput(
262                         StringListParam.newBuilder().addElement(testInput.getId()).build());
263         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
264         Intent data = new Intent();
265         data.putExtra(EpgContract.EXTRA_USE_CLOUD_EPG, true);
266         data.putExtra(TvInputInfo.EXTRA_INPUT_ID, testInput.getId());
267         activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, data);
268 
269         assertThat(testSingletonApp.epgFetcher.fetchStarted).isTrue();
270         assertThat(setupUtils.finishedId).isEqualTo("com.example/.Input");
271         assertThat(activity.isFinishing()).isFalse();
272 
273         setupUtils.finishedRunnable.run();
274         assertThat(activity.isFinishing()).isTrue();
275         assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK);
276     }
277 
278     @Test
onActivityResult_3rdPartyEpg_notAllowed()279     public void onActivityResult_3rdPartyEpg_notAllowed() {
280         TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest();
281         TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
282         testSingletonApp.setupUtils = setupUtils;
283         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
284         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
285         Intent data = new Intent();
286         data.putExtra(EpgContract.EXTRA_USE_CLOUD_EPG, true);
287         data.putExtra(TvInputInfo.EXTRA_INPUT_ID, testInput.getId());
288         activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, data);
289 
290         assertThat(testSingletonApp.epgFetcher.fetchStarted).isFalse();
291         assertThat(setupUtils.finishedId).isEqualTo("com.example/.Input");
292         assertThat(activity.isFinishing()).isFalse();
293 
294         setupUtils.finishedRunnable.run();
295         assertThat(activity.isFinishing()).isTrue();
296         assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK);
297     }
298 
299     @Test
onActivityResult_3rdPartyEpg_disabled()300     public void onActivityResult_3rdPartyEpg_disabled() {
301         TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.disableForTests();
302         TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
303         testSingletonApp.setupUtils = setupUtils;
304         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
305         testSingletonApp
306                 .getCloudEpgFlags()
307                 .setThirdPartyEpgInput(
308                         StringListParam.newBuilder().addElement(testInput.getId()).build());
309         testSingletonApp.dbExecutor = new RoboExecutorService();
310         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
311         Intent data = new Intent();
312         data.putExtra(EpgContract.EXTRA_USE_CLOUD_EPG, true);
313         data.putExtra(TvInputInfo.EXTRA_INPUT_ID, testInput.getId());
314         activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, data);
315 
316         assertThat(testSingletonApp.epgFetcher.fetchStarted).isFalse();
317         assertThat(setupUtils.finishedId).isEqualTo("com.example/.Input");
318         assertThat(activity.isFinishing()).isFalse();
319 
320         setupUtils.finishedRunnable.run();
321         assertThat(activity.isFinishing()).isTrue();
322         assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK);
323     }
324 
325     @Test
onActivityResult_ok_tvInputInfo_null()326     public void onActivityResult_ok_tvInputInfo_null() {
327         TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
328         testSingletonApp.setupUtils = setupUtils;
329         FakeTvInputManager tvInputManager =
330                 testSingletonApp.tvInputManagerHelper.getFakeTvInputManager();
331         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
332         activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, null);
333 
334         assertThat(tvInputManager.getTvInputInfo(testInput.getId())).isEqualTo(null);
335         assertThat(testSingletonApp.epgFetcher.fetchStarted).isFalse();
336         assertThat(activity.isFinishing()).isTrue();
337 
338         assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK);
339     }
340 
createSetupActivityFor(String inputId)341     private SetupPassthroughActivity createSetupActivityFor(String inputId) {
342         ActivityController<SetupPassthroughActivity> activityController =
343                 Robolectric.buildActivity(
344                         SetupPassthroughActivity.class,
345                         CommonUtils.createSetupIntent(new Intent(), inputId));
346         SetupPassthroughActivity activity = activityController.get();
347         ShadowActivity shadowActivity = shadowOf(activity);
348         shadowActivity.setCallingActivity(createTrustedComponent());
349         activityController.create();
350         return activity;
351     }
352 
createMockInput(String inputId)353     private TvInputInfo createMockInput(String inputId) {
354         TvInputInfo tvInputInfo = Mockito.mock(TvInputInfo.class);
355         ServiceInfo serviceInfo = new ServiceInfo();
356         ApplicationInfo applicationInfo = new ApplicationInfo();
357         Mockito.when(tvInputInfo.getId()).thenReturn(inputId);
358         serviceInfo.packageName = inputId.substring(0, inputId.indexOf('/'));
359         serviceInfo.applicationInfo = applicationInfo;
360         applicationInfo.flags = 0;
361         Mockito.when(tvInputInfo.getServiceInfo()).thenReturn(serviceInfo);
362         Mockito.when(tvInputInfo.loadLabel(ArgumentMatchers.any())).thenReturn("testLabel");
363         if (VERSION.SDK_INT >= VERSION_CODES.N) {
364             Mockito.when(tvInputInfo.loadCustomLabel(ArgumentMatchers.any()))
365                     .thenReturn("testCustomLabel");
366         }
367         return tvInputInfo;
368     }
369 
createTrustedComponent()370     private static ComponentName createTrustedComponent() {
371         return new ComponentName(CommonConstants.BASE_PACKAGE, "com.example.MyClass");
372     }
373 
374     /**
375      * Test SetupUtils.
376      *
377      * <p>SetupUtils has lots of DB and threading interactions, that make it hard to test. This
378      * bypasses all of that.
379      */
380     private static class TestSetupUtils extends SetupUtils {
381 
382         public String finishedId;
383         public Runnable finishedRunnable;
384 
TestSetupUtils(Context context)385         private TestSetupUtils(Context context) {
386             super(context, Optional.absent());
387         }
388 
389         @Override
onTvInputSetupFinished(String inputId, @Nullable Runnable postRunnable)390         public void onTvInputSetupFinished(String inputId, @Nullable Runnable postRunnable) {
391             finishedId = inputId;
392             finishedRunnable = postRunnable;
393         }
394     }
395 
396     /** Test app for {@link SetupPassthroughActivityTest} */
397     public static class MyTestApp extends TestSingletonApp implements HasAndroidInjector {
398 
399         @Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector;
400 
401         @Override
onCreate()402         public void onCreate() {
403 
404             super.onCreate();
405             // Inject afterwards so we can use objects created in super.
406             // Note TestSingletonApp does not do injection so it is safe
407             applicationInjector().inject(this);
408         }
409 
410         @Override
androidInjector()411         public AndroidInjector<Object> androidInjector() {
412             return dispatchingAndroidInjector;
413         }
414 
applicationInjector()415         protected AndroidInjector<MyTestApp> applicationInjector() {
416 
417             return DaggerSetupPassthroughActivityTest_TestComponent.builder()
418                     .applicationModule(new ApplicationModule(this))
419                     .tvSingletonsModule(new TvSingletonsModule(this))
420                     .testModule(new TestModule(this))
421                     .settableFlagsModule(flagsModule)
422                     .build();
423         }
424     }
425 
426     /** Dagger component for {@link SetupPassthroughActivityTest}. */
427     @Singleton
428     @Component(
429             modules = {
430                 AndroidInjectionModule.class,
431                 TestModule.class,
432             })
433     interface TestComponent extends AndroidInjector<MyTestApp> {}
434 
435     @Module(
436             includes = {
437                 SetupPassthroughActivity.Module.class,
438                 ApplicationModule.class,
439                 TvSingletonsModule.class,
440                 SettableFlagsModule.class,
441             })
442     /** Module for {@link MyTestApp} */
443     static class TestModule {
444 
445         private final MyTestApp myTestApp;
446 
TestModule(MyTestApp test)447         TestModule(MyTestApp test) {
448             myTestApp = test;
449         }
450 
451         @Provides
providesBuiltInTunerManager()452         Optional<BuiltInTunerManager> providesBuiltInTunerManager() {
453             return Optional.absent();
454         }
455 
456         @Provides
providesTvInputManagerHelper()457         TvInputManagerHelper providesTvInputManagerHelper() {
458             return myTestApp.tvInputManagerHelper;
459         }
460 
461         @Provides
providesTestSetupUtils()462         SetupUtils providesTestSetupUtils() {
463             return myTestApp.setupUtils;
464         }
465 
466         @Provides
467         @DbExecutor
providesDbExecutor()468         Executor providesDbExecutor() {
469             return myTestApp.dbExecutor;
470         }
471 
472         @Provides
providesChannelDataManager()473         ChannelDataManager providesChannelDataManager() {
474             return myTestApp.getChannelDataManager();
475         }
476 
477         @Provides
providesEpgFetcher()478         EpgFetcher providesEpgFetcher() {
479             return myTestApp.epgFetcher;
480         }
481     }
482 }
483