/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.tv; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Shadows.shadowOf; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; import android.media.tv.TvInputInfo; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.support.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import com.android.tv.common.CommonConstants; import com.android.tv.common.dagger.ApplicationModule; import com.android.tv.common.flags.impl.DefaultLegacyFlags; import com.android.tv.common.flags.impl.SettableFlagsModule; import com.android.tv.common.flags.proto.TypedFeatures.StringListParam; import com.android.tv.common.util.CommonUtils; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.epg.EpgFetcher; import com.android.tv.features.TvFeatures; import com.android.tv.modules.TvSingletonsModule; import com.android.tv.testing.FakeTvInputManager; import com.android.tv.testing.FakeTvInputManagerHelper; import com.android.tv.testing.TestSingletonApp; import com.android.tv.testing.constants.ConfigConstants; import com.android.tv.tunerinputcontroller.BuiltInTunerManager; import com.android.tv.util.AsyncDbTask.DbExecutor; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; import com.google.android.tv.partner.support.EpgContract; import com.google.common.base.Optional; import dagger.Component; import dagger.Module; import dagger.Provides; import dagger.android.AndroidInjectionModule; import dagger.android.AndroidInjector; import dagger.android.DispatchingAndroidInjector; import dagger.android.HasAndroidInjector; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.android.controller.ActivityController; import org.robolectric.android.util.concurrent.RoboExecutorService; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowActivity; import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Singleton; /** Tests for {@link SetupPassthroughActivity}. */ @RunWith(RobolectricTestRunner.class) @Config(sdk = ConfigConstants.SDK, application = SetupPassthroughActivityTest.MyTestApp.class) public class SetupPassthroughActivityTest { private static final int REQUEST_START_SETUP_ACTIVITY = 200; private MyTestApp testSingletonApp; private TvInputInfo testInput; @Before public void setup() { testInput = createMockInput("com.example/.Input"); testSingletonApp = (MyTestApp) ApplicationProvider.getApplicationContext(); testSingletonApp.flagsModule.legacyFlags = DefaultLegacyFlags.builder().enableQaFeatures(true).build(); testSingletonApp.tvInputManagerHelper.getFakeTvInputManager(); } @After public void after() { TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.resetForTests(); } @Test public void create_emptyIntent() { SetupPassthroughActivity activity = Robolectric.buildActivity(SetupPassthroughActivity.class, new Intent()) .create() .get(); ShadowActivity.IntentForResult shadowIntent = shadowOf(activity).getNextStartedActivityForResult(); // Since there is no inputs, the next activity should not be started. assertThat(shadowIntent).isNull(); assertThat(activity.isFinishing()).isTrue(); } @Test public void create_noInputs() { SetupPassthroughActivity activity = createSetupActivityFor("com.example/.Input"); ShadowActivity.IntentForResult shadowIntent = shadowOf(activity).getNextStartedActivityForResult(); // Since there is no inputs, the next activity should not be started. assertThat(shadowIntent).isNull(); assertThat(activity.isFinishing()).isTrue(); } @Test public void create_inputNotFound() { testSingletonApp.tvInputManagerHelper = new FakeTvInputManagerHelper(testSingletonApp); testSingletonApp.tvInputManagerHelper.start(); testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); SetupPassthroughActivity activity = createSetupActivityFor("com.example/.Other"); ShadowActivity.IntentForResult shadowIntent = shadowOf(activity).getNextStartedActivityForResult(); // Since the input is not found, the next activity should not be started. assertThat(shadowIntent).isNull(); assertThat(activity.isFinishing()).isTrue(); } @Test public void create_validInput() { testSingletonApp.tvInputManagerHelper.start(); testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId()); ShadowActivity.IntentForResult shadowIntent = shadowOf(activity).getNextStartedActivityForResult(); assertThat(shadowIntent).isNotNull(); assertThat(shadowIntent.options).isNull(); assertThat(shadowIntent.intent.getExtras()).isNotNull(); assertThat(shadowIntent.requestCode).isEqualTo(REQUEST_START_SETUP_ACTIVITY); assertThat(activity.isFinishing()).isFalse(); } @Test public void create_trustedCallingPackage() { testSingletonApp.tvInputManagerHelper.start(); testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); ActivityController activityController = Robolectric.buildActivity( SetupPassthroughActivity.class, CommonUtils.createSetupIntent(new Intent(), testInput.getId())); SetupPassthroughActivity activity = activityController.get(); ShadowActivity shadowActivity = shadowOf(activity); shadowActivity.setCallingActivity(createTrustedComponent()); activityController.create(); ShadowActivity.IntentForResult shadowIntent = shadowActivity.getNextStartedActivityForResult(); assertThat(shadowIntent).isNotNull(); assertThat(shadowIntent.options).isNull(); assertThat(shadowIntent.intent.getExtras()).isNotNull(); assertThat(shadowIntent.requestCode).isEqualTo(REQUEST_START_SETUP_ACTIVITY); assertThat(activity.isFinishing()).isFalse(); } @Test public void create_nonTrustedCallingPackage() { testSingletonApp.tvInputManagerHelper.start(); testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); ActivityController activityController = Robolectric.buildActivity( SetupPassthroughActivity.class, CommonUtils.createSetupIntent(new Intent(), testInput.getId())); SetupPassthroughActivity activity = activityController.get(); ShadowActivity shadowActivity = shadowOf(activity); shadowActivity.setCallingActivity( new ComponentName("com.notTrusted", "com.notTrusted.MyClass")); activityController.create(); ShadowActivity.IntentForResult shadowIntent = shadowActivity.getNextStartedActivityForResult(); // Since the calling activity is not trusted, the next activity should not be started. assertThat(shadowIntent).isNull(); assertThat(activity.isFinishing()).isTrue(); } @Test public void create_nullCallingPackage() { testSingletonApp.tvInputManagerHelper.start(); testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); ActivityController activityController = Robolectric.buildActivity( SetupPassthroughActivity.class, CommonUtils.createSetupIntent(new Intent(), testInput.getId())); SetupPassthroughActivity activity = activityController.get(); ShadowActivity shadowActivity = shadowOf(activity); shadowActivity.setCallingActivity(null); activityController.create(); ShadowActivity.IntentForResult shadowIntent = shadowActivity.getNextStartedActivityForResult(); // Since the calling activity is null, the next activity should not be started. assertThat(shadowIntent).isNull(); assertThat(activity.isFinishing()).isTrue(); } @Test public void onActivityResult_canceled() { testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId()); activity.onActivityResult(0, Activity.RESULT_CANCELED, null); assertThat(activity.isFinishing()).isTrue(); assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_CANCELED); } @Test public void onActivityResult_ok() { TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext()); testSingletonApp.setupUtils = setupUtils; testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId()); activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, null); assertThat(testSingletonApp.epgFetcher.fetchStarted).isFalse(); assertThat(setupUtils.finishedId).isEqualTo("com.example/.Input"); assertThat(activity.isFinishing()).isFalse(); setupUtils.finishedRunnable.run(); assertThat(activity.isFinishing()).isTrue(); assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK); } @Test public void onActivityResult_3rdPartyEpg_ok() { TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest(); TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext()); testSingletonApp.setupUtils = setupUtils; testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); testSingletonApp .getCloudEpgFlags() .setThirdPartyEpgInput( StringListParam.newBuilder().addElement(testInput.getId()).build()); SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId()); Intent data = new Intent(); data.putExtra(EpgContract.EXTRA_USE_CLOUD_EPG, true); data.putExtra(TvInputInfo.EXTRA_INPUT_ID, testInput.getId()); activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, data); assertThat(testSingletonApp.epgFetcher.fetchStarted).isTrue(); assertThat(setupUtils.finishedId).isEqualTo("com.example/.Input"); assertThat(activity.isFinishing()).isFalse(); setupUtils.finishedRunnable.run(); assertThat(activity.isFinishing()).isTrue(); assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK); } @Test public void onActivityResult_3rdPartyEpg_notAllowed() { TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest(); TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext()); testSingletonApp.setupUtils = setupUtils; testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId()); Intent data = new Intent(); data.putExtra(EpgContract.EXTRA_USE_CLOUD_EPG, true); data.putExtra(TvInputInfo.EXTRA_INPUT_ID, testInput.getId()); activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, data); assertThat(testSingletonApp.epgFetcher.fetchStarted).isFalse(); assertThat(setupUtils.finishedId).isEqualTo("com.example/.Input"); assertThat(activity.isFinishing()).isFalse(); setupUtils.finishedRunnable.run(); assertThat(activity.isFinishing()).isTrue(); assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK); } @Test public void onActivityResult_3rdPartyEpg_disabled() { TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.disableForTests(); TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext()); testSingletonApp.setupUtils = setupUtils; testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1); testSingletonApp .getCloudEpgFlags() .setThirdPartyEpgInput( StringListParam.newBuilder().addElement(testInput.getId()).build()); testSingletonApp.dbExecutor = new RoboExecutorService(); SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId()); Intent data = new Intent(); data.putExtra(EpgContract.EXTRA_USE_CLOUD_EPG, true); data.putExtra(TvInputInfo.EXTRA_INPUT_ID, testInput.getId()); activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, data); assertThat(testSingletonApp.epgFetcher.fetchStarted).isFalse(); assertThat(setupUtils.finishedId).isEqualTo("com.example/.Input"); assertThat(activity.isFinishing()).isFalse(); setupUtils.finishedRunnable.run(); assertThat(activity.isFinishing()).isTrue(); assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK); } @Test public void onActivityResult_ok_tvInputInfo_null() { TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext()); testSingletonApp.setupUtils = setupUtils; FakeTvInputManager tvInputManager = testSingletonApp.tvInputManagerHelper.getFakeTvInputManager(); SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId()); activity.onActivityResult(REQUEST_START_SETUP_ACTIVITY, Activity.RESULT_OK, null); assertThat(tvInputManager.getTvInputInfo(testInput.getId())).isEqualTo(null); assertThat(testSingletonApp.epgFetcher.fetchStarted).isFalse(); assertThat(activity.isFinishing()).isTrue(); assertThat(shadowOf(activity).getResultCode()).isEqualTo(Activity.RESULT_OK); } private SetupPassthroughActivity createSetupActivityFor(String inputId) { ActivityController activityController = Robolectric.buildActivity( SetupPassthroughActivity.class, CommonUtils.createSetupIntent(new Intent(), inputId)); SetupPassthroughActivity activity = activityController.get(); ShadowActivity shadowActivity = shadowOf(activity); shadowActivity.setCallingActivity(createTrustedComponent()); activityController.create(); return activity; } private TvInputInfo createMockInput(String inputId) { TvInputInfo tvInputInfo = Mockito.mock(TvInputInfo.class); ServiceInfo serviceInfo = new ServiceInfo(); ApplicationInfo applicationInfo = new ApplicationInfo(); Mockito.when(tvInputInfo.getId()).thenReturn(inputId); serviceInfo.packageName = inputId.substring(0, inputId.indexOf('/')); serviceInfo.applicationInfo = applicationInfo; applicationInfo.flags = 0; Mockito.when(tvInputInfo.getServiceInfo()).thenReturn(serviceInfo); Mockito.when(tvInputInfo.loadLabel(ArgumentMatchers.any())).thenReturn("testLabel"); if (VERSION.SDK_INT >= VERSION_CODES.N) { Mockito.when(tvInputInfo.loadCustomLabel(ArgumentMatchers.any())) .thenReturn("testCustomLabel"); } return tvInputInfo; } private static ComponentName createTrustedComponent() { return new ComponentName(CommonConstants.BASE_PACKAGE, "com.example.MyClass"); } /** * Test SetupUtils. * *

SetupUtils has lots of DB and threading interactions, that make it hard to test. This * bypasses all of that. */ private static class TestSetupUtils extends SetupUtils { public String finishedId; public Runnable finishedRunnable; private TestSetupUtils(Context context) { super(context, Optional.absent()); } @Override public void onTvInputSetupFinished(String inputId, @Nullable Runnable postRunnable) { finishedId = inputId; finishedRunnable = postRunnable; } } /** Test app for {@link SetupPassthroughActivityTest} */ public static class MyTestApp extends TestSingletonApp implements HasAndroidInjector { @Inject DispatchingAndroidInjector dispatchingAndroidInjector; @Override public void onCreate() { super.onCreate(); // Inject afterwards so we can use objects created in super. // Note TestSingletonApp does not do injection so it is safe applicationInjector().inject(this); } @Override public AndroidInjector androidInjector() { return dispatchingAndroidInjector; } protected AndroidInjector applicationInjector() { return DaggerSetupPassthroughActivityTest_TestComponent.builder() .applicationModule(new ApplicationModule(this)) .tvSingletonsModule(new TvSingletonsModule(this)) .testModule(new TestModule(this)) .settableFlagsModule(flagsModule) .build(); } } /** Dagger component for {@link SetupPassthroughActivityTest}. */ @Singleton @Component( modules = { AndroidInjectionModule.class, TestModule.class, }) interface TestComponent extends AndroidInjector {} @Module( includes = { SetupPassthroughActivity.Module.class, ApplicationModule.class, TvSingletonsModule.class, SettableFlagsModule.class, }) /** Module for {@link MyTestApp} */ static class TestModule { private final MyTestApp myTestApp; TestModule(MyTestApp test) { myTestApp = test; } @Provides Optional providesBuiltInTunerManager() { return Optional.absent(); } @Provides TvInputManagerHelper providesTvInputManagerHelper() { return myTestApp.tvInputManagerHelper; } @Provides SetupUtils providesTestSetupUtils() { return myTestApp.setupUtils; } @Provides @DbExecutor Executor providesDbExecutor() { return myTestApp.dbExecutor; } @Provides ChannelDataManager providesChannelDataManager() { return myTestApp.getChannelDataManager(); } @Provides EpgFetcher providesEpgFetcher() { return myTestApp.epgFetcher; } } }